Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67430a25a3 | ||
|
|
7f971f0256 | ||
|
|
a7d4d877a9 | ||
|
|
0c349bd4d5 | ||
|
|
a8f7c620d0 | ||
|
|
8077138609 | ||
|
|
9deb6a937b | ||
|
|
1259a6d70d | ||
|
|
14496d5d0b | ||
|
|
5f042e15eb | ||
|
|
6945c733e9 | ||
|
|
eea157b8d6 | ||
|
|
0c22eaa212 | ||
|
|
44542c4d29 | ||
|
|
2e398b7874 | ||
|
|
d0cd96e1df | ||
|
|
89b085381e |
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"title": "音频播放器控件功能完善",
|
||||
"features": [
|
||||
"播放列表管理(添加/删除/排序)",
|
||||
"音量调节控件",
|
||||
"多种播放模式(顺序/随机/单曲循环)",
|
||||
"播放列表导出/导入功能",
|
||||
"播放列表一键清空功能"
|
||||
],
|
||||
"tech": { "Web": { "arch": "vue", "component": "tdesign" } },
|
||||
"design": "现代简约风格,深色背景配合高对比度控件,主色调为TDesign主题蓝(#0052D9),包含固定底部播放控制区、垂直弹出式音量控制、图标式播放模式选择器和可拖拽排序的播放列表侧边栏。新增导出/导入功能使用TDesign对话框组件,提供文件导出/导入和内容复制/粘贴两种方式,所有导出内容进行加密处理。清空功能添加二次确认对话框防止误操作。",
|
||||
"plan": {
|
||||
"扩展Pinia状态管理,在ControlAudioStore中添加音量控制、播放模式和播放列表相关状态": "holding",
|
||||
"创建VolumeControl.vue组件,实现音量调节滑动条和静音按钮功能": "holding",
|
||||
"创建PlayModeSelector.vue组件,实现三种播放模式的切换功能": "holding",
|
||||
"创建PlaylistManager.vue组件,实现播放列表的基本显示功能": "holding",
|
||||
"在PlaylistManager组件中实现添加和删除曲目功能": "holding",
|
||||
"集成拖拽排序功能到PlaylistManager组件": "holding",
|
||||
"将新组件集成到主播放器界面,调整布局确保合理性": "holding",
|
||||
"实现播放列表与音频控制的联动,确保播放状态正确反映": "holding",
|
||||
"添加键盘快捷键支持和状态反馈机制": "holding",
|
||||
"进行组件间通信测试,确保功能协调工作": "holding",
|
||||
"创建播放列表加密/解密工具函数": "holding",
|
||||
"实现播放列表导出功能(文件导出和内容复制)": "holding",
|
||||
"实现播放列表导入功能(文件导入和内容粘贴)": "holding",
|
||||
"实现播放列表一键清空功能及二次确认机制": "holding",
|
||||
"测试导出/导入功能的加密解密正确性": "holding"
|
||||
}
|
||||
}
|
||||
11
.github/workflows/main.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false # 如果一个任务失败,其他任务继续运行
|
||||
matrix:
|
||||
os: [windows-latest, macos-latest] # 在Windows和macOS上运行任务
|
||||
os: [windows-latest, macos-latest, ubuntu-latest] # 在Windows和macOS上运行任务
|
||||
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
@@ -41,6 +41,10 @@ jobs:
|
||||
run: |
|
||||
yarn run build:mac
|
||||
|
||||
- name: Build Electron App for linux
|
||||
if: matrix.os == 'ubuntu-latest' # 只在Linux上运行
|
||||
run: yarn run build:linux # 构建Linux版应用
|
||||
|
||||
- name: Cleanup Artifacts for Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
@@ -51,6 +55,11 @@ jobs:
|
||||
run: |
|
||||
npx del-cli "dist/*" "!dist/(*.dmg|*.zip|latest*.yml)" # 清理macOS构建产物,只保留特定文件
|
||||
|
||||
- name: Cleanup Artifacts for Linux
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
npx del-cli "dist/*" "!dist/(*.AppImage|*.deb|*.snap|latest*.yml)" # 清理Linux构建产物,只保留特定文件
|
||||
|
||||
- name: upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
6
.gitignore
vendored
@@ -10,3 +10,9 @@ build
|
||||
/download/
|
||||
/plugin/
|
||||
/plugins/
|
||||
temp
|
||||
temp/log.txt
|
||||
/.kiro/
|
||||
/.vscode/
|
||||
/.codebuddy/
|
||||
/.idea/
|
||||
@@ -1,22 +0,0 @@
|
||||
# 语言设置
|
||||
|
||||
## 对话语言
|
||||
- **主要语言**: 中文(简体中文)
|
||||
- 与用户对话时请使用中文回复
|
||||
- 代码注释和文档也应该使用中文
|
||||
- 变量名和函数名仍使用英文(遵循编程规范)
|
||||
|
||||
## 代码规范
|
||||
- 代码本身使用英文命名
|
||||
- 注释使用中文说明
|
||||
- 错误信息和用户界面文本使用中文
|
||||
- README和文档文件使用中文编写
|
||||
|
||||
## 示例
|
||||
```typescript
|
||||
// 播放音乐的函数
|
||||
function playMusic(songId: string): void {
|
||||
// 开始播放指定的歌曲
|
||||
console.log('正在播放音乐...')
|
||||
}
|
||||
```
|
||||
@@ -1,16 +0,0 @@
|
||||
# Product Overview
|
||||
|
||||
**Ceru Music** is a free music application built as an Electron desktop app. It provides a cross-platform music experience for Windows, macOS, and Linux users.
|
||||
|
||||
## Key Features
|
||||
- Desktop music player application
|
||||
- Cross-platform compatibility (Windows, macOS, Linux)
|
||||
- Modern UI built with Vue.js and TDesign components
|
||||
- Auto-updating capabilities via electron-updater
|
||||
|
||||
## Target Platforms
|
||||
- Windows (primary build target)
|
||||
- macOS
|
||||
- Linux
|
||||
|
||||
The application follows Electron's multi-process architecture with separate main, renderer, and preload processes for security and performance.
|
||||
@@ -1,55 +0,0 @@
|
||||
# Project Structure
|
||||
|
||||
## Root Level Organization
|
||||
```
|
||||
├── src/ # Source code
|
||||
├── resources/ # App resources (icons, etc.)
|
||||
├── build/ # Build artifacts
|
||||
├── out/ # Compiled output
|
||||
└── node_modules/ # Dependencies
|
||||
```
|
||||
|
||||
## Source Code Architecture (`src/`)
|
||||
|
||||
### Electron Multi-Process Structure
|
||||
```
|
||||
src/
|
||||
├── main/ # Main process (Node.js)
|
||||
│ └── index.ts # Entry point for main process
|
||||
├── preload/ # Preload scripts (security bridge)
|
||||
│ ├── index.ts # Preload script implementation
|
||||
│ └── index.d.ts # Type definitions
|
||||
└── renderer/ # Renderer process (Vue app)
|
||||
├── src/ # Vue application source
|
||||
├── index.html # HTML entry point
|
||||
├── auto-imports.d.ts # Auto-generated import types
|
||||
└── components.d.ts # Auto-generated component types
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
|
||||
### TypeScript Configuration
|
||||
- `tsconfig.json`: Root config with project references
|
||||
- `tsconfig.node.json`: Node.js/Electron main process config
|
||||
- `tsconfig.web.json`: Web/renderer process config
|
||||
|
||||
### Build & Development
|
||||
- `electron.vite.config.ts`: Vite configuration for Electron
|
||||
- `electron-builder.yml`: App packaging configuration
|
||||
- `package.json`: Dependencies and scripts
|
||||
|
||||
### Code Quality
|
||||
- `eslint.config.mjs`: ESLint configuration (flat config)
|
||||
- `.prettierrc.yaml`: Prettier formatting rules
|
||||
- `.editorconfig`: Editor configuration
|
||||
|
||||
## Key Conventions
|
||||
- **Renderer alias**: Use `@renderer/*` for renderer source imports
|
||||
- **Auto-imports**: TDesign components and Vue composables are auto-imported
|
||||
- **Process separation**: Maintain clear boundaries between main, preload, and renderer
|
||||
- **TypeScript**: All source files should use TypeScript (.ts/.vue)
|
||||
|
||||
## File Naming
|
||||
- Use kebab-case for component files
|
||||
- Use camelCase for TypeScript files
|
||||
- Vue components should be multi-word (ESLint enforced, but disabled)
|
||||
@@ -1,52 +0,0 @@
|
||||
# Technology Stack
|
||||
|
||||
## Core Technologies
|
||||
- **Electron**: Desktop app framework (v37.2.3)
|
||||
- **Vue 3**: Frontend framework with Composition API (v3.5.17)
|
||||
- **TypeScript**: Primary language for type safety
|
||||
- **Vite**: Build tool and dev server via electron-vite
|
||||
- **PNPM**: Package manager (preferred over npm/yarn)
|
||||
|
||||
## UI Framework
|
||||
- **TDesign Vue Next**: Primary UI component library (v1.15.2)
|
||||
- **SCSS**: Styling preprocessor
|
||||
- **Auto-import**: Automatic component and composable imports
|
||||
|
||||
## State Management & Routing
|
||||
- **Pinia**: State management (v3.0.3)
|
||||
- **Vue Router**: Client-side routing (v4.5.1)
|
||||
|
||||
## Development Tools
|
||||
- **ESLint**: Code linting with Electron Toolkit configs
|
||||
- **Prettier**: Code formatting
|
||||
- **Vue TSC**: Vue TypeScript checking
|
||||
|
||||
## Common Commands
|
||||
|
||||
### Development
|
||||
```bash
|
||||
pnpm dev # Start development server
|
||||
pnpm start # Preview built app
|
||||
```
|
||||
|
||||
### Code Quality
|
||||
```bash
|
||||
pnpm lint # Run ESLint
|
||||
pnpm format # Format code with Prettier
|
||||
pnpm typecheck # Run TypeScript checks
|
||||
```
|
||||
|
||||
### Building
|
||||
```bash
|
||||
pnpm build # Build for current platform
|
||||
pnpm build:win # Build for Windows
|
||||
pnpm build:mac # Build for macOS
|
||||
pnpm build:linux # Build for Linux
|
||||
pnpm build:unpack # Build without packaging
|
||||
```
|
||||
|
||||
## Build System
|
||||
- **electron-vite**: Vite-based build system for Electron
|
||||
- **electron-builder**: Application packaging and distribution
|
||||
- Separate TypeScript configs for Node.js and web contexts
|
||||
- Auto-updating via electron-updater
|
||||
3
.vscode/extensions.json
vendored
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"recommendations": ["dbaeumer.vscode-eslint"]
|
||||
}
|
||||
39
.vscode/launch.json
vendored
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug Main Process",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd"
|
||||
},
|
||||
"runtimeArgs": ["--sourcemap"],
|
||||
"env": {
|
||||
"REMOTE_DEBUGGING_PORT": "9222"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Debug Renderer Process",
|
||||
"port": 9222,
|
||||
"request": "attach",
|
||||
"type": "chrome",
|
||||
"webRoot": "${workspaceFolder}/src/renderer",
|
||||
"timeout": 60000,
|
||||
"presentation": {
|
||||
"hidden": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Debug All",
|
||||
"configurations": ["Debug Main Process", "Debug Renderer Process"],
|
||||
"presentation": {
|
||||
"order": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
12
.vscode/settings.json
vendored
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "vscode.json-language-features"
|
||||
},
|
||||
"editor.fontSize": 14
|
||||
}
|
||||
14
README.md
@@ -29,25 +29,25 @@ Ceru Music 是基于 Electron 和 Vue 开发的跨平台桌面音乐播放器,
|
||||
|
||||
- **IDE**: VS Code 或 WebStorm
|
||||
- **Node.js 版本**: 推荐使用最新稳定版
|
||||
- **包管理器**: pnpm
|
||||
- **包管理器**: yarn
|
||||
|
||||
### 项目设置
|
||||
|
||||
1. 安装依赖:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
yarn install
|
||||
```
|
||||
|
||||
2. 启动开发服务器:
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
yarn dev
|
||||
```
|
||||
|
||||
3. 构建应用:
|
||||
```bash
|
||||
pnpm build
|
||||
yarn build
|
||||
```
|
||||
|
||||
### 平台构建指令
|
||||
@@ -55,18 +55,18 @@ Ceru Music 是基于 Electron 和 Vue 开发的跨平台桌面音乐播放器,
|
||||
- **Windows**:
|
||||
|
||||
```bash
|
||||
pnpm build:win
|
||||
yarn build:win
|
||||
```
|
||||
|
||||
- **macOS**:
|
||||
|
||||
```bash
|
||||
pnpm build:mac
|
||||
yarn build:mac
|
||||
```
|
||||
|
||||
- **Linux**:
|
||||
```bash
|
||||
pnpm build:linux
|
||||
yarn build:linux
|
||||
```
|
||||
|
||||
## 文档与资源
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
provider: generic
|
||||
url: https://example.com/auto-updates
|
||||
url: https://update.ceru.shiqianjiang.cn
|
||||
updaterCacheDirName: ceru-music-updater
|
||||
|
||||
BIN
docs/assets/3f50d3b838287b4bf1523d0f955fdf37.png
Normal file
|
After Width: | Height: | Size: 173 KiB |
BIN
docs/assets/image-20250826214921963.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
docs/assets/image-20250826215101522.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
docs/assets/image-20250826215206862.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
docs/assets/image-20250826215251525.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
docs/assets/image-20250826221438856.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
docs/assets/image-20250826221517247.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
121
docs/auto-update.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# 自动更新功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
本项目集成了完整的自动更新功能,使用 Electron 的 `autoUpdater` 模块和 TDesign 的通知组件,为用户提供友好的更新体验。
|
||||
|
||||
## 架构设计
|
||||
|
||||
### 主进程 (Main Process)
|
||||
|
||||
1. **autoUpdate.ts** - 自动更新核心逻辑
|
||||
- 配置更新服务器地址
|
||||
- 监听 autoUpdater 事件
|
||||
- 通过 IPC 向渲染进程发送更新消息
|
||||
|
||||
2. **events/autoUpdate.ts** - IPC 事件处理
|
||||
- 注册检查更新和安装更新的 IPC 处理器
|
||||
|
||||
### 渲染进程 (Renderer Process)
|
||||
|
||||
1. **services/autoUpdateService.ts** - 更新服务
|
||||
- 处理来自主进程的更新消息
|
||||
- 使用 TDesign Notification 显示更新通知
|
||||
- 管理更新状态和用户交互
|
||||
|
||||
2. **composables/useAutoUpdate.ts** - Vue 组合式函数
|
||||
- 封装自动更新功能,便于在组件中使用
|
||||
- 管理监听器的生命周期
|
||||
|
||||
3. **components/Settings/UpdateSettings.vue** - 更新设置组件
|
||||
- 提供手动检查更新的界面
|
||||
- 显示当前版本信息
|
||||
|
||||
## 更新流程
|
||||
|
||||
1. **启动检查**: 应用启动后延迟3秒自动检查更新
|
||||
2. **检查更新**: 向更新服务器发送请求检查新版本
|
||||
3. **下载更新**: 如有新版本,自动下载更新包
|
||||
4. **安装提示**: 下载完成后提示用户重启安装
|
||||
5. **自动安装**: 用户确认后退出应用并安装更新
|
||||
|
||||
## 通知类型
|
||||
|
||||
- **检查更新**: 显示正在检查更新的信息通知
|
||||
- **发现新版本**: 显示发现新版本并开始下载的成功通知
|
||||
- **无需更新**: 显示当前已是最新版本的信息通知
|
||||
- **下载进度**: 实时显示下载进度和速度
|
||||
- **下载完成**: 显示下载完成并提供重启按钮
|
||||
- **更新错误**: 显示更新过程中的错误信息
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 更新服务器配置
|
||||
|
||||
在 `src/main/autoUpdate.ts` 中配置更新服务器地址:
|
||||
|
||||
```typescript
|
||||
const server = 'https://update.ceru.shiqianjiang.cn/';
|
||||
```
|
||||
|
||||
### 版本检查
|
||||
|
||||
更新服务器需要提供以下格式的 API:
|
||||
- URL: `${server}/update/${platform}/${currentVersion}`
|
||||
- 返回: 更新信息 JSON
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 在组件中使用
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { useAutoUpdate } from '@/composables/useAutoUpdate'
|
||||
|
||||
const { checkForUpdates } = useAutoUpdate()
|
||||
|
||||
// 手动检查更新
|
||||
const handleCheckUpdate = async () => {
|
||||
await checkForUpdates()
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 监听更新消息
|
||||
|
||||
```typescript
|
||||
import { autoUpdateService } from '@/services/autoUpdateService'
|
||||
|
||||
// 开始监听
|
||||
autoUpdateService.startListening()
|
||||
|
||||
// 停止监听
|
||||
autoUpdateService.stopListening()
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **权限要求**: 自动更新需要应用具有写入权限
|
||||
2. **网络连接**: 需要稳定的网络连接来下载更新
|
||||
3. **用户体验**: 更新过程中避免强制重启,给用户选择权
|
||||
4. **错误处理**: 妥善处理网络错误和下载失败的情况
|
||||
|
||||
## 开发调试
|
||||
|
||||
在开发环境中,可以通过以下方式测试自动更新:
|
||||
|
||||
1. 修改 `package.json` 中的版本号
|
||||
2. 在更新设置页面手动触发检查更新
|
||||
3. 观察控制台日志和通知显示
|
||||
|
||||
## 构建配置
|
||||
|
||||
确保在 `electron-builder` 配置中启用自动更新:
|
||||
|
||||
```json
|
||||
{
|
||||
"publish": {
|
||||
"provider": "generic",
|
||||
"url": "https://update.ceru.shiqianjiang.cn/"
|
||||
}
|
||||
}
|
||||
1587
docs/design.html
Normal file
51
docs/使用文档.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# CeruMusic 使用教程
|
||||
|
||||
## 1. 软件下载
|
||||
|
||||
由于我们团段都是个人开发者原因 暂时无能力部署到 `OSS` 承担高下载量的能力,供大家下载只能通过[Github](https://github.com/timeshiftsauce/CeruMusic)下载安装使用
|
||||
|
||||
### Window 安装
|
||||
|
||||
由于没有证书原因 **`Window`** 平台可能会出现安装包体误报**危险**。请放心我们的软件都是**开源**在 `Github` 自动化打包的。**具体安装步骤如下**
|
||||
|
||||
<img src="assets/image-20250826214921963.png" alt="image-20250826214921963" style="zoom: 50%;" />如果出现类似图例效果请先点击 **右侧 三个点**
|
||||
|
||||
|
||||
|
||||
<img src="assets/image-20250826215101522.png" alt="image-20250826215101522" style="zoom:50%;" />**点击保留**
|
||||
|
||||
|
||||
|
||||
<img src="assets/image-20250826215206862.png" alt="image-20250826215206862" style="zoom:50%;" />**点击下拉按钮**
|
||||
|
||||
|
||||
|
||||
<img src="assets/image-20250826215251525.png" alt="image-20250826215251525" style="zoom:50%;" />**任然保留**就可以双击打开安装到此教程结束
|
||||
|
||||
### Mac OS 系统下载安装
|
||||
|
||||
由于同样没有**签名**的原因mac的护栏也会拦截提示安装包损坏
|
||||
|
||||
<img src="assets/3f50d3b838287b4bf1523d0f955fdf37.png" alt="3f50d3b838287b4bf1523d0f955fdf37" style="zoom:50%;" />请不用担心这是典型的签名问题
|
||||
|
||||
适用于 macOS 14 Sonoma 及以上版本。
|
||||
|
||||
注意:由于我们不提供经过签名的程序包体,因此在安装后首次运行可能会出现 “**澜音** 已损坏” 之类的提示,此时只需打开终端,输入命令
|
||||
|
||||
```bash
|
||||
sudo xattr -r -d com.apple.quarantine /Applications/澜音.app
|
||||
```
|
||||
|
||||
并回车,输入密码再次回车,重新尝试启动程序即可
|
||||
|
||||
*要是还有问题可自行在搜索引擎查询由于 。```apple```官方证书需要99刀的价格实在无能为力见谅* 如果你有能力成为`澜音`的赞助者可联系
|
||||
|
||||
- QQ:`2115295703`
|
||||
- 微信:`cl_wj0623`
|
||||
|
||||
### 插件安装
|
||||
|
||||
首次进入应用需要在软件右上角设置导入**音源**才能使用可查询`Ceru插件`**(目前生态欠缺)** 或现成的**落雪**插件导入使用
|
||||
|
||||
###### 导入完成点击使用
|
||||
|
||||
@@ -61,6 +61,6 @@ appImage:
|
||||
npmRebuild: false
|
||||
publish:
|
||||
provider: generic
|
||||
url: https://example.com/auto-updates
|
||||
url: https://update.ceru.shiqianjiang.cn
|
||||
electronDownload:
|
||||
mirror: https://npmmirror.com/mirrors/electron/
|
||||
|
||||
@@ -58,25 +58,25 @@ const vueRule = {
|
||||
'vue/use-v-on-exact': 'off'
|
||||
}
|
||||
|
||||
exports.base = {
|
||||
export const base = {
|
||||
extends: ['standard'],
|
||||
rules: baseRule,
|
||||
parser: '@babel/eslint-parser'
|
||||
}
|
||||
|
||||
exports.html = {
|
||||
export const html = {
|
||||
files: ['*.html'],
|
||||
plugins: ['html']
|
||||
}
|
||||
|
||||
exports.typescript = {
|
||||
export const typescript = {
|
||||
files: ['*.ts'],
|
||||
rules: typescriptRule,
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: ['standard-with-typescript']
|
||||
}
|
||||
|
||||
exports.vue = {
|
||||
export const vue = {
|
||||
files: ['*.vue'],
|
||||
rules: vueRule,
|
||||
parser: 'vue-eslint-parser',
|
||||
|
||||
16
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ceru-music",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.9",
|
||||
"description": "一款简洁优雅的音乐播放器",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "sqj,wldss,star",
|
||||
@@ -11,17 +11,17 @@
|
||||
"lint": "eslint --cache . --fix",
|
||||
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
|
||||
"typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false",
|
||||
"typecheck": "npm run typecheck:node && npm run typecheck:web",
|
||||
"typecheck": "yarn run typecheck:node && yarn run typecheck:web",
|
||||
"start": "electron-vite preview",
|
||||
"dev": "electron-vite dev --watch",
|
||||
"build": "npm run typecheck && electron-vite build",
|
||||
"build": "yarn run typecheck && electron-vite build",
|
||||
"onlybuild": "electron-vite build && electron-builder --win --x64",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"build:unpack": "npm run build && electron-builder --dir",
|
||||
"build:win": "npm run build && electron-builder --win --x64 --config --publish never",
|
||||
"build:mac": "npm run build && electron-builder --mac --config --publish never",
|
||||
"build:linux": "npm run build && electron-builder --linux --publish never",
|
||||
"build:deps": "electron-builder install-app-deps && npm run build && electron-builder --win --x64 --config",
|
||||
"build:unpack": "yarn run build && electron-builder --dir",
|
||||
"build:win": "yarn run build && electron-builder --win --x64 --config --publish never",
|
||||
"build:mac": "yarn run build && electron-builder --mac --config --publish never",
|
||||
"build:linux": "yarn run build && electron-builder --linux --config --publish never",
|
||||
"build:deps": "electron-builder install-app-deps && yarn run build && electron-builder --win --x64 --config",
|
||||
"buildico": "electron-icon-builder --input=./resources/logo.png --output=resources --flatten"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
log "[CeruMusic] Plugin \"ceru 音乐插件\" loaded successfully."
|
||||
log "[ceru 音乐插件] 请求音乐链接: https://www.lihouse.xyz/coco_widget/music_resource/id/2645500113"
|
||||
log "[CeruMusic] 发起请求: https://www.lihouse.xyz/coco_widget/music_resource/id/2645500113"
|
||||
log "[CeruMusic] 请求响应状态: 200"
|
||||
error "[ceru 音乐插件] 请求失败: 歌曲不存在"
|
||||
error "[ceru 音乐插件] Error: 歌曲不存在"
|
||||
log "[CeruMusic] 请求响应内容: [object Object]"
|
||||
log "[CeruMusic] 响应不是JSON格式,内容: {\"status\":true,\"song_data\":{\"id\":2645500113,\"name\":\"跳楼机\",\"artist\":\"LBI利比\",\"album\":\"跳楼机\",\"pic_id\":\"109951170507596121\",\"url_id\":2645500113,\"lyric_id\":2645500113,\"source\":\"netease\",\"lyric\":\"[00:00.00] 作..."log "[ceru 音乐插件] 请求音乐链接: https://www.lihouse.xyz/coco_widget/music_resource/id/2645500113"
|
||||
log "[CeruMusic] 发起请求: https://www.lihouse.xyz/coco_widget/music_resource/id/2645500113"
|
||||
log "[ceru 音乐插件] 请求音乐链接: https://www.lihouse.xyz/coco_widget/music_resource/id/2645500113"
|
||||
log "[CeruMusic] 发起请求: https://www.lihouse.xyz/coco_widget/music_resource/id/2645500113"
|
||||
log "[ceru 音乐插件] 请求音乐链接: https://www.lihouse.xyz/coco_widget/music_resource/id/1135545898"
|
||||
log "[CeruMusic] 发起请求: https://www.lihouse.xyz/coco_widget/music_resource/id/1135545898"
|
||||
log "[CeruMusic] 发起请求: https://www.lihouse.xyz/coco_widget/music_resource/id/1139605559"
|
||||
log "[ceru 音乐插件] 请求音乐链接: https://www.lihouse.xyz/coco_widget/music_resource/id/1139605559"
|
||||
log "[ceru 音乐插件] 请求音乐链接: https://www.lihouse.xyz/coco_widget/music_resource/id/2149456401"
|
||||
log "[CeruMusic] 发起请求: https://www.lihouse.xyz/coco_widget/music_resource/id/2149456401"
|
||||
log "[ceru 音乐插件] 请求音乐链接: https://www.lihouse.xyz/coco_widget/music_resource/id/2003594562"
|
||||
log "[CeruMusic] 发起请求: https://www.lihouse.xyz/coco_widget/music_resource/id/2003594562"
|
||||
log "[CeruMusic] 请求响应状态: 200"
|
||||
log "[CeruMusic] 请求响应内容: [object Object]"
|
||||
log "[CeruMusic] 响应不是JSON格式,内容: ..."
|
||||
error "[ceru 音乐插件] Error: 歌曲不存在"
|
||||
error "[ceru 音乐插件] 请求失败: 歌曲不存在"
|
||||
log "[CeruMusic] 请求响应状态: 200"
|
||||
log "[CeruMusic] 响应不是JSON格式,内容: ..."
|
||||
log "[CeruMusic] 请求响应内容: [object Object]"
|
||||
error "[ceru 音乐插件] Error: 歌曲不存在"
|
||||
error "[ceru 音乐插件] 请求失败: 歌曲不存在"
|
||||
log "[CeruMusic] 请求响应状态: 200"
|
||||
log "[CeruMusic] 响应不是JSON格式,内容: {\"status\":true,\"song_data\":{\"id\":2645500113,\"name\":\"跳楼机\",\"artist\":\"LBI利比\",\"album\":\"跳楼机\",\"pic_id\":\"109951170507596121\",\"url_id\":2645500113,\"lyric_id\":2645500113,\"source\":\"netease\",\"lyric\":\"[00:00.00] 作..."
|
||||
error "[ceru 音乐插件] Error: 歌曲不存在"
|
||||
error "[ceru 音乐插件] 请求失败: 歌曲不存在"
|
||||
log "[CeruMusic] 请求响应内容: [object Object]"
|
||||
log "[CeruMusic] 请求响应状态: 200"
|
||||
log "[CeruMusic] 响应不是JSON格式,内容: {\"status\":true,\"song_data\":{\"id\":2645500113,\"name\":\"跳楼机\",\"artist\":\"LBI利比\",\"album\":\"跳楼机\",\"pic_id\":\"109951170507596121\",\"url_id\":2645500113,\"lyric_id\":2645500113,\"source\":\"netease\",\"lyric\":\"[00:00.00] 作..."
|
||||
log "[CeruMusic] 请求响应内容: [object Object]"
|
||||
error "[ceru 音乐插件] Error: 歌曲不存在"
|
||||
error "[ceru 音乐插件] 请求失败: 歌曲不存在"
|
||||
log "[CeruMusic] 请求响应状态: 200"
|
||||
log "[CeruMusic] 响应不是JSON格式,内容: {\"status\":true,\"song_data\":{\"id\":2149456401,\"name\":\"alone.\",\"artist\":\"DLSS\",\"album\":\"alone.\",\"pic_id\":\"109951169530908047\",\"url_id\":2149456401,\"lyric_id\":2149456401,\"source\":\"netease\",\"lyric\":\"[00:00...."
|
||||
log "[CeruMusic] 请求响应内容: [object Object]"
|
||||
error "[ceru 音乐插件] Error: 歌曲不存在"
|
||||
error "[ceru 音乐插件] 请求失败: 歌曲不存在"
|
||||
log "[CeruMusic] 请求响应状态: 200"
|
||||
log "[CeruMusic] 响应不是JSON格式,内容: {\"status\":true,\"song_data\":{\"id\":2003594562,\"name\":\"天晴\",\"artist\":\"是二智呀\",\"album\":\"天晴\",\"pic_id\":\"109951168114308737\",\"url_id\":2003594562,\"lyric_id\":2003594562,\"source\":\"netease\",\"lyric\":\"[00:00.06]作词:陈澈..."
|
||||
error "[ceru 音乐插件] Error: 歌曲不存在"
|
||||
log "[CeruMusic] 请求响应内容: [object Object]"
|
||||
error "[ceru 音乐插件] 请求失败: 歌曲不存在"
|
||||
log "[CeruMusic] Plugin \"ceru 音乐插件\" loaded successfully."
|
||||
log "[CeruMusic] 发起请求: https://www.lihouse.xyz/coco_widget/music_resource/id/2645500113"
|
||||
log "[ceru 音乐插件] 请求音乐链接: https://www.lihouse.xyz/coco_widget/music_resource/id/2645500113"
|
||||
log "[CeruMusic] Plugin \"ceru 音乐插件\" loaded successfully."
|
||||
log "[ceru 音乐插件] 请求音乐链接: https://www.lihouse.xyz/coco_widget/music_resource/id/2645500113"
|
||||
log "[CeruMusic] 发起请求: https://www.lihouse.xyz/coco_widget/music_resource/id/2645500113"
|
||||
log "[CeruMusic] 请求响应状态: 200"
|
||||
log "[CeruMusic] 请求响应内容: [object Object]"log "[CeruMusic] Plugin \"ceru 音乐插件\" loaded successfully."
|
||||
log "[CeruMusic] Plugin \"ceru 音乐插件\" loaded successfully."
|
||||
log "[CeruMusic] Plugin \"ceru 音乐插件\" loaded successfully."
|
||||
log "[CeruMusic] Plugin \"ceru 音乐插件\" loaded successfully."
|
||||
log "[CeruMusic] Plugin \"ceru 音乐插件\" loaded successfully."
|
||||
log "[CeruMusic] Plugin \"ceru 音乐插件\" loaded successfully."
|
||||
log "[CeruMusic] Plugin \"ceru 音乐插件\" loaded successfully."
|
||||
log "[CeruMusic] Plugin \"ceru 音乐插件\" loaded successfully."
|
||||
log "[CeruMusic] Plugin \"ceru 音乐插件\" loaded successfully."
|
||||
log "[CeruMusic] Plugin \"ceru 音乐插件\" loaded successfully."
|
||||
log "[CeruMusic] Plugin \"ceru 音乐插件\" loaded successfully."
|
||||
log "[CeruMusic] Plugin \"ceru 音乐插件\" loaded successfully."
|
||||
log "[CeruMusic] Plugin \"ceru 音乐插件\" loaded successfully."
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [ceru 音乐插件] 请求音乐链接: https://www.lihouse.xyz/coco_widget/music_resource/id/254574
|
||||
log [CeruMusic] 发起请求: https://www.lihouse.xyz/coco_widget/music_resource/id/254574
|
||||
log [CeruMusic] 请求响应状态: 200
|
||||
log [CeruMusic] 请求响应内容: {"status":true,"song_data":{"id":254574,"name":"后来","artist":"刘若英","album":"我等你","pic_id":"109951163351825356","url_id":254574,"lyric_id":254574,"source":"netease","lyric":"[00:00.000] 作词 : 施人诚\n[00:01.000] 作曲 : 玉城千春\n[00:02.000] 编曲 : 王继康\n[00:03.000] 制作人 : 光良\n[00:12.571]后来 我总算学会了如何去爱\n[00:19.303]可惜你 早已远去 消失在人海\n[00:25.238]后来 终于在眼泪中明白\n[00:32.064]有些人 一旦错过就不在\n[00:39.800]栀子花 白花瓣\n[00:46.027]落在我蓝色百褶裙上\n[00:51.763]爱你 你轻声说\n[00:58.844]我低下头 闻见一阵芬芳\n[01:05.371]那个永恒的夜晚\n[01:09.341]十七岁仲夏\n[01:12.104]你吻我的那个夜晚\n[01:18.232]让我往后的时光\n[01:21.749]每当有感叹\n[01:24.862]总想起当天的星光\n[01:30.992]那时候的爱情\n[01:37.422]为什么就能那样简单\n[01:42.493]而又是为什么 人年少时\n[01:50.179]一定要让深爱的人受伤\n[01:56.301]在这相似的深夜里\n[02:00.566]你是否一样 也在静静追悔感伤\n[02:09.350]如果当时我们能\n[02:13.015]不那么倔强\n[02:16.079]现在也 不那么遗憾\n[02:21.053]你都如何回忆我\n[02:24.014]带着笑或是很沉默\n[02:27.231]这些年来\n[02:28.837]有没有人能让你不寂寞\n[02:33.455]后来 我总算学会了如何去爱\n[02:40.238]可惜你 早已远去 消失在人海\n[02:46.318]后来 终于在眼泪中明白\n[02:53.042]有些人 一旦错过就不在\n[03:25.379]你都如何回忆我\n[03:28.132]带着笑或是很沉默\n[03:31.296]这些年来\n[03:32.953]有没有人能让你不寂寞\n[03:37.523]后来\n[03:39.530]我总算学会了如何去爱\n[03:44.301]可惜你 早已远去 消失在人海\n[03:50.174]后来 终于在眼泪中明白\n[03:57.104]有些人 一旦错过就不在\n[04:03.136]后来 我总算学会了如何去爱\n[02:59.120]\n[04:09.965]可惜你 早已远去 消失在人海\n[04:15.937]后来 终于在眼泪中明白\n[04:22.771]有些人 一旦错过就不在\n[04:29.598]永远不会再重来\n[04:35.510]有一个男孩爱着那个女孩\n[05:02.681]录音工程师 : 周建平 & Peter Chong (MAL)\n[05:03.781]混音工程师 : 王晋溢\n[05:04.881]录音室 : 捷奏 & Synchrosound (MAL)\n[05:05.981]混音室 : 白金录音室\n[05:06.111]吉他 : Jamie Wilson\n[05:07.222]和声编写 : 光良\n[05:08.333]和声 : 梁静茹\n[05:09.444]OP : Victor Music Publishing Co., Ltd.\n[05:10.555]ISRC TW-A45-99-62502\n","pic":"https://p3.music.126.net/eBF7bHnJYBUfOFrJ_7SUfw==/109951163351825356.jpg?param=300y300","url":"https://m701.music.126.net/20250822145154/db9d934a5c833fe5bbe68e21bfd4720f/jdymusic/obj/wo3DlMOGwrbDjj7DisKw/14783185971/f717/93d1/d743/037c337c4123b835758717a3e2e9286b.mp3","size":13658950,"br":320}}
|
||||
log [ceru 音乐插件] 请求音乐链接: https://www.lihouse.xyz/coco_widget/music_resource/id/254574
|
||||
log [CeruMusic] 发起请求: https://www.lihouse.xyz/coco_widget/music_resource/id/254574
|
||||
log [CeruMusic] 请求响应状态: 200
|
||||
log [CeruMusic] 请求响应内容: {"status":true,"song_data":{"id":254574,"name":"后来","artist":"刘若英","album":"我等你","pic_id":"109951163351825356","url_id":254574,"lyric_id":254574,"source":"netease","lyric":"[00:00.000] 作词 : 施人诚\n[00:01.000] 作曲 : 玉城千春\n[00:02.000] 编曲 : 王继康\n[00:03.000] 制作人 : 光良\n[00:12.571]后来 我总算学会了如何去爱\n[00:19.303]可惜你 早已远去 消失在人海\n[00:25.238]后来 终于在眼泪中明白\n[00:32.064]有些人 一旦错过就不在\n[00:39.800]栀子花 白花瓣\n[00:46.027]落在我蓝色百褶裙上\n[00:51.763]爱你 你轻声说\n[00:58.844]我低下头 闻见一阵芬芳\n[01:05.371]那个永恒的夜晚\n[01:09.341]十七岁仲夏\n[01:12.104]你吻我的那个夜晚\n[01:18.232]让我往后的时光\n[01:21.749]每当有感叹\n[01:24.862]总想起当天的星光\n[01:30.992]那时候的爱情\n[01:37.422]为什么就能那样简单\n[01:42.493]而又是为什么 人年少时\n[01:50.179]一定要让深爱的人受伤\n[01:56.301]在这相似的深夜里\n[02:00.566]你是否一样 也在静静追悔感伤\n[02:09.350]如果当时我们能\n[02:13.015]不那么倔强\n[02:16.079]现在也 不那么遗憾\n[02:21.053]你都如何回忆我\n[02:24.014]带着笑或是很沉默\n[02:27.231]这些年来\n[02:28.837]有没有人能让你不寂寞\n[02:33.455]后来 我总算学会了如何去爱\n[02:40.238]可惜你 早已远去 消失在人海\n[02:46.318]后来 终于在眼泪中明白\n[02:53.042]有些人 一旦错过就不在\n[03:25.379]你都如何回忆我\n[03:28.132]带着笑或是很沉默\n[03:31.296]这些年来\n[03:32.953]有没有人能让你不寂寞\n[03:37.523]后来\n[03:39.530]我总算学会了如何去爱\n[03:44.301]可惜你 早已远去 消失在人海\n[03:50.174]后来 终于在眼泪中明白\n[03:57.104]有些人 一旦错过就不在\n[04:03.136]后来 我总算学会了如何去爱\n[02:59.120]\n[04:09.965]可惜你 早已远去 消失在人海\n[04:15.937]后来 终于在眼泪中明白\n[04:22.771]有些人 一旦错过就不在\n[04:29.598]永远不会再重来\n[04:35.510]有一个男孩爱着那个女孩\n[05:02.681]录音工程师 : 周建平 & Peter Chong (MAL)\n[05:03.781]混音工程师 : 王晋溢\n[05:04.881]录音室 : 捷奏 & Synchrosound (MAL)\n[05:05.981]混音室 : 白金录音室\n[05:06.111]吉他 : Jamie Wilson\n[05:07.222]和声编写 : 光良\n[05:08.333]和声 : 梁静茹\n[05:09.444]OP : Victor Music Publishing Co., Ltd.\n[05:10.555]ISRC TW-A45-99-62502\n","pic":"https://p3.music.126.net/eBF7bHnJYBUfOFrJ_7SUfw==/109951163351825356.jpg?param=300y300","url":"https://m701.music.126.net/20250822145155/4f1e01f33ad960791480b07624819f2a/jdymusic/obj/wo3DlMOGwrbDjj7DisKw/14783185971/f717/93d1/d743/037c337c4123b835758717a3e2e9286b.mp3","size":13658950,"br":320}}
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
log [CeruMusic] Plugin "ceru 音乐插件" loaded successfully.
|
||||
@@ -1,100 +0,0 @@
|
||||
log [CeruMusic] Plugin "未知插件" loaded successfully.log [插件] 注册事件监听器: request
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/urlinfo/1.0.0
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [未知插件] 使用事件驱动方式获取 kw 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/kw/254574/320k
|
||||
log [CeruMusic] 请求响应状态: 503
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
log [插件] 音源注册完成: kw,wy,mg,tx,kg
|
||||
log [插件] 发送事件: inited [object Object]
|
||||
error [CeruMusic] Request failed: Expected JSON response but got: text/html
|
||||
log [CeruMusic] 响应不是JSON格式,内容: <html>
|
||||
<head><title>503 Service Temporarily Unavailable</title></head>
|
||||
<body>
|
||||
<center><h1>503 Service Temporarily Unavailable</h1></center>
|
||||
<hr><center>nginx/1.26.1</center>
|
||||
</body>
|
||||
</html>
|
||||
...
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
log [未知插件] 使用事件驱动方式获取 kw 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/kw/254574/320k
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/kw/254574/320k
|
||||
log [未知插件] 使用事件驱动方式获取 kw 音源链接
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
log [未知插件] 使用事件驱动方式获取 kw 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/kw/254574/320k
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [未知插件] 使用事件驱动方式获取 kw 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/kw/254574/320k
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
log [未知插件] 使用事件驱动方式获取 kw 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/kw/254574/320k
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [未知插件] 使用事件驱动方式获取 kw 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/kw/254574/320k
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
log [未知插件] 使用事件驱动方式获取 kw 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/kw/254574/320k
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
log [未知插件] 使用事件驱动方式获取 kw 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/kw/254574/320k
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
log [未知插件] 使用事件驱动方式获取 kw 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/kw/254574/320k
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
log [未知插件] 使用事件驱动方式获取 kw 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/kw/254574/320k
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [未知插件] 使用事件驱动方式获取 kw 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/kw/254574/320k
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [未知插件] 使用事件驱动方式获取 kw 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/kw/254574/320k
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [未知插件] 使用事件驱动方式获取 kw 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/kw/2061973302/320k
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
@@ -1,28 +0,0 @@
|
||||
log "[CeruMusic] Plugin \"LiHouse 音乐插件\" loaded successfully."log "[LiHouse 音乐插件] 请求音乐链接: https://www.lihouse.xyz/coco_widget/music_resource/id/1135545898"
|
||||
log "[CeruMusic] 发起请求: https://www.lihouse.xyz/coco_widget/music_resource/id/1135545898"
|
||||
log "[CeruMusic] 发起请求: https://www.lihouse.xyz/coco_widget/music_resource/id/2149456401"
|
||||
log "[LiHouse 音乐插件] 请求音乐链接: https://www.lihouse.xyz/coco_widget/music_resource/id/2149456401"
|
||||
log "[CeruMusic] 发起请求: https://www.lihouse.xyz/coco_widget/music_resource/id/2003594562"
|
||||
log "[LiHouse 音乐插件] 请求音乐链接: https://www.lihouse.xyz/coco_widget/music_resource/id/2003594562"
|
||||
log "[CeruMusic] 请求响应状态: 200"
|
||||
log "[CeruMusic] 响应不是JSON格式,内容: ..."
|
||||
log "[CeruMusic] 请求响应内容: [object Object]"
|
||||
error "[LiHouse 音乐插件] 请求失败: 歌曲不存在"
|
||||
error "[LiHouse 音乐插件] Error: 歌曲不存在"
|
||||
log "[CeruMusic] 请求响应状态: 200"
|
||||
log "[CeruMusic] 响应不是JSON格式,内容: {\"status\":true,\"song_data\":{\"id\":2149456401,\"name\":\"alone.\",\"artist\":\"DLSS\",\"album\":\"alone.\",\"pic_id\":\"109951169530908047\",\"url_id\":2149456401,\"lyric_id\":2149456401,\"source\":\"netease\",\"lyric\":\"[00:00...."
|
||||
error "[LiHouse 音乐插件] Error: 歌曲不存在"
|
||||
error "[LiHouse 音乐插件] 请求失败: 歌曲不存在"
|
||||
log "[CeruMusic] 请求响应内容: [object Object]"
|
||||
log "[CeruMusic] 请求响应状态: 200"
|
||||
log "[CeruMusic] 请求响应内容: [object Object]"
|
||||
error "[LiHouse 音乐插件] 请求失败: 歌曲不存在"
|
||||
log "[CeruMusic] 响应不是JSON格式,内容: {\"status\":true,\"song_data\":{\"id\":2003594562,\"name\":\"天晴\",\"artist\":\"是二智呀\",\"album\":\"天晴\",\"pic_id\":\"109951168114308737\",\"url_id\":2003594562,\"lyric_id\":2003594562,\"source\":\"netease\",\"lyric\":\"[00:00.06]作词:陈澈..."
|
||||
error "[LiHouse 音乐插件] Error: 歌曲不存在"
|
||||
log "[CeruMusic] 发起请求: https://www.lihouse.xyz/coco_widget/music_resource/id/2149456401"
|
||||
log "[LiHouse 音乐插件] 请求音乐链接: https://www.lihouse.xyz/coco_widget/music_resource/id/2149456401"
|
||||
log "[CeruMusic] 请求响应状态: 200"
|
||||
log "[CeruMusic] 响应不是JSON格式,内容: {\"status\":true,\"song_data\":{\"id\":2149456401,\"name\":\"alone.\",\"artist\":\"DLSS\",\"album\":\"alone.\",\"pic_id\":\"109951169530908047\",\"url_id\":2149456401,\"lyric_id\":2149456401,\"source\":\"netease\",\"lyric\":\"[00:00...."
|
||||
log "[CeruMusic] 请求响应内容: [object Object]"
|
||||
error "[LiHouse 音乐插件] Error: 歌曲不存在"
|
||||
error "[LiHouse 音乐插件] 请求失败: 歌曲不存在"
|
||||
@@ -1,200 +0,0 @@
|
||||
end
|
||||
log handleGetMusicUrl(kg_178240, hires) failed: Key失效/鉴权失败
|
||||
log [ikun音源] 使用事件驱动方式获取 kw 音源链接
|
||||
start Handle Action(musicUrl)
|
||||
log musicInfo {"name":"后来","singer":"刘若英","source":"kw","songmid":"96765035","albumId":"13825074","interval":"05:09","albumName":"2020 刘若英陪你 献上录音专辑","lrc":null,"img":"http://img1.kwcdn.kuwo.cn/star/albumcover/500/s4s68/34/3776980601.jpg","otherSource":null,"types":[{"type":"128k","size":"4.72Mb"},{"type":"320k","size":"11.80Mb"},{"type":"flac","size":"60.23Mb"},{"type":"flac24bit","size":"60.23Mb"}],"_types":{"flac24bit":{"size":"60.23MB"},"flac":{"size":"60.23MB"},"320k":{"size":"11.80MB"},"128k":{"size":"4.72MB"}},"typeUrl":{},"url":"http://cu.sycdn.kuwo.cn/226d5ab974a1d2ca5b5643e2354e7696/68a73db3/resource/s2/87/81/730236710.flac"}
|
||||
log [CeruMusic] 发起请求: https://api.ikunshare.com/url?source=kw&songId=96765035&quality=hires
|
||||
log quality hires
|
||||
log source kw
|
||||
log --- start --- https://api.ikunshare.com/url?source=kw&songId=96765035&quality=hires
|
||||
log [CeruMusic] 请求响应状态: 200
|
||||
log API Response: {"body":{"code":200,"message":"成功","url":"http://cu.sycdn.kuwo.cn/f9678d8cdc643bcd8ff61bc61099540d/68a7e45b/resource/s2/87/81/730236710.flac","info":{"id":"96765035","name":"后来","album":"2020 刘若英陪你 献上录音专辑","artist":"刘若英"},"ekey":null,"cache":false,"quality":{"target":"无损音质 24Bit","result":"无损音质 24Bit"},"expire":{"ExpireAt":"2025-08-22 12:15:35","canExpire":true},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}},"statusCode":200,"headers":{"server":["openresty"],"date":["Fri, 22 Aug 2025 03:30:35 GMT"],"content-type":["application/json; charset=utf-8"],"content-length":["583"],"connection":["keep-alive"],"access-control-allow-origin":["*"],"x-process-time":["0.12571569811552763"],"cache-control":["no-cache"],"alt-svc":["h3=\":443\"; ma=2592000"]}}
|
||||
log [CeruMusic] 请求响应内容: {"code":200,"message":"成功","url":"http://cu.sycdn.kuwo.cn/f9678d8cdc643bcd8ff61bc61099540d/68a7e45b/resource/s2/87/81/730236710.flac","info":{"id":"96765035","name":"后来","album":"2020 刘若英陪你 献上录音专辑","artist":"刘若英"},"ekey":null,"cache":false,"quality":{"target":"无损音质 24Bit","result":"无损音质 24Bit"},"expire":{"ExpireAt":"2025-08-22 12:15:35","canExpire":true},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}}
|
||||
end
|
||||
log handleGetMusicUrl(kw_96765035, hires) success, URL: http://cu.sycdn.kuwo.cn/f9678d8cdc643bcd8ff61bc61099540d/68a7e45b/resource/s2/87/81/730236710.flac
|
||||
log [ikun音源] Got URL: http://cu.sycdn.kuwo.cn/f9678d8cdc643bcd8ff61bc61099540d/68a7e45b/resource/s2/87/81/730236710.flaclog 提取到的 MUSIC_QUALITY 字符串: {"kw":["128k","320k","flac","flac24bit","hires"],"wy":["128k","320k","flac","flac24bit","hires","atmos","master"],"mg":["128k","320k","flac","flac24bit","hires"]}
|
||||
log 解析后的 MUSIC_QUALITY 数据: {"kw":["128k","320k","flac","flac24bit","hires"],"wy":["128k","320k","flac","flac24bit","hires","atmos","master"],"mg":["128k","320k","flac","flac24bit","hires"]}
|
||||
log 提取的音源配置: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]}}
|
||||
log [CeruMusic] 发起请求: https://api.ikunshare.com/script?key=&checkUpdate=6c72d109ed7db5cf037b3bbf1dfccb53
|
||||
log [ikun音源 by Ceru插件] 发送事件: inited {"status":true,"openDevTools":false,"sources":{"kw":{"name":"kw","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"wy","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"mg","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires"]}}}
|
||||
log [ikun音源 by Ceru插件] 音源注册完成: ["kw","wy","mg"]
|
||||
log [ikun音源 by Ceru插件] 注册事件监听器: request
|
||||
log [ikun音源 by Ceru插件] 动态音源信息已更新: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]}}
|
||||
log [CeruMusic] Plugin "ikun音源" loaded successfully.
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log --- start --- https://api.ikunshare.com/script?key=&checkUpdate=6c72d109ed7db5cf037b3bbf1dfccb53
|
||||
log [CeruMusic] 请求响应状态: 200
|
||||
log [CeruMusic] 请求响应内容: {"code":200,"message":"成功","data":{"updateMsg":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}}
|
||||
log API Response: {"body":{"code":200,"message":"成功","data":{"updateMsg":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}},"statusCode":200,"headers":{"server":["openresty"],"date":["Fri, 22 Aug 2025 03:32:16 GMT"],"content-type":["application/json; charset=utf-8"],"content-length":["370"],"connection":["keep-alive"],"access-control-allow-origin":["*"],"x-process-time":["0.0007874071598052979"],"cache-control":["no-cache"],"alt-svc":["h3=\":443\"; ma=2592000"]}}
|
||||
log checkUpdate success
|
||||
log [ikun音源 by Ceru插件] 发送事件: updateAlert {"log":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"}
|
||||
log [ikun音源] 使用事件驱动方式获取 kw 音源链接
|
||||
start Handle Action(musicUrl)
|
||||
log quality hires
|
||||
log musicInfo {"name":"后来","singer":"刘若英","source":"kw","songmid":"96765035","albumId":"13825074","interval":"05:09","albumName":"2020 刘若英陪你 献上录音专辑","lrc":null,"img":"http://img1.kwcdn.kuwo.cn/star/albumcover/500/s4s68/34/3776980601.jpg","otherSource":null,"types":[{"type":"128k","size":"4.72Mb"},{"type":"320k","size":"11.80Mb"},{"type":"flac","size":"60.23Mb"},{"type":"flac24bit","size":"60.23Mb"}],"_types":{"flac24bit":{"size":"60.23MB"},"flac":{"size":"60.23MB"},"320k":{"size":"11.80MB"},"128k":{"size":"4.72MB"}},"typeUrl":{},"url":"http://cu.sycdn.kuwo.cn/f9678d8cdc643bcd8ff61bc61099540d/68a7e45b/resource/s2/87/81/730236710.flac"}
|
||||
log [CeruMusic] 发起请求: https://api.ikunshare.com/url?source=kw&songId=96765035&quality=hires
|
||||
log source kw
|
||||
log --- start --- https://api.ikunshare.com/url?source=kw&songId=96765035&quality=hires
|
||||
log [CeruMusic] 请求响应状态: 200
|
||||
log [CeruMusic] 请求响应内容: {"code":200,"message":"成功","url":"http://cu.sycdn.kuwo.cn/f9678d8cdc643bcd8ff61bc61099540d/68a7e45b/resource/s2/87/81/730236710.flac","info":{"id":"96765035","name":"后来","album":"2020 刘若英陪你 献上录音专辑","artist":"刘若英"},"ekey":null,"cache":true,"quality":{"target":"无损音质 24Bit","result":"无损音质 24Bit"},"expire":{"ExpireAt":"2025-08-22 12:15:35","canExpire":true},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}}
|
||||
log API Response: {"body":{"code":200,"message":"成功","url":"http://cu.sycdn.kuwo.cn/f9678d8cdc643bcd8ff61bc61099540d/68a7e45b/resource/s2/87/81/730236710.flac","info":{"id":"96765035","name":"后来","album":"2020 刘若英陪你 献上录音专辑","artist":"刘若英"},"ekey":null,"cache":true,"quality":{"target":"无损音质 24Bit","result":"无损音质 24Bit"},"expire":{"ExpireAt":"2025-08-22 12:15:35","canExpire":true},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}},"statusCode":200,"headers":{"server":["openresty"],"date":["Fri, 22 Aug 2025 03:32:17 GMT"],"content-type":["application/json; charset=utf-8"],"content-length":["582"],"connection":["keep-alive"],"access-control-allow-origin":["*"],"x-process-time":["0.0030085640028119087"],"cache-control":["no-cache"],"alt-svc":["h3=\":443\"; ma=2592000"]}}
|
||||
end
|
||||
log handleGetMusicUrl(kw_96765035, hires) success, URL: http://cu.sycdn.kuwo.cn/f9678d8cdc643bcd8ff61bc61099540d/68a7e45b/resource/s2/87/81/730236710.flac
|
||||
log [ikun音源] Got URL: http://cu.sycdn.kuwo.cn/f9678d8cdc643bcd8ff61bc61099540d/68a7e45b/resource/s2/87/81/730236710.flac
|
||||
log 提取到的 MUSIC_QUALITY 字符串: {"kw":["128k","320k","flac","flac24bit","hires"],"wy":["128k","320k","flac","flac24bit","hires","atmos","master"],"mg":["128k","320k","flac","flac24bit","hires"]}
|
||||
log 解析后的 MUSIC_QUALITY 数据: {"kw":["128k","320k","flac","flac24bit","hires"],"wy":["128k","320k","flac","flac24bit","hires","atmos","master"],"mg":["128k","320k","flac","flac24bit","hires"]}
|
||||
log [ikun音源 by Ceru插件] 注册事件监听器: request
|
||||
log 提取的音源配置: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]}}
|
||||
log --- start --- https://api.ikunshare.com/script?key=&checkUpdate=6c72d109ed7db5cf037b3bbf1dfccb53
|
||||
log [ikun音源 by Ceru插件] 发送事件: inited {"status":true,"openDevTools":false,"sources":{"kw":{"name":"kw","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"wy","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"mg","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires"]}}}
|
||||
log [ikun音源 by Ceru插件] 动态音源信息已更新: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]}}
|
||||
log [CeruMusic] 发起请求: https://api.ikunshare.com/script?key=&checkUpdate=6c72d109ed7db5cf037b3bbf1dfccb53
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [ikun音源 by Ceru插件] 音源注册完成: ["kw","wy","mg"]
|
||||
log [CeruMusic] Plugin "ikun音源" loaded successfully.
|
||||
log [CeruMusic] 请求响应状态: 200
|
||||
log [CeruMusic] 请求响应内容: {"code":200,"message":"成功","data":{"updateMsg":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}}
|
||||
log API Response: {"body":{"code":200,"message":"成功","data":{"updateMsg":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}},"statusCode":200,"headers":{"server":["openresty"],"date":["Fri, 22 Aug 2025 03:32:58 GMT"],"content-type":["application/json; charset=utf-8"],"content-length":["370"],"connection":["keep-alive"],"access-control-allow-origin":["*"],"x-process-time":["0.0008850144222378731"],"cache-control":["no-cache"],"alt-svc":["h3=\":443\"; ma=2592000"]}}
|
||||
log checkUpdate success
|
||||
log [ikun音源 by Ceru插件] 发送事件: updateAlert {"log":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"}
|
||||
log [ikun音源] 使用事件驱动方式获取 kw 音源链接
|
||||
error [ikun音源] Error: args.map is not a function
|
||||
log 提取到的 MUSIC_QUALITY 字符串: {"kw":["128k","320k","flac","flac24bit","hires"],"wy":["128k","320k","flac","flac24bit","hires","atmos","master"],"mg":["128k","320k","flac","flac24bit","hires"]}
|
||||
log 解析后的 MUSIC_QUALITY 数据: {"kw":["128k","320k","flac","flac24bit","hires"],"wy":["128k","320k","flac","flac24bit","hires","atmos","master"],"mg":["128k","320k","flac","flac24bit","hires"]}
|
||||
log 提取的音源配置: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]}}
|
||||
log [ikun音源 by Ceru插件] 注册事件监听器: request
|
||||
log --- start --- https://api.ikunshare.com/script?key=&checkUpdate=6c72d109ed7db5cf037b3bbf1dfccb53
|
||||
log [CeruMusic] 发起请求: https://api.ikunshare.com/script?key=&checkUpdate=6c72d109ed7db5cf037b3bbf1dfccb53
|
||||
log [ikun音源 by Ceru插件] 发送事件: inited {"status":true,"openDevTools":false,"sources":{"kw":{"name":"kw","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"wy","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"mg","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires"]}}}
|
||||
log [ikun音源 by Ceru插件] 音源注册完成: ["kw","wy","mg"]
|
||||
log [ikun音源 by Ceru插件] 动态音源信息已更新: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]}}
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [CeruMusic] Plugin "ikun音源" loaded successfully.
|
||||
log [CeruMusic] 请求响应状态: 200
|
||||
log [CeruMusic] 请求响应内容: {"code":200,"message":"成功","data":{"updateMsg":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}}
|
||||
log API Response: {"body":{"code":200,"message":"成功","data":{"updateMsg":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}},"statusCode":200,"headers":{"server":["openresty"],"date":["Fri, 22 Aug 2025 03:33:02 GMT"],"content-type":["application/json; charset=utf-8"],"content-length":["370"],"connection":["keep-alive"],"access-control-allow-origin":["*"],"x-process-time":["0.0008127372711896896"],"cache-control":["no-cache"],"alt-svc":["h3=\":443\"; ma=2592000"]}}
|
||||
log checkUpdate success
|
||||
log [ikun音源 by Ceru插件] 发送事件: updateAlert {"log":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"}
|
||||
log [ikun音源] 使用事件驱动方式获取 kw 音源链接
|
||||
error [ikun音源] Error: args.map is not a function
|
||||
log 提取到的 MUSIC_QUALITY 字符串: {"kw":["128k","320k","flac","flac24bit","hires"],"wy":["128k","320k","flac","flac24bit","hires","atmos","master"],"mg":["128k","320k","flac","flac24bit","hires"]}
|
||||
log 解析后的 MUSIC_QUALITY 数据: {"kw":["128k","320k","flac","flac24bit","hires"],"wy":["128k","320k","flac","flac24bit","hires","atmos","master"],"mg":["128k","320k","flac","flac24bit","hires"]}
|
||||
log 提取的音源配置: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]}}
|
||||
log [CeruMusic] 发起请求: https://api.ikunshare.com/script?key=&checkUpdate=6c72d109ed7db5cf037b3bbf1dfccb53
|
||||
log [ikun音源 by Ceru插件] 发送事件: inited {"status":true,"openDevTools":false,"sources":{"kw":{"name":"kw","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"wy","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"mg","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires"]}}}
|
||||
log [ikun音源 by Ceru插件] 音源注册完成: ["kw","wy","mg"]
|
||||
log [ikun音源 by Ceru插件] 注册事件监听器: request
|
||||
log --- start --- https://api.ikunshare.com/script?key=&checkUpdate=6c72d109ed7db5cf037b3bbf1dfccb53
|
||||
log [ikun音源 by Ceru插件] 动态音源信息已更新: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]}}
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [CeruMusic] Plugin "ikun音源" loaded successfully.
|
||||
log [CeruMusic] 请求响应状态: 200
|
||||
log [CeruMusic] 请求响应内容: {"code":200,"message":"成功","data":{"updateMsg":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}}
|
||||
log API Response: {"body":{"code":200,"message":"成功","data":{"updateMsg":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}},"statusCode":200,"headers":{"server":["openresty"],"date":["Fri, 22 Aug 2025 03:33:30 GMT"],"content-type":["application/json; charset=utf-8"],"content-length":["370"],"connection":["keep-alive"],"access-control-allow-origin":["*"],"x-process-time":["0.0008348245173692703"],"cache-control":["no-cache"],"alt-svc":["h3=\":443\"; ma=2592000"]}}
|
||||
log [ikun音源 by Ceru插件] 发送事件: updateAlert {"log":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"}
|
||||
log checkUpdate success
|
||||
log [ikun音源] 使用事件驱动方式获取 kw 音源链接
|
||||
error [ikun音源] Error: args.map is not a function
|
||||
log 提取到的 MUSIC_QUALITY 字符串: {"kw":["128k","320k","flac","flac24bit","hires"],"wy":["128k","320k","flac","flac24bit","hires","atmos","master"],"mg":["128k","320k","flac","flac24bit","hires"]}
|
||||
log 解析后的 MUSIC_QUALITY 数据: {"kw":["128k","320k","flac","flac24bit","hires"],"wy":["128k","320k","flac","flac24bit","hires","atmos","master"],"mg":["128k","320k","flac","flac24bit","hires"]}
|
||||
log 提取的音源配置: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]}}
|
||||
log [ikun音源 by Ceru插件] 注册事件监听器: request
|
||||
log --- start --- https://api.ikunshare.com/script?key=&checkUpdate=6c72d109ed7db5cf037b3bbf1dfccb53
|
||||
log [CeruMusic] 发起请求: https://api.ikunshare.com/script?key=&checkUpdate=6c72d109ed7db5cf037b3bbf1dfccb53
|
||||
log [ikun音源 by Ceru插件] 发送事件: inited {"status":true,"openDevTools":false,"sources":{"kw":{"name":"kw","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"wy","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"mg","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires"]}}}
|
||||
log [ikun音源 by Ceru插件] 音源注册完成: ["kw","wy","mg"]
|
||||
log [ikun音源 by Ceru插件] 动态音源信息已更新: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]}}
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [CeruMusic] Plugin "ikun音源" loaded successfully.
|
||||
log [CeruMusic] 请求响应状态: 200
|
||||
log API Response: {"body":{"code":200,"message":"成功","data":{"updateMsg":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}},"statusCode":200,"headers":{"server":["openresty"],"date":["Fri, 22 Aug 2025 03:34:27 GMT"],"content-type":["application/json; charset=utf-8"],"content-length":["370"],"connection":["keep-alive"],"access-control-allow-origin":["*"],"x-process-time":["0.0008898386731743813"],"cache-control":["no-cache"],"alt-svc":["h3=\":443\"; ma=2592000"]}}
|
||||
log checkUpdate success
|
||||
log [CeruMusic] 请求响应内容: {"code":200,"message":"成功","data":{"updateMsg":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}}
|
||||
log [ikun音源 by Ceru插件] 发送事件: updateAlert {"log":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"}
|
||||
log [ikun音源] 使用事件驱动方式获取 kw 音源链接
|
||||
error [ikun音源] Error: args.map is not a functionlog [ikun音源] 使用事件驱动方式获取 kw 音源链接
|
||||
error [ikun音源] Error: args.map is not a function
|
||||
error [ikun音源] Error: args.map is not a function
|
||||
log [ikun音源] 使用事件驱动方式获取 kg 音源链接
|
||||
log [ikun音源] 使用事件驱动方式获取 kg 音源链接
|
||||
error [ikun音源] Error: args.map is not a function
|
||||
log 提取到的 MUSIC_QUALITY 字符串: {"kw":["128k","320k","flac","flac24bit","hires"],"wy":["128k","320k","flac","flac24bit","hires","atmos","master"],"mg":["128k","320k","flac","flac24bit","hires"]}
|
||||
log 解析后的 MUSIC_QUALITY 数据: {"kw":["128k","320k","flac","flac24bit","hires"],"wy":["128k","320k","flac","flac24bit","hires","atmos","master"],"mg":["128k","320k","flac","flac24bit","hires"]}
|
||||
log 提取的音源配置: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]}}
|
||||
log [CeruMusic] 发起请求: https://api.ikunshare.com/script?key=&checkUpdate=6c72d109ed7db5cf037b3bbf1dfccb53
|
||||
log [ikun音源 by Ceru插件] 注册事件监听器: request
|
||||
log [ikun音源 by Ceru插件] 发送事件: inited {"status":true,"openDevTools":false,"sources":{"kw":{"name":"kw","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"wy","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"mg","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires"]}}}
|
||||
log --- start --- https://api.ikunshare.com/script?key=&checkUpdate=6c72d109ed7db5cf037b3bbf1dfccb53
|
||||
log [ikun音源 by Ceru插件] 音源注册完成: ["kw","wy","mg"]
|
||||
log [ikun音源 by Ceru插件] 动态音源信息已更新: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]}}
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [CeruMusic] Plugin "ikun音源" loaded successfully.
|
||||
log [CeruMusic] 请求响应状态: 200
|
||||
log [CeruMusic] 请求响应内容: {"code":200,"message":"成功","data":{"updateMsg":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}}
|
||||
log API Response: {"body":{"code":200,"message":"成功","data":{"updateMsg":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}},"statusCode":200,"headers":{"server":["openresty"],"date":["Fri, 22 Aug 2025 03:35:51 GMT"],"content-type":["application/json; charset=utf-8"],"content-length":["370"],"connection":["keep-alive"],"access-control-allow-origin":["*"],"x-process-time":["0.0008251480758190155"],"cache-control":["no-cache"],"alt-svc":["h3=\":443\"; ma=2592000"]}}
|
||||
log [ikun音源 by Ceru插件] 发送事件: updateAlert {"log":"⚠️ 重要更新\n更新地址:\nhttps://api.ikunshare.com/script\n日志:\n更换域名\nQQ群: https://qm.qq.com/q/okMS3ubs0E","updateUrl":"https://api.ikunshare.com/script"}
|
||||
log checkUpdate success
|
||||
log [ikun音源] 使用事件驱动方式获取 kg 音源链接
|
||||
log quality hires
|
||||
start groupStart--------- Handle Action(musicUrl)
|
||||
log musicInfo {"singer":"周杰伦","name":"半岛铁盒","albumName":"八度空间","albumId":"961807","songmid":178240,"source":"kg","interval":"05:19","_interval":319,"img":"http://imge.kugou.com/stdmusic/480/20250221/20250221180703846658.jpg","lrc":null,"otherSource":null,"hash":"67BA8A4A0681F2078BC423CB13B904B7","types":[{"type":"128k","size":"4.88 MB","hash":"67BA8A4A0681F2078BC423CB13B904B7"},{"type":"320k","size":"12.19 MB","hash":"68DEA6329FB28534271943B3F97599DC"},{"type":"flac","size":"34.93 MB","hash":"9F0F140F8A9C3A0E639A9BF8680E80E9"},{"type":"flac24bit","size":"61.72 MB","hash":"2CCC7683F50BE90A0C407D6FF0F973D9"}],"_types":{"128k":{"size":"4.88 MB","hash":"67BA8A4A0681F2078BC423CB13B904B7"},"320k":{"size":"12.19 MB","hash":"68DEA6329FB28534271943B3F97599DC"},"flac":{"size":"34.93 MB","hash":"9F0F140F8A9C3A0E639A9BF8680E80E9"},"flac24bit":{"size":"61.72 MB","hash":"2CCC7683F50BE90A0C407D6FF0F973D9"}},"typeUrl":{}}
|
||||
log [CeruMusic] 发起请求: https://api.ikunshare.com/url?source=kg&songId=67BA8A4A0681F2078BC423CB13B904B7&quality=hires
|
||||
log --- start --- https://api.ikunshare.com/url?source=kg&songId=67BA8A4A0681F2078BC423CB13B904B7&quality=hires
|
||||
log source kg
|
||||
log [CeruMusic] 请求响应状态: 403
|
||||
log API Response: {"body":{"code":403,"message":"你还没给我上供","yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}},"statusCode":403,"headers":{"server":["openresty"],"date":["Fri, 22 Aug 2025 03:35:51 GMT"],"content-type":["application/json; charset=utf-8"],"content-length":["138"],"connection":["keep-alive"],"access-control-allow-origin":["*"],"x-process-time":["0.00015995558351278305"]}}
|
||||
log [CeruMusic] 请求响应内容: {"code":403,"message":"你还没给我上供","yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}}
|
||||
log handleGetMusicUrl(kg_178240, hires) failed: Key失效/鉴权失败
|
||||
error [ikun音源] Error: Key失效/鉴权失败
|
||||
end log [ikun音源] 使用事件驱动方式获取 kg 音源链接
|
||||
start groupStart--------- Handle Action(musicUrl)
|
||||
log source kg
|
||||
log musicInfo {"singer":"周杰伦","name":"半岛铁盒","albumName":"八度空间","albumId":"961807","songmid":178240,"source":"kg","interval":"05:19","_interval":319,"img":"http://imge.kugou.com/stdmusic/480/20250221/20250221180703846658.jpg","lrc":null,"otherSource":null,"hash":"67BA8A4A0681F2078BC423CB13B904B7","types":[{"type":"128k","size":"4.88 MB","hash":"67BA8A4A0681F2078BC423CB13B904B7"},{"type":"320k","size":"12.19 MB","hash":"68DEA6329FB28534271943B3F97599DC"},{"type":"flac","size":"34.93 MB","hash":"9F0F140F8A9C3A0E639A9BF8680E80E9"},{"type":"flac24bit","size":"61.72 MB","hash":"2CCC7683F50BE90A0C407D6FF0F973D9"}],"_types":{"128k":{"size":"4.88 MB","hash":"67BA8A4A0681F2078BC423CB13B904B7"},"320k":{"size":"12.19 MB","hash":"68DEA6329FB28534271943B3F97599DC"},"flac":{"size":"34.93 MB","hash":"9F0F140F8A9C3A0E639A9BF8680E80E9"},"flac24bit":{"size":"61.72 MB","hash":"2CCC7683F50BE90A0C407D6FF0F973D9"}},"typeUrl":{}}
|
||||
log --- start --- https://api.ikunshare.com/url?source=kg&songId=67BA8A4A0681F2078BC423CB13B904B7&quality=hires
|
||||
log [CeruMusic] 发起请求: https://api.ikunshare.com/url?source=kg&songId=67BA8A4A0681F2078BC423CB13B904B7&quality=hires
|
||||
log quality hires
|
||||
log [CeruMusic] 请求响应状态: 403
|
||||
log API Response: {"body":{"code":403,"message":"你还没给我上供","yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}},"statusCode":403,"headers":{"server":["openresty"],"date":["Fri, 22 Aug 2025 03:36:55 GMT"],"content-type":["application/json; charset=utf-8"],"content-length":["138"],"connection":["keep-alive"],"access-control-allow-origin":["*"],"x-process-time":["8.029583841562271e-05"]}}
|
||||
log handleGetMusicUrl(kg_178240, hires) failed: Key失效/鉴权失败
|
||||
error [ikun音源] Error: Key失效/鉴权失败
|
||||
end
|
||||
log [CeruMusic] 请求响应内容: {"code":403,"message":"你还没给我上供","yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}}
|
||||
log [ikun音源] 使用事件驱动方式获取 kw 音源链接
|
||||
log source kw
|
||||
log musicInfo {"name":"后来","singer":"刘若英","source":"kw","songmid":"96765035","albumId":"13825074","interval":"05:09","albumName":"2020 刘若英陪你 献上录音专辑","lrc":null,"img":"http://img1.kwcdn.kuwo.cn/star/albumcover/500/s4s68/34/3776980601.jpg","otherSource":null,"types":[{"type":"128k","size":"4.72Mb"},{"type":"320k","size":"11.80Mb"},{"type":"flac","size":"60.23Mb"},{"type":"flac24bit","size":"60.23Mb"}],"_types":{"flac24bit":{"size":"60.23MB"},"flac":{"size":"60.23MB"},"320k":{"size":"11.80MB"},"128k":{"size":"4.72MB"}},"typeUrl":{},"url":"http://cu.sycdn.kuwo.cn/f9678d8cdc643bcd8ff61bc61099540d/68a7e45b/resource/s2/87/81/730236710.flac"}
|
||||
log --- start --- https://api.ikunshare.com/url?source=kw&songId=96765035&quality=hires
|
||||
log [CeruMusic] 发起请求: https://api.ikunshare.com/url?source=kw&songId=96765035&quality=hires
|
||||
log quality hires
|
||||
start groupStart--------- Handle Action(musicUrl)
|
||||
log [CeruMusic] 请求响应状态: 200
|
||||
log [CeruMusic] 请求响应内容: {"code":200,"message":"成功","url":"http://cu.sycdn.kuwo.cn/f9678d8cdc643bcd8ff61bc61099540d/68a7e45b/resource/s2/87/81/730236710.flac","info":{"id":"96765035","name":"后来","album":"2020 刘若英陪你 献上录音专辑","artist":"刘若英"},"ekey":null,"cache":true,"quality":{"target":"无损音质 24Bit","result":"无损音质 24Bit"},"expire":{"ExpireAt":"2025-08-22 12:15:35","canExpire":true},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}}
|
||||
log API Response: {"body":{"code":200,"message":"成功","url":"http://cu.sycdn.kuwo.cn/f9678d8cdc643bcd8ff61bc61099540d/68a7e45b/resource/s2/87/81/730236710.flac","info":{"id":"96765035","name":"后来","album":"2020 刘若英陪你 献上录音专辑","artist":"刘若英"},"ekey":null,"cache":true,"quality":{"target":"无损音质 24Bit","result":"无损音质 24Bit"},"expire":{"ExpireAt":"2025-08-22 12:15:35","canExpire":true},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}},"statusCode":200,"headers":{"server":["openresty"],"date":["Fri, 22 Aug 2025 03:36:56 GMT"],"content-type":["application/json; charset=utf-8"],"content-length":["582"],"connection":["keep-alive"],"access-control-allow-origin":["*"],"x-process-time":["0.002598297782242298"],"cache-control":["no-cache"],"alt-svc":["h3=\":443\"; ma=2592000"]}}
|
||||
log handleGetMusicUrl(kw_96765035, hires) success, URL: http://cu.sycdn.kuwo.cn/f9678d8cdc643bcd8ff61bc61099540d/68a7e45b/resource/s2/87/81/730236710.flac
|
||||
end
|
||||
log [ikun音源] Got URL: http://cu.sycdn.kuwo.cn/f9678d8cdc643bcd8ff61bc61099540d/68a7e45b/resource/s2/87/81/730236710.flac
|
||||
log [ikun音源] 使用事件驱动方式获取 mg 音源链接
|
||||
log source mg
|
||||
start groupStart--------- Handle Action(musicUrl)
|
||||
log musicInfo {"singer":"音阙诗听","name":"红昭愿 (伴奏)","albumName":"诗","albumId":"1140686463","songmid":"1135545898","copyrightId":"69069801208","source":"mg","interval":"02:53","img":"http://d.musicapp.migu.cn/data/oss/resource/00/4g/u6/5fa3dbc0f6584fa88ba2c073f52d7330.webp","lrc":null,"lrcUrl":"https://d.musicapp.migu.cn/data/oss/resource/00/4s/oi/6972a559ab2d4162b649ac12646c9678","types":[{"type":"128k","size":"2.62 MB"},{"type":"320k","size":"6.56 MB"}],"_types":{"128k":{"size":"2.62 MB"},"320k":{"size":"6.56 MB"}},"typeUrl":{},"url":"http://freetyst.nf.migu.cn/public/product9th/product47/2025/03/1216/2025%E5%B9%B401%E6%9C%8820%E6%97%A514%E7%82%B940%E5%88%86%E7%B4%A7%E6%80%A5%E5%86%85%E5%AE%B9%E5%87%86%E5%85%A5%E6%88%90%E9%83%BD%E8%B0%A6%E4%BF%AE%E6%88%90%E6%9C%AC502%E9%A6%96015401/%E6%A0%87%E6%B8%85%E9%AB%98%E6%B8%85/MP3_128_16_Stero/69069801208160124.mp3"}
|
||||
log quality hires
|
||||
log [CeruMusic] 发起请求: https://api.ikunshare.com/url?source=mg&songId=1135545898&quality=hires
|
||||
log --- start --- https://api.ikunshare.com/url?source=mg&songId=1135545898&quality=hires
|
||||
log [CeruMusic] 请求响应状态: 200
|
||||
log [CeruMusic] 请求响应内容: {"code":200,"message":"成功","url":"http://freetyst.nf.migu.cn/public/product9th/product47/2025/03/1216/2025%E5%B9%B401%E6%9C%8820%E6%97%A514%E7%82%B940%E5%88%86%E7%B4%A7%E6%80%A5%E5%86%85%E5%AE%B9%E5%87%86%E5%85%A5%E6%88%90%E9%83%BD%E8%B0%A6%E4%BF%AE%E6%88%90%E6%9C%AC502%E9%A6%96015401/%E6%A0%87%E6%B8%85%E9%AB%98%E6%B8%85/MP3_128_16_Stero/69069801208160124.mp3","info":{"id":"1135545898","name":"红昭愿 (伴奏)","album":"诗","artist":"音阙诗听"},"ekey":"","cache":true,"quality":{"target":"无损音质 24Bit","result":"无损音质 24Bit"},"expire":{"ExpireAt":null,"canExpire":false},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}}
|
||||
log API Response: {"body":{"code":200,"message":"成功","url":"http://freetyst.nf.migu.cn/public/product9th/product47/2025/03/1216/2025%E5%B9%B401%E6%9C%8820%E6%97%A514%E7%82%B940%E5%88%86%E7%B4%A7%E6%80%A5%E5%86%85%E5%AE%B9%E5%87%86%E5%85%A5%E6%88%90%E9%83%BD%E8%B0%A6%E4%BF%AE%E6%88%90%E6%9C%AC502%E9%A6%96015401/%E6%A0%87%E6%B8%85%E9%AB%98%E6%B8%85/MP3_128_16_Stero/69069801208160124.mp3","info":{"id":"1135545898","name":"红昭愿 (伴奏)","album":"诗","artist":"音阙诗听"},"ekey":"","cache":true,"quality":{"target":"无损音质 24Bit","result":"无损音质 24Bit"},"expire":{"ExpireAt":null,"canExpire":false},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}},"statusCode":200,"headers":{"server":["openresty"],"date":["Fri, 22 Aug 2025 03:37:05 GMT"],"content-type":["application/json; charset=utf-8"],"content-length":["761"],"connection":["keep-alive"],"access-control-allow-origin":["*"],"x-process-time":["0.004381708800792694"],"cache-control":["no-cache"],"alt-svc":["h3=\":443\"; ma=2592000"]}}
|
||||
end
|
||||
log [ikun音源] Got URL: http://freetyst.nf.migu.cn/public/product9th/product47/2025/03/1216/2025%E5%B9%B401%E6%9C%8820%E6%97%A514%E7%82%B940%E5%88%86%E7%B4%A7%E6%80%A5%E5%86%85%E5%AE%B9%E5%87%86%E5%85%A5%E6%88%90%E9%83%BD%E8%B0%A6%E4%BF%AE%E6%88%90%E6%9C%AC502%E9%A6%96015401/%E6%A0%87%E6%B8%85%E9%AB%98%E6%B8%85/MP3_128_16_Stero/69069801208160124.mp3
|
||||
log handleGetMusicUrl(mg_1135545898, hires) success, URL: http://freetyst.nf.migu.cn/public/product9th/product47/2025/03/1216/2025%E5%B9%B401%E6%9C%8820%E6%97%A514%E7%82%B940%E5%88%86%E7%B4%A7%E6%80%A5%E5%86%85%E5%AE%B9%E5%87%86%E5%85%A5%E6%88%90%E9%83%BD%E8%B0%A6%E4%BF%AE%E6%88%90%E6%9C%AC502%E9%A6%96015401/%E6%A0%87%E6%B8%85%E9%AB%98%E6%B8%85/MP3_128_16_Stero/69069801208160124.mp3
|
||||
log [ikun音源] 使用事件驱动方式获取 wy 音源链接
|
||||
start groupStart--------- Handle Action(musicUrl)
|
||||
log musicInfo {"singer":"DLSS","name":"alone.","albumName":"alone.","albumId":193463408,"source":"wy","interval":"02:25","songmid":2149456401,"img":"http://p2.music.126.net/I-pKOLxJENNTkZp1nbY2VA==/109951169530908047.jpg","lrc":null,"types":[{"type":"128k","size":"2.21 MB"},{"type":"320k","size":"5.54 MB"},{"type":"flac","size":"16.91 MB"},{"type":"flac24bit","size":"30.14 MB"}],"_types":{"flac24bit":{"size":"30.14 MB"},"flac":{"size":"16.91 MB"},"320k":{"size":"5.54 MB"},"128k":{"size":"2.21 MB"}},"typeUrl":{},"url":"http://m701.music.126.net/20250822111746/88562bac7e992f3433e465b0d8b57883/jdymusic/obj/wo3DlMOGwrbDjj7DisKw/35506990299/de4f/d537/7094/a86a0c894b20f627e58f4e037d4c395d.flac"}
|
||||
log source wy
|
||||
log --- start --- https://api.ikunshare.com/url?source=wy&songId=2149456401&quality=hires
|
||||
log quality hires
|
||||
log [CeruMusic] 发起请求: https://api.ikunshare.com/url?source=wy&songId=2149456401&quality=hires
|
||||
log [CeruMusic] 请求响应状态: 200
|
||||
log [CeruMusic] 请求响应内容: {"code":200,"message":"成功","url":"http://m701.music.126.net/20250822120208/2d51054583249a7c26ad5fb98b6cd10c/jdymusic/obj/wo3DlMOGwrbDjj7DisKw/35506990299/de4f/d537/7094/a86a0c894b20f627e58f4e037d4c395d.flac","info":{"id":"2149456401","name":"无","album":"无","artist":"无"},"ekey":"","cache":false,"quality":{"target":"无损音质 24Bit","result":"无损音质 24Bit"},"expire":{"ExpireAt":"2025-08-22 11:46:08","canExpire":true},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}}
|
||||
log API Response: {"body":{"code":200,"message":"成功","url":"http://m701.music.126.net/20250822120208/2d51054583249a7c26ad5fb98b6cd10c/jdymusic/obj/wo3DlMOGwrbDjj7DisKw/35506990299/de4f/d537/7094/a86a0c894b20f627e58f4e037d4c395d.flac","info":{"id":"2149456401","name":"无","album":"无","artist":"无"},"ekey":"","cache":false,"quality":{"target":"无损音质 24Bit","result":"无损音质 24Bit"},"expire":{"ExpireAt":"2025-08-22 11:46:08","canExpire":true},"yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}},"statusCode":200,"headers":{"server":["openresty"],"date":["Fri, 22 Aug 2025 03:37:08 GMT"],"content-type":["application/json; charset=utf-8"],"content-length":["573"],"connection":["keep-alive"],"access-control-allow-origin":["*"],"x-process-time":["0.05814129579812288"],"cache-control":["no-cache"],"alt-svc":["h3=\":443\"; ma=2592000"]}}
|
||||
log handleGetMusicUrl(wy_2149456401, hires) success, URL: http://m701.music.126.net/20250822120208/2d51054583249a7c26ad5fb98b6cd10c/jdymusic/obj/wo3DlMOGwrbDjj7DisKw/35506990299/de4f/d537/7094/a86a0c894b20f627e58f4e037d4c395d.flac
|
||||
end
|
||||
log [ikun音源] Got URL: http://m701.music.126.net/20250822120208/2d51054583249a7c26ad5fb98b6cd10c/jdymusic/obj/wo3DlMOGwrbDjj7DisKw/35506990299/de4f/d537/7094/a86a0c894b20f627e58f4e037d4c395d.flac
|
||||
@@ -1,83 +0,0 @@
|
||||
log [未知插件 by Ceru插件] 注册事件监听器: request
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/urlinfo/1.0.0
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [CeruMusic] Plugin "未知插件" loaded successfully.
|
||||
log [CeruMusic] 请求响应状态: 503
|
||||
log [CeruMusic] 响应不是JSON格式,内容: <html>
|
||||
<head><title>503 Service Temporarily Unavailable</title></head>
|
||||
<body>
|
||||
<center><h1>503 Service Temporarily Unavailable</h1></center>
|
||||
<hr><center>nginx/1.26.1</center>
|
||||
</body>
|
||||
</html>
|
||||
...
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
error [CeruMusic] Request failed: Expected JSON response but got: text/html
|
||||
log [未知插件 by Ceru插件] 发送事件: inited [object Object]
|
||||
log [未知插件 by Ceru插件] 动态音源信息已更新: [object Object]
|
||||
log [未知插件 by Ceru插件] 音源注册完成: kw,wy,mg,tx,kglog [未知插件] 使用事件驱动方式获取 tx 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/tx/1895330088/master
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
log [未知插件] 使用事件驱动方式获取 tx 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/tx/38576323/master
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [未知插件] 使用事件驱动方式获取 tx 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/tx/1808492017/master
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [未知插件] 使用事件驱动方式获取 tx 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/tx/502043537/master
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [未知插件] 使用事件驱动方式获取 tx 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/tx/1887139866/master
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [未知插件] 使用事件驱动方式获取 tx 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/tx/1365898499/master
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [未知插件] 使用事件驱动方式获取 tx 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/tx/1363948882/master
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
log [未知插件 by Ceru插件] 注册事件监听器: request
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/urlinfo/1.0.0
|
||||
log [CeruMusic] Plugin "未知插件" loaded successfully.
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [CeruMusic] 请求响应状态: 503
|
||||
log [CeruMusic] 响应不是JSON格式,内容: <html>
|
||||
<head><title>503 Service Temporarily Unavailable</title></head>
|
||||
<body>
|
||||
<center><h1>503 Service Temporarily Unavailable</h1></center>
|
||||
<hr><center>nginx/1.26.1</center>
|
||||
</body>
|
||||
</html>
|
||||
...
|
||||
log [未知插件 by Ceru插件] 发送事件: inited [object Object]
|
||||
error [CeruMusic] Request failed: Expected JSON response but got: text/html
|
||||
log [未知插件 by Ceru插件] 动态音源信息已更新: [object Object]
|
||||
log [未知插件 by Ceru插件] 音源注册完成: kw,wy,mg,tx,kg
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
log [未知插件] 使用事件驱动方式获取 tx 音源链接
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/url/tx/1363948882/master
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 响应不是JSON格式,内容: Not Found...
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
error [未知插件] Error: Expected JSON response but got: text/plain; charset=utf-8
|
||||
@@ -1,156 +0,0 @@
|
||||
log [CeruMusic] Plugin "ikun音源" loaded successfully.log [聚合API接口 (by lerd) by Ceru插件] 注册事件监听器: requestlog [聚合API接口 (by lerd) by Ceru插件] 发送事件: inited [object Object]log [聚合API接口 (by lerd) by Ceru插件] 动态音源信息已更新: [object Object]log [聚合API接口 (by lerd) by Ceru插件] 音源注册完成: tx,wy,kg,kw,mglog [CeruMusic] Plugin "聚合API接口 (by lerd)" loaded successfully.log [CeruMusic] 事件驱动插件初始化成功log [未知插件 by Ceru插件] 注册事件监听器: request
|
||||
log [CeruMusic] 发起请求: http://flower.tempmusics.tk/v1/urlinfo/1.0.0
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [CeruMusic] Plugin "未知插件" loaded successfully.
|
||||
log [CeruMusic] 请求响应状态: 503
|
||||
log [CeruMusic] 响应不是JSON格式,内容: <html>
|
||||
<head><title>503 Service Temporarily Unavailable</title></head>
|
||||
<body>
|
||||
<center><h1>503 Service Temporarily Unavailable</h1></center>
|
||||
<hr><center>nginx/1.26.1</center>
|
||||
</body>
|
||||
</html>
|
||||
...
|
||||
log [未知插件 by Ceru插件] 发送事件: inited [object Object]
|
||||
log [未知插件 by Ceru插件] 音源注册完成: kw,wy,mg,tx,kg
|
||||
log [未知插件 by Ceru插件] 动态音源信息已更新: [object Object]
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
error [CeruMusic] Request failed: Expected JSON response but got: text/html
|
||||
log [CeruMusic] Plugin "未知插件" loaded successfully.
|
||||
log 提取到的 MUSIC_QUALITY 字符串: {"kw":["128k","320k","flac","flac24bit","hires"],"wy":["128k","320k","flac","flac24bit","hires","atmos","master"],"mg":["128k","320k","flac","flac24bit","hires"]}
|
||||
log 提取的音源配置: [object Object]
|
||||
log [CeruMusic] 发起请求: https://api.ikunshare.com/script?key=&checkUpdate=6c72d109ed7db5cf037b3bbf1dfccb53
|
||||
log 解析后的 MUSIC_QUALITY 数据: [object Object]
|
||||
log --- start --- https://api.ikunshare.com/script?key=&checkUpdate=6c72d109ed7db5cf037b3bbf1dfccb53
|
||||
log [ikun音源 by Ceru插件] 注册事件监听器: request
|
||||
log [ikun音源 by Ceru插件] 发送事件: inited [object Object]
|
||||
log [ikun音源 by Ceru插件] 动态音源信息已更新: [object Object]
|
||||
log [CeruMusic] Plugin "ikun音源" loaded successfully.
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [ikun音源 by Ceru插件] 音源注册完成: kw,wy,mg
|
||||
log [CeruMusic] 请求响应状态: 200
|
||||
log [CeruMusic] 请求响应内容: [object Object]
|
||||
log checkUpdate success
|
||||
log API Response: [object Object]
|
||||
log [ikun音源 by Ceru插件] 发送事件: updateAlert [object Object]
|
||||
log "[CeruMusic] Plugin \"LiHouse 音乐插件\" loaded successfully."
|
||||
log "[CeruMusic] Plugin \"ceru 音乐插件\" loaded successfully."
|
||||
log 解析后的 MUSIC_QUALITY 数据: {"kw":["128k","320k","flac","flac24bit","hires"],"wy":["128k","320k","flac","flac24bit","hires","atmos","master"],"mg":["128k","320k","flac","flac24bit","hires"]}
|
||||
log 提取到的 MUSIC_QUALITY 字符串: {"kw":["128k","320k","flac","flac24bit","hires"],"wy":["128k","320k","flac","flac24bit","hires","atmos","master"],"mg":["128k","320k","flac","flac24bit","hires"]}
|
||||
log 提取的音源配置: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]}}
|
||||
log --- start --- https://api.ikunshare.com/script?key=&checkUpdate=5917eb1e565bf906fb0433bb1a3b00b5
|
||||
log [ikun音源 by Ceru插件] 注册事件监听器: request
|
||||
log [CeruMusic] 发起请求: https://api.ikunshare.com/script?key=&checkUpdate=5917eb1e565bf906fb0433bb1a3b00b5
|
||||
log [ikun音源 by Ceru插件] 发送事件: inited {"status":true,"openDevTools":false,"sources":{"kw":{"name":"kw","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"wy","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"mg","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit","hires"]}}}
|
||||
log [ikun音源 by Ceru插件] 音源注册完成: ["kw","wy","mg"]
|
||||
log [ikun音源 by Ceru插件] 动态音源信息已更新: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires"]}}
|
||||
log [CeruMusic] Plugin "ikun音源" loaded successfully.
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [CeruMusic] 请求响应状态: 200
|
||||
log [CeruMusic] 请求响应内容: {"code":200,"message":"成功","yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}}
|
||||
log API Response: {"body":{"code":200,"message":"成功","yourinfo":{"ip":"183.251.97.29","ua":"lx-music-nodejs/1.0.0"}},"statusCode":200,"headers":{"server":["openresty"],"date":["Fri, 22 Aug 2025 03:44:41 GMT"],"content-type":["application/json; charset=utf-8"],"content-length":["108"],"connection":["keep-alive"],"access-control-allow-origin":["*"],"x-process-time":["0.0007938602939248085"],"cache-control":["no-cache"],"alt-svc":["h3=\":443\"; ma=2592000"]}}
|
||||
log checkUpdate success
|
||||
log [野草🌾 by Ceru插件] 注册事件监听器: request
|
||||
log [CeruMusic] 发起请求: http://grass.tempmusics.tk/v1/urlinfo/1.0.0
|
||||
log [CeruMusic] Plugin "野草🌾" loaded successfully.
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [CeruMusic] 请求响应状态: 503
|
||||
log [CeruMusic] 请求响应内容: {"code":503,"msg":"Failed to parse response: invalid json response body at http://grass.tempmusics.tk/v1/urlinfo/1.0.0 reason: Unexpected token '<', \"<html>\r\n<h\"... is not valid JSON"}
|
||||
error [CeruMusic] Request failed: Failed to parse response: invalid json response body at http://grass.tempmusics.tk/v1/urlinfo/1.0.0 reason: Unexpected token '<', "<html>
|
||||
<h"... is not valid JSON
|
||||
log [野草🌾 by Ceru插件] 动态音源信息已更新: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k"]},"kg":{"name":"酷狗音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"tx":{"name":"QQ音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit","hires","atmos","master"]}}
|
||||
log [野草🌾 by Ceru插件] 音源注册完成: ["kw"]
|
||||
error [CeruMusic] 解析响应失败: invalid json response body at http://grass.tempmusics.tk/v1/urlinfo/1.0.0 reason: Unexpected token '<', "<html>
|
||||
<h"... is not valid JSON
|
||||
log [野草🌾 by Ceru插件] 发送事件: inited {"sources":{"kw":{"type":"music","actions":["musicUrl"],"qualitys":["128k"]}}}
|
||||
log [CeruMusic] Plugin "六音音源" loaded successfully.
|
||||
log [CeruMusic] 插件初始化完成: window is not defined
|
||||
log [CeruMusic] 插件初始化完成: window is not defined
|
||||
log [CeruMusic] Plugin "六音音源" loaded successfully.
|
||||
log [小熊猫音源 by Ceru插件] 注册事件监听器: request
|
||||
log [小熊猫音源 by Ceru插件] 发送事件: inited {"status":true,"openDevTools":false,"sources":{"kw":{"name":"酷我音乐","type":"music","actions":["musicUrl"],"qualitys":["128k","320k"]},"kg":{"name":"酷狗音乐","type":"music","actions":["musicUrl"],"qualitys":["128k"]},"tx":{"name":"企鹅音乐","type":"music","actions":["musicUrl"],"qualitys":["128k"]},"wy":{"name":"网易音乐","type":"music","actions":["musicUrl"],"qualitys":["128k"]},"mg":{"name":"咪咕音乐","type":"music","actions":["musicUrl"],"qualitys":["128k"]}}}
|
||||
log [小熊猫音源 by Ceru插件] 音源注册完成: ["kw","kg","tx","wy","mg"]
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [CeruMusic] 发起请求: https://api.leobba.cn/lxmusic/lx-music-source/package.json
|
||||
log [CeruMusic] Plugin "小熊猫音源" loaded successfully.
|
||||
log [小熊猫音源 by Ceru插件] 动态音源信息已更新: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k"]},"kg":{"name":"酷狗音乐","type":"music","qualitys":["128k"]},"tx":{"name":"QQ音乐","type":"music","qualitys":["128k"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k"]}}
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 请求响应内容: {"code":404,"msg":"Failed to parse response: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', \"<html>\r\n<h\"... is not valid JSON"}
|
||||
error [CeruMusic] 解析响应失败: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', "<html>
|
||||
<h"... is not valid JSON
|
||||
log [CeruMusic] 发起请求: https://api.leobba.cn/lxmusic/lx-music-source/package.json
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
error [CeruMusic] 解析响应失败: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', "<html>
|
||||
<h"... is not valid JSON
|
||||
log [CeruMusic] 请求响应内容: {"code":404,"msg":"Failed to parse response: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', \"<html>\r\n<h\"... is not valid JSON"}
|
||||
log [CeruMusic] 发起请求: https://api.leobba.cn/lxmusic/lx-music-source/package.json
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
error [CeruMusic] 解析响应失败: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', "<html>
|
||||
<h"... is not valid JSON
|
||||
log [CeruMusic] 请求响应内容: {"code":404,"msg":"Failed to parse response: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', \"<html>\r\n<h\"... is not valid JSON"}
|
||||
log [小熊猫音源 by Ceru插件] 注册事件监听器: request
|
||||
log [小熊猫音源 by Ceru插件] 动态音源信息已更新: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k"]},"kg":{"name":"酷狗音乐","type":"music","qualitys":["128k"]},"tx":{"name":"QQ音乐","type":"music","qualitys":["128k"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k"]}}
|
||||
log [CeruMusic] 发起请求: https://api.leobba.cn/lxmusic/lx-music-source/package.json
|
||||
log [小熊猫音源 by Ceru插件] 发送事件: inited {"status":true,"openDevTools":false,"sources":{"kw":{"name":"酷我音乐","type":"music","actions":["musicUrl"],"qualitys":["128k","320k"]},"kg":{"name":"酷狗音乐","type":"music","actions":["musicUrl"],"qualitys":["128k"]},"tx":{"name":"企鹅音乐","type":"music","actions":["musicUrl"],"qualitys":["128k"]},"wy":{"name":"网易音乐","type":"music","actions":["musicUrl"],"qualitys":["128k"]},"mg":{"name":"咪咕音乐","type":"music","actions":["musicUrl"],"qualitys":["128k"]}}}
|
||||
log [小熊猫音源 by Ceru插件] 音源注册完成: ["kw","kg","tx","wy","mg"]
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [CeruMusic] Plugin "小熊猫音源" loaded successfully.
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
error [CeruMusic] 解析响应失败: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', "<html>
|
||||
<h"... is not valid JSON
|
||||
log [CeruMusic] 请求响应内容: {"code":404,"msg":"Failed to parse response: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', \"<html>\r\n<h\"... is not valid JSON"}
|
||||
log [CeruMusic] 发起请求: https://api.leobba.cn/lxmusic/lx-music-source/package.json
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
error [CeruMusic] 解析响应失败: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', "<html>
|
||||
<h"... is not valid JSON
|
||||
log [CeruMusic] 请求响应内容: {"code":404,"msg":"Failed to parse response: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', \"<html>\r\n<h\"... is not valid JSON"}
|
||||
log [CeruMusic] 发起请求: https://api.leobba.cn/lxmusic/lx-music-source/package.json
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
error [CeruMusic] 解析响应失败: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', "<html>
|
||||
<h"... is not valid JSON
|
||||
log [CeruMusic] 请求响应内容: {"code":404,"msg":"Failed to parse response: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', \"<html>\r\n<h\"... is not valid JSON"}
|
||||
log [小熊猫音源 by Ceru插件] 注册事件监听器: request
|
||||
log [小熊猫音源 by Ceru插件] 发送事件: inited {"status":true,"openDevTools":false,"sources":{"kw":{"name":"酷我音乐","type":"music","actions":["musicUrl"],"qualitys":["128k","320k"]},"kg":{"name":"酷狗音乐","type":"music","actions":["musicUrl"],"qualitys":["128k"]},"tx":{"name":"企鹅音乐","type":"music","actions":["musicUrl"],"qualitys":["128k"]},"wy":{"name":"网易音乐","type":"music","actions":["musicUrl"],"qualitys":["128k"]},"mg":{"name":"咪咕音乐","type":"music","actions":["musicUrl"],"qualitys":["128k"]}}}
|
||||
log [小熊猫音源 by Ceru插件] 音源注册完成: ["kw","kg","tx","wy","mg"]
|
||||
log [CeruMusic] 发起请求: https://api.leobba.cn/lxmusic/lx-music-source/package.json
|
||||
log [小熊猫音源 by Ceru插件] 动态音源信息已更新: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k"]},"kg":{"name":"酷狗音乐","type":"music","qualitys":["128k"]},"tx":{"name":"QQ音乐","type":"music","qualitys":["128k"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k"]}}
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [CeruMusic] Plugin "小熊猫音源" loaded successfully.
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
log [CeruMusic] 请求响应内容: {"code":404,"msg":"Failed to parse response: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', \"<html>\r\n<h\"... is not valid JSON"}
|
||||
log [CeruMusic] 发起请求: https://api.leobba.cn/lxmusic/lx-music-source/package.json
|
||||
error [CeruMusic] 解析响应失败: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', "<html>
|
||||
<h"... is not valid JSON
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
error [CeruMusic] 解析响应失败: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', "<html>
|
||||
<h"... is not valid JSON
|
||||
log [CeruMusic] 请求响应内容: {"code":404,"msg":"Failed to parse response: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', \"<html>\r\n<h\"... is not valid JSON"}
|
||||
log [CeruMusic] 发起请求: https://api.leobba.cn/lxmusic/lx-music-source/package.json
|
||||
log [CeruMusic] 请求响应状态: 404
|
||||
error [CeruMusic] 解析响应失败: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', "<html>
|
||||
<h"... is not valid JSON
|
||||
log [CeruMusic] 请求响应内容: {"code":404,"msg":"Failed to parse response: invalid json response body at https://api.leobba.cn/lxmusic/lx-music-source/package.json reason: Unexpected token '<', \"<html>\r\n<h\"... is not valid JSON"}
|
||||
log [Huibq_lxmusic源 by Ceru插件] 注册事件监听器: request
|
||||
log [Huibq_lxmusic源 by Ceru插件] 发送事件: inited {"status":true,"openDevTools":false,"sources":{"kw":{"name":"kw","type":"music","actions":["musicUrl"],"qualitys":["128k","320k"]},"kg":{"name":"kg","type":"music","actions":["musicUrl"],"qualitys":["128k","320k"]},"tx":{"name":"tx","type":"music","actions":["musicUrl"],"qualitys":["128k","320k"]},"wy":{"name":"wy","type":"music","actions":["musicUrl"],"qualitys":["128k","320k"]},"mg":{"name":"mg","type":"music","actions":["musicUrl"],"qualitys":["128k","320k"]}}}
|
||||
log [Huibq_lxmusic源 by Ceru插件] 音源注册完成: ["kw","kg","tx","wy","mg"]
|
||||
log [Huibq_lxmusic源 by Ceru插件] 动态音源信息已更新: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k"]},"kg":{"name":"酷狗音乐","type":"music","qualitys":["128k","320k"]},"tx":{"name":"QQ音乐","type":"music","qualitys":["128k","320k"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k"]}}
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [CeruMusic] Plugin "Huibq_lxmusic源" loaded successfully.
|
||||
log 提取到的 MUSIC_QUALITY 字符串: {"kw":["128k","320k","flac","flac24bit"],"kg":["128k","320k","flac","flac24bit"],"tx":["128k","320k","flac","flac24bit"],"wy":["128k","320k","flac","flac24bit"],"mg":["128k","320k","flac","flac24bit"]}
|
||||
log 解析后的 MUSIC_QUALITY 数据: {"kw":["128k","320k","flac","flac24bit"],"kg":["128k","320k","flac","flac24bit"],"tx":["128k","320k","flac","flac24bit"],"wy":["128k","320k","flac","flac24bit"],"mg":["128k","320k","flac","flac24bit"]}
|
||||
log [微信公众号:洛雪音乐 by Ceru插件] 注册事件监听器: request
|
||||
log [微信公众号:洛雪音乐 by Ceru插件] 发送事件: inited {"status":true,"openDevTools":false,"sources":{"kw":{"name":"kw","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit"]},"kg":{"name":"kg","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit"]},"tx":{"name":"tx","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit"]},"wy":{"name":"wy","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit"]},"mg":{"name":"mg","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit"]},"local":{"name":"local","type":"music","actions":["musicUrl","pic","lyric"],"qualitys":[]}}}
|
||||
log 提取的音源配置: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"kg":{"name":"酷狗音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"tx":{"name":"QQ音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]}}
|
||||
log [微信公众号:洛雪音乐 by Ceru插件] 音源注册完成: ["kw","kg","tx","wy","mg","local"]
|
||||
log [微信公众号:洛雪音乐 by Ceru插件] 动态音源信息已更新: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"kg":{"name":"酷狗音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"tx":{"name":"QQ音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"local":{"name":"LOCAL音乐","type":"music","qualitys":[]}}
|
||||
log [CeruMusic] Plugin "微信公众号:洛雪音乐" loaded successfully.
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log 提取到的 MUSIC_QUALITY 字符串: {"kw":["128k","320k","flac","flac24bit"],"kg":["128k","320k","flac","flac24bit"],"tx":["128k","320k","flac","flac24bit"],"wy":["128k","320k","flac","flac24bit"],"mg":["128k","320k","flac","flac24bit"]}
|
||||
log 解析后的 MUSIC_QUALITY 数据: {"kw":["128k","320k","flac","flac24bit"],"kg":["128k","320k","flac","flac24bit"],"tx":["128k","320k","flac","flac24bit"],"wy":["128k","320k","flac","flac24bit"],"mg":["128k","320k","flac","flac24bit"]}
|
||||
log [Ceru插件 by Ceru插件] 注册事件监听器: request
|
||||
log [Ceru插件 by Ceru插件] 发送事件: inited {"status":true,"openDevTools":false,"sources":{"kw":{"name":"kw","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit"]},"kg":{"name":"kg","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit"]},"tx":{"name":"tx","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit"]},"wy":{"name":"wy","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit"]},"mg":{"name":"mg","type":"music","actions":["musicUrl"],"qualitys":["128k","320k","flac","flac24bit"]},"local":{"name":"local","type":"music","actions":["musicUrl","pic","lyric"],"qualitys":[]}}}
|
||||
log 提取的音源配置: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"kg":{"name":"酷狗音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"tx":{"name":"QQ音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]}}
|
||||
log [Ceru插件 by Ceru插件] 音源注册完成: ["kw","kg","tx","wy","mg","local"]
|
||||
log [Ceru插件 by Ceru插件] 动态音源信息已更新: {"kw":{"name":"酷我音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"kg":{"name":"酷狗音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"tx":{"name":"QQ音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"wy":{"name":"网易云音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"mg":{"name":"咪咕音乐","type":"music","qualitys":["128k","320k","flac","flac24bit"]},"local":{"name":"LOCAL音乐","type":"music","qualitys":[]}}
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [CeruMusic] Plugin "Ceru插件" loaded successfully.
|
||||
@@ -1,75 +0,0 @@
|
||||
/**
|
||||
* LiHouse 音乐插件
|
||||
* @author CodeBuddy
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
// 基础 URL
|
||||
const baseTwoUrl = 'https://www.lihouse.xyz/coco_widget';
|
||||
|
||||
// 1. 插件信息
|
||||
const pluginInfo = {
|
||||
name: 'ceru 音乐插件',
|
||||
version: '1.0.0',
|
||||
author: '时迁酱',
|
||||
description: '提供 网易云 音乐资源的访问'
|
||||
}
|
||||
|
||||
// 2. 支持的音源配置
|
||||
const sources = {
|
||||
wy: {
|
||||
name: '网易云',
|
||||
type: 'music',
|
||||
qualitys: ['320k'] // 假设支持这些音质
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 获取音乐URL的核心函数
|
||||
async function musicUrl(source, musicInfo, quality) {
|
||||
// 从 cerumusic 对象获取 API
|
||||
const { request, env, version } = cerumusic
|
||||
|
||||
// 构建请求参数
|
||||
const songId = musicInfo.songmid || musicInfo.hash;
|
||||
if (!songId) {
|
||||
throw new Error('无效的歌曲ID');
|
||||
}
|
||||
|
||||
const apiUrl = `${baseTwoUrl}/music_resource/id/${songId}`;
|
||||
|
||||
console.log(`[${pluginInfo.name}] 请求音乐链接: ${apiUrl}`);
|
||||
|
||||
try {
|
||||
// 发起网络请求
|
||||
const { body, statusCode } = await request(apiUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': `cerumusic-${env}/${version}`
|
||||
}
|
||||
});
|
||||
|
||||
// 处理响应
|
||||
if (statusCode !== 200 || !body.status) {
|
||||
const errorMessage = body.message || '歌曲不存在';
|
||||
console.error(`[${pluginInfo.name}] Error: ${errorMessage}`);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
// 根据请求的音质选择对应的链接
|
||||
// 假设 song_data 中包含不同音质的链接
|
||||
const songData = body.song_data;
|
||||
return songData.url
|
||||
|
||||
throw new Error('无效的响应格式');
|
||||
} catch (error) {
|
||||
console.error(`[${pluginInfo.name}] 请求失败:`, error.message);
|
||||
throw new Error(`获取音乐链接失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
// 导出插件
|
||||
module.exports = {
|
||||
pluginInfo,
|
||||
sources,
|
||||
musicUrl,
|
||||
}
|
||||
@@ -1,284 +0,0 @@
|
||||
/**
|
||||
* 由 CeruMusic 插件转换器转换 - @author sqj
|
||||
* @name ikun音源
|
||||
* @author ikunshare
|
||||
* @version v14
|
||||
* @description 反馈群951962664
|
||||
*/
|
||||
|
||||
const pluginInfo = {
|
||||
name: "ikun音源",
|
||||
version: "v14",
|
||||
author: "ikunshare",
|
||||
description: "反馈群951962664"
|
||||
};
|
||||
|
||||
// 原始插件代码
|
||||
const originalPluginCode = "/*!\n * @name ikun音源\n * @description 反馈群951962664\n * @version v14\n * @author ikunshare\n */\n\nconst DEV_ENABLE = false\nconst UPDATE_ENABLE = true\nconst API_URL = \"https://api.ikunshare.com\"\nconst API_KEY = ``\nconst MUSIC_QUALITY = JSON.parse('{\"kw\":[\"128k\",\"320k\",\"flac\",\"flac24bit\",\"hires\"],\"wy\":[\"128k\",\"320k\",\"flac\",\"flac24bit\",\"hires\",\"atmos\",\"master\"],\"mg\":[\"128k\",\"320k\",\"flac\",\"flac24bit\",\"hires\"]}');\nconst MUSIC_SOURCE = Object.keys(MUSIC_QUALITY);\n\nconst { EVENT_NAMES, request, on, send, utils, env, version } = globalThis.lx;\n\nconst SCRIPT_MD5 = \"5917eb1e565bf906fb0433bb1a3b00b5\";\n\nconst httpFetch = (url, options = { method: \"GET\" }) => {\n return new Promise((resolve, reject) => {\n console.log(\"--- start --- \" + url);\n request(url, options, (err, resp) => {\n if (err) return reject(err);\n console.log(\"API Response: \", resp);\n resolve(resp);\n });\n });\n};\n\nconst handleGetMusicUrl = async (source, musicInfo, quality) => {\n const songId = musicInfo.hash ?? musicInfo.songmid;\n const request = await httpFetch(\n `${API_URL}/url?source=${source}&songId=${songId}&quality=${quality}`,\n {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"User-Agent\": `${\n env ? `lx-music-${env}/${version}` : `lx-music-request/${version}`\n }`,\n \"X-Request-Key\": API_KEY,\n },\n follow_max: 5,\n }\n );\n const { body } = request;\n if (!body || isNaN(Number(body.code))) throw new Error(\"unknow error\");\n if (env != \"mobile\") console.groupEnd();\n switch (body.code) {\n case 200:\n console.log(\n `handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) success, URL: ${body.url}`\n );\n return body.url;\n case 403:\n console.log(\n `handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) failed: Key失效/鉴权失败`\n );\n throw new Error(\"Key失效/鉴权失败\");\n case 500:\n console.log(\n `handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) failed, ${body.msg}`\n );\n throw new Error(`获取URL失败, ${body.msg ?? \"未知错误\"}`);\n case 429:\n console.log(\n `handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) failed, 请求过于频繁,请休息一下吧`\n );\n throw new Error(\"请求过速\");\n default:\n console.log(\n `handleGetMusicUrl(${source}_${\n musicInfo.songmid\n }, ${quality}) failed, ${body.msg ? body.msg : \"未知错误\"}`\n );\n throw new Error(body.msg ?? \"未知错误\");\n }\n};\n\nconst checkUpdate = async () => {\n const request = await httpFetch(\n `${API_URL}/script?key=${API_KEY}&checkUpdate=${SCRIPT_MD5}`,\n {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"User-Agent\": `${\n env ? `lx-music-${env}/${version}` : `lx-music-request/${version}`\n }`,\n },\n }\n );\n const { body } = request;\n\n if (!body || body.code !== 200) console.log(\"checkUpdate failed\");\n else {\n console.log(\"checkUpdate success\");\n if (body.data != null) {\n globalThis.lx.send(lx.EVENT_NAMES.updateAlert, {\n log: body.data.updateMsg,\n updateUrl: body.data.updateUrl,\n });\n }\n }\n};\n\nconst musicSources = {};\nMUSIC_SOURCE.forEach((item) => {\n musicSources[item] = {\n name: item,\n type: \"music\",\n actions: [\"musicUrl\"],\n qualitys: MUSIC_QUALITY[item],\n };\n});\n\non(EVENT_NAMES.request, ({ action, source, info }) => {\n switch (action) {\n case \"musicUrl\":\n if (env != \"mobile\") {\n console.group(`Handle Action(musicUrl)`);\n console.log(\"source\", source);\n console.log(\"quality\", info.type);\n console.log(\"musicInfo\", info.musicInfo);\n } else {\n console.log(`Handle Action(musicUrl)`);\n console.log(\"source\", source);\n console.log(\"quality\", info.type);\n console.log(\"musicInfo\", info.musicInfo);\n }\n return handleGetMusicUrl(source, info.musicInfo, info.type)\n .then((data) => Promise.resolve(data))\n .catch((err) => Promise.reject(err));\n default:\n console.error(`action(${action}) not support`);\n return Promise.reject(\"action not support\");\n }\n});\n\nif (UPDATE_ENABLE) checkUpdate();\n\nsend(EVENT_NAMES.inited, {\n status: true,\n openDevTools: DEV_ENABLE,\n sources: musicSources,\n});\n";
|
||||
|
||||
// 音源信息将通过插件的 send 调用动态获取
|
||||
let sources = {};
|
||||
|
||||
function getSourceName(sourceId) {
|
||||
const nameMap = {
|
||||
'kw': '酷我音乐',
|
||||
'kg': '酷狗音乐',
|
||||
'tx': 'QQ音乐',
|
||||
'wy': '网易云音乐',
|
||||
'mg': '咪咕音乐'
|
||||
};
|
||||
return nameMap[sourceId] || sourceId.toUpperCase() + '音乐';
|
||||
}
|
||||
|
||||
// 提取默认音源配置作为备用
|
||||
function extractDefaultSources() {
|
||||
// 尝试从 MUSIC_QUALITY 常量中提取音源信息
|
||||
const qualityMatch = originalPluginCode.match(/const\s+MUSIC_QUALITY\s*=\s*JSON\.parse\(([^)]+)\)/);
|
||||
if (qualityMatch) {
|
||||
try {
|
||||
// 处理字符串,移除外层引号并正确解析
|
||||
let qualityStr = qualityMatch[1].trim();
|
||||
if (qualityStr.startsWith("'") && qualityStr.endsWith("'")) {
|
||||
qualityStr = qualityStr.slice(1, -1);
|
||||
} else if (qualityStr.startsWith('"') && qualityStr.endsWith('"')) {
|
||||
qualityStr = qualityStr.slice(1, -1);
|
||||
}
|
||||
|
||||
console.log('提取到的 MUSIC_QUALITY 字符串:', qualityStr);
|
||||
const qualityData = JSON.parse(qualityStr);
|
||||
console.log('解析后的 MUSIC_QUALITY 数据:', qualityData);
|
||||
|
||||
const extractedSources = {};
|
||||
Object.keys(qualityData).forEach(sourceId => {
|
||||
extractedSources[sourceId] = {
|
||||
name: getSourceName(sourceId),
|
||||
type: 'music',
|
||||
qualitys: qualityData[sourceId] || ['128k', '320k']
|
||||
};
|
||||
});
|
||||
|
||||
console.log('提取的音源配置:', extractedSources);
|
||||
return extractedSources;
|
||||
} catch (e) {
|
||||
console.log('解析 MUSIC_QUALITY 失败:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 默认音源配置
|
||||
return {
|
||||
kw: { name: "酷我音乐", type: "music", qualitys: ['128k', '320k', 'flac', 'flac24bit', 'hires', 'atmos', 'master'] },
|
||||
kg: { name: "酷狗音乐", type: "music", qualitys: ['128k', '320k', 'flac', 'flac24bit', 'hires', 'atmos', 'master'] },
|
||||
tx: { name: "QQ音乐", type: "music", qualitys: ['128k', '320k', 'flac', 'flac24bit', 'hires', 'atmos', 'master'] },
|
||||
wy: { name: "网易云音乐", type: "music", qualitys: ['128k', '320k', 'flac', 'flac24bit', 'hires', 'atmos', 'master'] },
|
||||
mg: { name: "咪咕音乐", type: "music", qualitys: ['128k', '320k', 'flac', 'flac24bit', 'hires', 'atmos', 'master'] }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 初始化默认音源
|
||||
sources = extractDefaultSources();
|
||||
|
||||
// 插件状态
|
||||
let isInitialized = false;
|
||||
let pluginSources = {};
|
||||
let requestHandler = null;
|
||||
initializePlugin()
|
||||
function initializePlugin() {
|
||||
if (isInitialized) return;
|
||||
|
||||
const { request, utils } = cerumusic;
|
||||
|
||||
// 创建完整的 lx 模拟环境
|
||||
const mockLx = {
|
||||
EVENT_NAMES: {
|
||||
request: 'request',
|
||||
inited: 'inited',
|
||||
updateAlert: 'updateAlert'
|
||||
},
|
||||
on: (event, handler) => {
|
||||
console.log(`[ikun音源 by Ceru插件] 注册事件监听器: ${event}`);
|
||||
if (event === 'request') {
|
||||
requestHandler = handler;
|
||||
}
|
||||
},
|
||||
send: (event, data) => {
|
||||
console.log(`[ikun音源 by Ceru插件] 发送事件: ${event}`, data);
|
||||
if (event === 'inited' && data.sources) {
|
||||
// 动态更新音源信息,保持原始的音质配置
|
||||
pluginSources = data.sources;
|
||||
|
||||
// 将插件发送的音源信息转换为正确格式并同步到导出的 sources
|
||||
Object.keys(pluginSources).forEach(sourceId => {
|
||||
const sourceInfo = pluginSources[sourceId];
|
||||
|
||||
// 保留原始音质配置,如果存在的话
|
||||
const originalQualitys = sources[sourceId] && sources[sourceId].qualitys;
|
||||
|
||||
sources[sourceId] = {
|
||||
name: getSourceName(sourceId),
|
||||
type: sourceInfo.type || 'music',
|
||||
// 优先使用插件发送的音质配置,其次使用原始解析的配置,最后使用默认配置
|
||||
qualitys: sourceInfo.qualitys || originalQualitys || ['128k', '320k']
|
||||
};
|
||||
});
|
||||
|
||||
console.log('[ikun音源 by Ceru插件] 音源注册完成:', Object.keys(pluginSources));
|
||||
console.log('[ikun音源 by Ceru插件] 动态音源信息已更新:', sources);
|
||||
}
|
||||
},
|
||||
request: request,
|
||||
utils: {
|
||||
buffer: utils.buffer,
|
||||
crypto: {
|
||||
aesEncrypt: (data, mode, key, iv) => {
|
||||
// 简化的 AES 加密实现
|
||||
try {
|
||||
return utils.crypto ? utils.crypto.aesEncrypt(data, mode, key, iv) : data;
|
||||
} catch (e) {
|
||||
return data;
|
||||
}
|
||||
},
|
||||
md5: (str) => {
|
||||
try {
|
||||
return utils.crypto ? utils.crypto.md5(str) : str;
|
||||
} catch (e) {
|
||||
return str;
|
||||
}
|
||||
},
|
||||
randomBytes: (size) => {
|
||||
try {
|
||||
return utils.crypto ? utils.crypto.randomBytes(size) : Buffer.alloc(size);
|
||||
} catch (e) {
|
||||
return Buffer.alloc(size);
|
||||
}
|
||||
},
|
||||
rsaEncrypt: (data, key) => {
|
||||
try {
|
||||
return utils.crypto ? utils.crypto.rsaEncrypt(data, key) : data;
|
||||
} catch (e) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
version: '1.0.0',
|
||||
currentScriptInfo: {
|
||||
rawScript: originalPluginCode,
|
||||
name: 'ikun音源',
|
||||
version: 'v14',
|
||||
author: 'ikunshare',
|
||||
description: '反馈群951962664'
|
||||
},
|
||||
env: 'nodejs' // 添加环境信息
|
||||
};
|
||||
|
||||
// 创建全局环境
|
||||
const globalThis = {
|
||||
lx: mockLx
|
||||
};
|
||||
|
||||
// 创建沙箱环境
|
||||
const sandbox = {
|
||||
globalThis: globalThis,
|
||||
lx: mockLx,
|
||||
console: console,
|
||||
setTimeout: setTimeout,
|
||||
clearTimeout: clearTimeout,
|
||||
setInterval: setInterval,
|
||||
clearInterval: clearInterval,
|
||||
Buffer: Buffer,
|
||||
JSON: JSON,
|
||||
require: () => ({}),
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
process: { env: { NODE_ENV: 'production' } }
|
||||
};
|
||||
|
||||
try {
|
||||
// 使用 Function 构造器执行插件代码
|
||||
const pluginFunction = new Function(
|
||||
'globalThis', 'lx', 'console', 'setTimeout', 'clearTimeout',
|
||||
'setInterval', 'clearInterval', 'Buffer', 'JSON', 'require',
|
||||
'module', 'exports', 'process',
|
||||
originalPluginCode
|
||||
);
|
||||
|
||||
pluginFunction(
|
||||
globalThis, mockLx, console, setTimeout, clearTimeout,
|
||||
setInterval, clearInterval, Buffer, JSON, () => ({}),
|
||||
{ exports: {} }, {}, { env: { NODE_ENV: 'production' } }
|
||||
);
|
||||
|
||||
isInitialized = true;
|
||||
console.log(`[CeruMusic] 事件驱动插件初始化成功`);
|
||||
} catch (error) {
|
||||
console.log(`[CeruMusic] 插件初始化完成: ${error.message}`);
|
||||
isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function musicUrl(source, musicInfo, quality) {
|
||||
// 确保插件已初始化
|
||||
initializePlugin();
|
||||
|
||||
// 等待一小段时间让插件完全初始化
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
if (!requestHandler) {
|
||||
const errorMessage = '插件请求处理器未初始化';
|
||||
console.error(`[${pluginInfo.name}] Error: ${errorMessage}`);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
console.log(`[${pluginInfo.name}] 使用事件驱动方式获取 ${source} 音源链接`);
|
||||
|
||||
try {
|
||||
// 调用插件的请求处理器
|
||||
const result = await requestHandler({
|
||||
source: source,
|
||||
action: 'musicUrl',
|
||||
info: {
|
||||
musicInfo: musicInfo,
|
||||
type: quality
|
||||
}
|
||||
});
|
||||
|
||||
// 检查结果是否有效
|
||||
if (!result) {
|
||||
const errorMessage = `获取 ${source} 音源链接失败: 返回结果为空`;
|
||||
console.error(`[${pluginInfo.name}] Error: ${errorMessage}`);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
// 如果结果是对象且包含错误信息
|
||||
if (typeof result === 'object' && result.error) {
|
||||
const errorMessage = result.error || `获取 ${source} 音源链接失败`;
|
||||
console.error(`[${pluginInfo.name}] Error: ${errorMessage}`);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
// 如果结果是对象且包含状态码
|
||||
if (typeof result === 'object' && result.code && result.code !== 200) {
|
||||
const errorMessage = result.msg || `接口错误 (Code: ${result.code})`;
|
||||
console.error(`[${pluginInfo.name}] Error: ${errorMessage}`);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
console.log(`[${pluginInfo.name}] Got URL: ${typeof result === 'string' ? result : result.url || result}`);
|
||||
return result;
|
||||
} catch (error) {
|
||||
// 确保错误信息格式与 example-plugin.js 一致
|
||||
const errorMessage = error.message || `获取 ${source} 音源链接时发生未知错误`;
|
||||
console.error(`[${pluginInfo.name}] Error: ${errorMessage}`);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
pluginInfo,
|
||||
sources,
|
||||
musicUrl
|
||||
};
|
||||
@@ -6,7 +6,6 @@ const enc_key = Buffer.from(
|
||||
[0x40, 0x47, 0x61, 0x77, 0x5e, 0x32, 0x74, 0x47, 0x51, 0x36, 0x31, 0x2d, 0xce, 0xd2, 0x6e, 0x69],
|
||||
'binary'
|
||||
)
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
const decodeLyric = (str) =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (!str.length) return
|
||||
@@ -22,7 +21,6 @@ const decodeLyric = (str) =>
|
||||
|
||||
const headExp = /^.*\[id:\$\w+\]\n/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
const parseLyric = (str) => {
|
||||
str = str.replace(/\r/g, '')
|
||||
if (headExp.test(str)) str = str.replace(headExp, '')
|
||||
@@ -85,7 +83,6 @@ const parseLyric = (str) => {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export const decodeKrc = async (data) => {
|
||||
return decodeLyric(data).then(parseLyric)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// 业务工具方法
|
||||
|
||||
import { LX } from "../../types/global"
|
||||
|
||||
export const toNewMusicInfo = (oldMusicInfo: any): LX.Music.MusicInfo => {
|
||||
const meta: Record<string, any> = {
|
||||
songId: oldMusicInfo.songmid, // 歌曲ID,local为文件路径
|
||||
|
||||
262
src/main/autoUpdate.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
import { BrowserWindow, app, shell } from 'electron';
|
||||
import axios from 'axios';
|
||||
import fs from 'fs';
|
||||
import path from 'node:path';
|
||||
|
||||
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
let currentUpdateInfo: UpdateInfo | null = null;
|
||||
let downloadProgress = { percent: 0, transferred: 0, total: 0 };
|
||||
|
||||
// 更新信息接口
|
||||
interface UpdateInfo {
|
||||
url: string;
|
||||
name: string;
|
||||
notes: string;
|
||||
pub_date: string;
|
||||
}
|
||||
|
||||
// 更新服务器配置
|
||||
const UPDATE_SERVER = 'https://update.ceru.shiqianjiang.cn';
|
||||
const UPDATE_API_URL = `${UPDATE_SERVER}/update/${process.platform}/${app.getVersion()}`;
|
||||
|
||||
// 初始化自动更新器
|
||||
export function initAutoUpdater(window: BrowserWindow) {
|
||||
mainWindow = window;
|
||||
console.log('Auto updater initialized');
|
||||
}
|
||||
|
||||
// 检查更新
|
||||
export async function checkForUpdates(window?: BrowserWindow) {
|
||||
if (window) {
|
||||
mainWindow = window;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Checking for updates...');
|
||||
mainWindow?.webContents.send('auto-updater:checking-for-update');
|
||||
|
||||
const updateInfo = await fetchUpdateInfo();
|
||||
|
||||
if (updateInfo && isNewerVersion(updateInfo.name, app.getVersion())) {
|
||||
console.log('Update available:', updateInfo);
|
||||
currentUpdateInfo = updateInfo;
|
||||
mainWindow?.webContents.send('auto-updater:update-available', updateInfo);
|
||||
} else {
|
||||
console.log('No update available');
|
||||
mainWindow?.webContents.send('auto-updater:update-not-available');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking for updates:', error);
|
||||
mainWindow?.webContents.send('auto-updater:error', (error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取更新信息
|
||||
async function fetchUpdateInfo(): Promise<UpdateInfo | null> {
|
||||
try {
|
||||
const response = await axios.get(UPDATE_API_URL, {
|
||||
timeout: 10000, // 10秒超时
|
||||
validateStatus: (status) => status === 200 || status === 204 // 允许 200 和 204 状态码
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
return response.data as UpdateInfo;
|
||||
} else if (response.status === 204) {
|
||||
// 204 表示没有更新
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
// 服务器响应了错误状态码
|
||||
throw new Error(`HTTP ${error.response.status}: ${error.response.statusText}`);
|
||||
} else if (error.request) {
|
||||
// 请求已发出但没有收到响应
|
||||
throw new Error('Network error: No response received');
|
||||
} else {
|
||||
// 其他错误
|
||||
throw new Error(`Request failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 比较版本号
|
||||
function isNewerVersion(remoteVersion: string, currentVersion: string): boolean {
|
||||
const parseVersion = (version: string) => {
|
||||
return version.replace(/^v/, '').split('.').map(num => parseInt(num, 10));
|
||||
};
|
||||
|
||||
const remote = parseVersion(remoteVersion);
|
||||
const current = parseVersion(currentVersion);
|
||||
|
||||
for (let i = 0; i < Math.max(remote.length, current.length); i++) {
|
||||
const r = remote[i] || 0;
|
||||
const c = current[i] || 0;
|
||||
|
||||
if (r > c) return true;
|
||||
if (r < c) return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 下载更新
|
||||
export async function downloadUpdate() {
|
||||
if (!currentUpdateInfo) {
|
||||
throw new Error('No update info available');
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Starting download:', currentUpdateInfo.url);
|
||||
|
||||
// 通知渲染进程开始下载
|
||||
mainWindow?.webContents.send('auto-updater:download-started', currentUpdateInfo);
|
||||
|
||||
const downloadPath = await downloadFile(currentUpdateInfo.url);
|
||||
console.log('Download completed:', downloadPath);
|
||||
|
||||
mainWindow?.webContents.send('auto-updater:update-downloaded', {
|
||||
downloadPath,
|
||||
updateInfo: currentUpdateInfo
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Download failed:', error);
|
||||
mainWindow?.webContents.send('auto-updater:error', (error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
async function downloadFile(url: string): Promise<string> {
|
||||
const fileName = path.basename(url);
|
||||
const downloadPath = path.join(app.getPath('temp'), fileName);
|
||||
|
||||
// 进度节流变量
|
||||
let lastProgressSent = 0;
|
||||
let lastProgressTime = 0;
|
||||
const PROGRESS_THROTTLE_INTERVAL = 500; // 500ms 发送一次进度
|
||||
const PROGRESS_THRESHOLD = 1; // 进度变化超过1%才发送
|
||||
|
||||
try {
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: url,
|
||||
responseType: 'stream',
|
||||
timeout: 30000, // 30秒超时
|
||||
onDownloadProgress: (progressEvent) => {
|
||||
const { loaded, total } = progressEvent;
|
||||
const percent = total ? (loaded / total) * 100 : 0;
|
||||
const currentTime = Date.now();
|
||||
|
||||
// 节流逻辑:只在进度变化显著或时间间隔足够时发送
|
||||
const progressDiff = Math.abs(percent - lastProgressSent);
|
||||
const timeDiff = currentTime - lastProgressTime;
|
||||
|
||||
if (progressDiff >= PROGRESS_THRESHOLD || timeDiff >= PROGRESS_THROTTLE_INTERVAL) {
|
||||
downloadProgress = {
|
||||
percent,
|
||||
transferred: loaded,
|
||||
total: total || 0
|
||||
};
|
||||
|
||||
mainWindow?.webContents.send('auto-updater:download-progress', downloadProgress);
|
||||
lastProgressSent = percent;
|
||||
lastProgressTime = currentTime;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 发送初始进度
|
||||
const totalSize = parseInt(response.headers['content-length'] || '0', 10);
|
||||
mainWindow?.webContents.send('auto-updater:download-progress', {
|
||||
percent: 0,
|
||||
transferred: 0,
|
||||
total: totalSize
|
||||
});
|
||||
|
||||
// 创建写入流
|
||||
const writer = fs.createWriteStream(downloadPath);
|
||||
|
||||
// 将响应数据流写入文件
|
||||
response.data.pipe(writer);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
writer.on('finish', () => {
|
||||
// 发送最终进度
|
||||
mainWindow?.webContents.send('auto-updater:download-progress', {
|
||||
percent: 100,
|
||||
transferred: totalSize,
|
||||
total: totalSize
|
||||
});
|
||||
|
||||
console.log('File download completed:', downloadPath);
|
||||
resolve(downloadPath);
|
||||
});
|
||||
|
||||
writer.on('error', (error) => {
|
||||
// 删除部分下载的文件
|
||||
fs.unlink(downloadPath, () => {});
|
||||
reject(error);
|
||||
});
|
||||
|
||||
response.data.on('error', (error: Error) => {
|
||||
writer.destroy();
|
||||
fs.unlink(downloadPath, () => {});
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
// 删除可能创建的文件
|
||||
if (fs.existsSync(downloadPath)) {
|
||||
fs.unlink(downloadPath, () => {});
|
||||
}
|
||||
|
||||
if (error.response) {
|
||||
throw new Error(`Download failed: HTTP ${error.response.status} ${error.response.statusText}`);
|
||||
} else if (error.request) {
|
||||
throw new Error('Download failed: Network error');
|
||||
} else {
|
||||
throw new Error(`Download failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 退出并安装
|
||||
export function quitAndInstall() {
|
||||
if (!currentUpdateInfo) {
|
||||
console.error('No update info available for installation');
|
||||
return;
|
||||
}
|
||||
|
||||
// 对于不同平台,处理方式不同
|
||||
if (process.platform === 'win32') {
|
||||
// Windows: 打开安装程序
|
||||
const fileName = path.basename(currentUpdateInfo.url);
|
||||
const downloadPath = path.join(app.getPath('temp'), fileName);
|
||||
|
||||
if (fs.existsSync(downloadPath)) {
|
||||
shell.openPath(downloadPath).then(() => {
|
||||
app.quit();
|
||||
});
|
||||
} else {
|
||||
console.error('Downloaded file not found:', downloadPath);
|
||||
}
|
||||
} else if (process.platform === 'darwin') {
|
||||
// macOS: 打开 dmg 或 zip 文件
|
||||
const fileName = path.basename(currentUpdateInfo.url);
|
||||
const downloadPath = path.join(app.getPath('temp'), fileName);
|
||||
|
||||
if (fs.existsSync(downloadPath)) {
|
||||
shell.openPath(downloadPath).then(() => {
|
||||
app.quit();
|
||||
});
|
||||
} else {
|
||||
console.error('Downloaded file not found:', downloadPath);
|
||||
}
|
||||
} else {
|
||||
// Linux: 打开下载文件夹
|
||||
shell.showItemInFolder(path.join(app.getPath('temp'), path.basename(currentUpdateInfo.url)));
|
||||
}
|
||||
}
|
||||
28
src/main/events/autoUpdate.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { ipcMain, BrowserWindow } from 'electron';
|
||||
import { initAutoUpdater, checkForUpdates, downloadUpdate, quitAndInstall } from '../autoUpdate';
|
||||
|
||||
// 注册自动更新相关的IPC事件
|
||||
export function registerAutoUpdateEvents() {
|
||||
// 检查更新
|
||||
ipcMain.handle('auto-updater:check-for-updates', (event) => {
|
||||
const window = BrowserWindow.fromWebContents(event.sender);
|
||||
if (window) {
|
||||
checkForUpdates(window);
|
||||
}
|
||||
});
|
||||
|
||||
// 下载更新
|
||||
ipcMain.handle('auto-updater:download-update', () => {
|
||||
downloadUpdate();
|
||||
});
|
||||
|
||||
// 安装更新
|
||||
ipcMain.handle('auto-updater:quit-and-install', () => {
|
||||
quitAndInstall();
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化自动更新(在主窗口创建后调用)
|
||||
export function initAutoUpdateForWindow(window: BrowserWindow) {
|
||||
initAutoUpdater(window);
|
||||
}
|
||||
33
src/main/events/musicCache.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import { musicCacheService } from '../services/musicCache'
|
||||
|
||||
// 获取缓存信息
|
||||
ipcMain.handle('music-cache:get-info', async () => {
|
||||
try {
|
||||
return await musicCacheService.getCacheInfo()
|
||||
} catch (error) {
|
||||
console.error('获取缓存信息失败:', error)
|
||||
return { count: 0, size: 0, sizeFormatted: '0 B' }
|
||||
}
|
||||
})
|
||||
|
||||
// 清空缓存
|
||||
ipcMain.handle('music-cache:clear', async () => {
|
||||
try {
|
||||
await musicCacheService.clearCache()
|
||||
return { success: true, message: '缓存已清空' }
|
||||
} catch (error) {
|
||||
console.error('清空缓存失败:', error)
|
||||
return { success: false, message: '清空缓存失败' }
|
||||
}
|
||||
})
|
||||
|
||||
// 获取缓存大小
|
||||
ipcMain.handle('music-cache:get-size', async () => {
|
||||
try {
|
||||
return await musicCacheService.getCacheSize()
|
||||
} catch (error) {
|
||||
console.error('获取缓存大小失败:', error)
|
||||
return 0
|
||||
}
|
||||
})
|
||||
@@ -191,6 +191,8 @@ ipcMain.handle('service-music-request', async (_, api, args) => {
|
||||
})
|
||||
|
||||
aiEvents(mainWindow)
|
||||
import './events/musicCache'
|
||||
import { registerAutoUpdateEvents, initAutoUpdateForWindow } from './events/autoUpdate'
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
@@ -275,6 +277,14 @@ app.whenReady().then(async () => {
|
||||
|
||||
createWindow()
|
||||
createTray()
|
||||
|
||||
// 注册自动更新事件
|
||||
registerAutoUpdateEvents()
|
||||
|
||||
// 初始化自动更新器
|
||||
if (mainWindow) {
|
||||
initAutoUpdateForWindow(mainWindow)
|
||||
}
|
||||
|
||||
app.on('activate', function () {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
|
||||
189
src/main/services/musicCache/index.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { app } from 'electron'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs/promises'
|
||||
import * as crypto from 'crypto'
|
||||
import axios from 'axios'
|
||||
|
||||
|
||||
export class MusicCacheService {
|
||||
private cacheDir: string
|
||||
private cacheIndex: Map<string, string> = new Map()
|
||||
private indexFilePath: string
|
||||
|
||||
constructor() {
|
||||
this.cacheDir = path.join(app.getPath('userData'), 'music-cache')
|
||||
this.indexFilePath = path.join(this.cacheDir, 'cache-index.json')
|
||||
this.initCache()
|
||||
}
|
||||
|
||||
private async initCache() {
|
||||
try {
|
||||
// 确保缓存目录存在
|
||||
await fs.mkdir(this.cacheDir, { recursive: true })
|
||||
|
||||
// 加载缓存索引
|
||||
await this.loadCacheIndex()
|
||||
} catch (error) {
|
||||
console.error('初始化音乐缓存失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
private async loadCacheIndex() {
|
||||
try {
|
||||
const indexData = await fs.readFile(this.indexFilePath, 'utf-8')
|
||||
const index = JSON.parse(indexData)
|
||||
this.cacheIndex = new Map(Object.entries(index))
|
||||
} catch (error) {
|
||||
// 索引文件不存在或损坏,创建新的
|
||||
this.cacheIndex = new Map()
|
||||
await this.saveCacheIndex()
|
||||
}
|
||||
}
|
||||
|
||||
private async saveCacheIndex() {
|
||||
try {
|
||||
const indexObj = Object.fromEntries(this.cacheIndex)
|
||||
await fs.writeFile(this.indexFilePath, JSON.stringify(indexObj, null, 2))
|
||||
} catch (error) {
|
||||
console.error('保存缓存索引失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
private generateCacheKey(songId: string): string {
|
||||
return crypto.createHash('md5').update(`${songId}`).digest('hex')
|
||||
}
|
||||
|
||||
private getCacheFilePath(cacheKey: string, url: string): string {
|
||||
const ext = path.extname(new URL(url).pathname) || '.mp3'
|
||||
return path.join(this.cacheDir, `${cacheKey}${ext}`)
|
||||
}
|
||||
|
||||
async getCachedMusicUrl(songId: string, originalUrlPromise: Promise<string>): Promise<string> {
|
||||
const cacheKey = this.generateCacheKey(songId)
|
||||
console.log('hash',cacheKey)
|
||||
|
||||
// 检查是否已缓存
|
||||
if (this.cacheIndex.has(cacheKey)) {
|
||||
const cachedFilePath = this.cacheIndex.get(cacheKey)!
|
||||
|
||||
try {
|
||||
// 验证文件是否存在
|
||||
await fs.access(cachedFilePath)
|
||||
console.log(`使用缓存文件: ${cachedFilePath}`)
|
||||
return `file://${cachedFilePath}`
|
||||
} catch (error) {
|
||||
// 文件不存在,从缓存索引中移除
|
||||
this.cacheIndex.delete(cacheKey)
|
||||
await this.saveCacheIndex()
|
||||
}
|
||||
}
|
||||
|
||||
// 下载并缓存文件 先返回源链接不等待结果优化体验
|
||||
this.downloadAndCache(songId, await originalUrlPromise, cacheKey)
|
||||
return await originalUrlPromise
|
||||
}
|
||||
|
||||
private async downloadAndCache(songId: string, url: string, cacheKey: string): Promise<string> {
|
||||
try {
|
||||
console.log(`开始下载歌曲: ${songId}`)
|
||||
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: url,
|
||||
responseType: 'stream',
|
||||
timeout: 30000,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||||
}
|
||||
})
|
||||
|
||||
const cacheFilePath = this.getCacheFilePath(cacheKey, url)
|
||||
const writer = require('fs').createWriteStream(cacheFilePath)
|
||||
|
||||
response.data.pipe(writer)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
writer.on('finish', async () => {
|
||||
try {
|
||||
// 更新缓存索引
|
||||
this.cacheIndex.set(cacheKey, cacheFilePath)
|
||||
await this.saveCacheIndex()
|
||||
|
||||
console.log(`歌曲缓存完成: ${cacheFilePath}`)
|
||||
resolve(`file://${cacheFilePath}`)
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
|
||||
writer.on('error', (error: Error) => {
|
||||
console.error(`下载歌曲失败: ${songId}`, error)
|
||||
// 清理失败的文件
|
||||
fs.unlink(cacheFilePath).catch(() => {})
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(`下载歌曲失败: ${songId}`, error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async clearCache(): Promise<void> {
|
||||
try {
|
||||
// 删除所有缓存文件
|
||||
for (const filePath of this.cacheIndex.values()) {
|
||||
try {
|
||||
await fs.unlink(filePath)
|
||||
} catch (error) {
|
||||
// 忽略文件不存在的错误
|
||||
}
|
||||
}
|
||||
|
||||
// 清空缓存索引
|
||||
this.cacheIndex.clear()
|
||||
await this.saveCacheIndex()
|
||||
|
||||
console.log('音乐缓存已清空')
|
||||
} catch (error) {
|
||||
console.error('清空缓存失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async getCacheSize(): Promise<number> {
|
||||
let totalSize = 0
|
||||
|
||||
for (const filePath of this.cacheIndex.values()) {
|
||||
try {
|
||||
const stats = await fs.stat(filePath)
|
||||
totalSize += stats.size
|
||||
} catch (error) {
|
||||
// 文件不存在,忽略
|
||||
}
|
||||
}
|
||||
|
||||
return totalSize
|
||||
}
|
||||
|
||||
async getCacheInfo(): Promise<{ count: number; size: number; sizeFormatted: string }> {
|
||||
const size = await this.getCacheSize()
|
||||
const count = this.cacheIndex.size
|
||||
|
||||
const formatSize = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B'
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
return {
|
||||
count,
|
||||
size,
|
||||
sizeFormatted: formatSize(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 单例实例
|
||||
export const musicCacheService = new MusicCacheService()
|
||||
@@ -11,12 +11,16 @@ export function request<T extends keyof MainApi>(
|
||||
source: any
|
||||
} & (MethodParams<T> extends object ? MethodParams<T> : { [key: string]: any })
|
||||
): ReturnType<MainApi[T]> {
|
||||
const { source, ...args } = options
|
||||
if (!source) throw new Error('请配置音源')
|
||||
const Api = main(source)
|
||||
if (Api.hasOwnProperty(method)) {
|
||||
return (Api[method] as (args: any) => any)(args)
|
||||
try {
|
||||
const { source, ...args } = options
|
||||
if (!source) throw new Error('请配置音源')
|
||||
const Api = main(source)
|
||||
if (Api.hasOwnProperty(method)) {
|
||||
return (Api[method] as (args: any) => any)(args)
|
||||
}
|
||||
throw new Error(`未知的方法: ${method}`)
|
||||
}catch (error:any){
|
||||
throw new Error(error.message)
|
||||
}
|
||||
throw new Error(`未知的方法: ${method}`)
|
||||
}
|
||||
ipcMain.handle('service-music-sdk-request', request)
|
||||
|
||||
@@ -6,10 +6,23 @@ import {
|
||||
GetLyricArg,
|
||||
PlaylistResult,
|
||||
GetSongListDetailsArg,
|
||||
PlaylistDetailResult
|
||||
PlaylistDetailResult,
|
||||
DownloadSingleSongArgs
|
||||
} from './type'
|
||||
import pluginService from '../plugin/index'
|
||||
import musicSdk from '../../utils/musicSdk/index'
|
||||
import { getAppDirPath } from '../../utils/path'
|
||||
import { musicCacheService } from '../musicCache'
|
||||
import path from 'node:path'
|
||||
import fs from 'fs'
|
||||
import fsPromise from 'fs/promises'
|
||||
import axios from 'axios'
|
||||
import { pipeline } from 'node:stream/promises'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const fileLock: Record<string, boolean> = {}
|
||||
|
||||
|
||||
function main(source: string) {
|
||||
const Api = musicSdk[source]
|
||||
return {
|
||||
@@ -21,7 +34,23 @@ function main(source: string) {
|
||||
try {
|
||||
const usePlugin = pluginService.getPluginById(pluginId)
|
||||
if (!pluginId || !usePlugin) return { error: '请配置音源来播放歌曲' }
|
||||
return await usePlugin.getMusicUrl(source, songInfo, quality)
|
||||
|
||||
// 获取原始URL
|
||||
const originalUrlPromise = usePlugin.getMusicUrl(source, songInfo, quality)
|
||||
|
||||
|
||||
// 生成歌曲唯一标识
|
||||
const songId = `${songInfo.name}-${songInfo.singer}-${source}-${quality}`
|
||||
|
||||
// 尝试获取缓存的URL
|
||||
try {
|
||||
const cachedUrl = await musicCacheService.getCachedMusicUrl(songId, originalUrlPromise)
|
||||
return cachedUrl
|
||||
} catch (cacheError) {
|
||||
console.warn('缓存获取失败,使用原始URL:', cacheError)
|
||||
const originalUrl = await originalUrlPromise
|
||||
return originalUrl
|
||||
}
|
||||
} catch (e: any) {
|
||||
return {
|
||||
error: '获取歌曲失败 ' + e.error || e
|
||||
@@ -56,16 +85,83 @@ function main(source: string) {
|
||||
|
||||
async getPlaylistDetail({ id, page }: GetSongListDetailsArg) {
|
||||
return (await Api.songList.getListDetail(id, page)) as PlaylistDetailResult
|
||||
},
|
||||
|
||||
async downloadSingleSong({ pluginId, songInfo, quality }: DownloadSingleSongArgs) {
|
||||
const url = await this.getMusicUrl({ pluginId, songInfo, quality })
|
||||
if (typeof url === 'object') throw new Error('无法获取歌曲链接')
|
||||
|
||||
// 从URL中提取文件扩展名,如果没有则默认为mp3
|
||||
const getFileExtension = (url: string): string => {
|
||||
try {
|
||||
const urlObj = new URL(url)
|
||||
const pathname = urlObj.pathname
|
||||
const lastDotIndex = pathname.lastIndexOf('.')
|
||||
if (lastDotIndex !== -1 && lastDotIndex < pathname.length - 1) {
|
||||
const extension = pathname.substring(lastDotIndex + 1).toLowerCase()
|
||||
// 验证是否为常见的音频格式
|
||||
const validExtensions = ['mp3', 'flac', 'wav', 'aac', 'm4a', 'ogg', 'wma']
|
||||
if (validExtensions.includes(extension)) {
|
||||
return extension
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('解析URL失败,使用默认扩展名:', error)
|
||||
}
|
||||
return 'mp3' // 默认扩展名
|
||||
}
|
||||
|
||||
const fileExtension = getFileExtension(url)
|
||||
const songPath = path.join(
|
||||
getAppDirPath('music'),
|
||||
'CeruMusic',
|
||||
'songs',
|
||||
`${songInfo.name}-${songInfo.singer}-${source}.${fileExtension}`
|
||||
.replace(/[/\\:*?"<>|]/g, '')
|
||||
.replace(/^\.+/, '')
|
||||
.replace(/\.+$/, '')
|
||||
.trim()
|
||||
)
|
||||
|
||||
if (fileLock[songPath]) {
|
||||
throw new Error('歌曲正在下载中')
|
||||
} else {
|
||||
fileLock[songPath] = true
|
||||
}
|
||||
|
||||
try {
|
||||
if (fs.existsSync(songPath)) {
|
||||
return {
|
||||
message: '歌曲已存在'
|
||||
}
|
||||
}
|
||||
await fsPromise.mkdir(path.dirname(songPath), { recursive: true })
|
||||
|
||||
if (url.startsWith('file://')) {
|
||||
const filePath = fileURLToPath(url)
|
||||
|
||||
const readStream = fs.createReadStream(filePath)
|
||||
const writeStream = fs.createWriteStream(songPath)
|
||||
|
||||
await pipeline(readStream, writeStream)
|
||||
} else {
|
||||
const songDataRes = await axios({
|
||||
method: 'GET',
|
||||
url: url,
|
||||
responseType: 'stream'
|
||||
})
|
||||
|
||||
await pipeline(songDataRes.data, fs.createWriteStream(songPath))
|
||||
}
|
||||
} finally {
|
||||
delete fileLock[songPath]
|
||||
}
|
||||
|
||||
return {
|
||||
message: '下载成功',
|
||||
path: songPath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export default main
|
||||
// musicSdk.wy.songList.handleParseId('https://music.163.com/m/playlist?id=13916216005&creatorId=3359622909',2).then(res=>{
|
||||
// console.log(res)//13916216005
|
||||
// })
|
||||
// main('kg').getHotSonglist().then(res=>{
|
||||
// console.log(res.list[0])//13916216005
|
||||
// })
|
||||
musicSdk.kg.songList.getListDetail('id_8052780', 1).then((res) => {
|
||||
console.log(res) //13916216005
|
||||
})
|
||||
|
||||
@@ -89,3 +89,7 @@ export interface PlaylistDetailResult {
|
||||
source: string
|
||||
info: PlaylistInfo
|
||||
}
|
||||
|
||||
export interface DownloadSingleSongArgs extends GetMusicUrlArg {
|
||||
path?: string
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { getAppDirPath } from '../../utils/path'
|
||||
import { remove_empty_strings } from '../../utils/array'
|
||||
|
||||
function getLogPath(pluginId: string): string {
|
||||
return path.join(getAppDirPath(), 'plugin', 'logs', `${pluginId}.txt`)
|
||||
return path.join(getAppDirPath(), 'plugins', 'logs', `${pluginId}.txt`)
|
||||
}
|
||||
|
||||
const fileLock: Record<string, Promise<any> | undefined> = {}
|
||||
|
||||
@@ -197,7 +197,9 @@ class CeruMusicPluginHost {
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
makeRequest().catch(() => {}) // 错误已在makeRequest中处理
|
||||
makeRequest().catch((error) => {
|
||||
console.error(`[CeruMusic] Unhandled request error in callback mode: ${error.message}`)
|
||||
}) // 确保错误被正确处理
|
||||
return undefined
|
||||
} else {
|
||||
return makeRequest()
|
||||
@@ -407,7 +409,9 @@ class CeruMusicPluginHost {
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
makeRequest().catch(() => {}) // 错误已在makeRequest中处理
|
||||
makeRequest().catch((error) => {
|
||||
console.error(`[CeruMusic] Unhandled request error in callback mode: ${error.message}`)
|
||||
}) // 确保错误被正确处理
|
||||
return undefined
|
||||
} else {
|
||||
return makeRequest()
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
import { decodeName, formatPlayTime, sizeFormate } from '../../index'
|
||||
import { signatureParams, createHttpFetch } from './util'
|
||||
import { formatSingerName } from '../../utils'
|
||||
|
||||
export default {
|
||||
limit: 30,
|
||||
total: 0,
|
||||
page: 0,
|
||||
allPage: 1,
|
||||
musicSearch(str, page, limit) {
|
||||
const sign = signatureParams(
|
||||
`userid=0&area_code=1&appid=1005&dopicfull=1&page=${page}&token=0&privilegefilter=0&requestid=0&pagesize=${limit}&user_labels=&clienttime=0&sec_aggre=1&iscorrection=1&uuid=0&mid=0&keyword=${str}&dfid=-&clientver=11409&platform=AndroidFilter&tag=`,
|
||||
3
|
||||
)
|
||||
return createHttpFetch(
|
||||
`https://gateway.kugou.com/complexsearch/v3/search/song?userid=0&area_code=1&appid=1005&dopicfull=1&page=${page}&token=0&privilegefilter=0&requestid=0&pagesize=${limit}&user_labels=&clienttime=0&sec_aggre=1&iscorrection=1&uuid=0&mid=0&dfid=-&clientver=11409&platform=AndroidFilter&tag=&keyword=${encodeURIComponent(str)}&signature=${sign}`,
|
||||
{
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||
referer: 'https://kugou.com'
|
||||
}
|
||||
}
|
||||
).then((body) => body)
|
||||
},
|
||||
filterList(raw) {
|
||||
let ids = new Set()
|
||||
const list = []
|
||||
|
||||
raw.forEach((item) => {
|
||||
if (ids.has(item.Audioid)) return
|
||||
ids.add(item.Audioid)
|
||||
|
||||
const types = []
|
||||
const _types = {}
|
||||
if (item.FileSize !== 0) {
|
||||
let size = sizeFormate(item.FileSize)
|
||||
types.push({ type: '128k', size, hash: item.FileHash })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
hash: item.FileHash
|
||||
}
|
||||
}
|
||||
if (item.HQ != undefined) {
|
||||
let size = sizeFormate(item.HQ.FileSize)
|
||||
types.push({ type: '320k', size, hash: item.HQ.Hash })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
hash: item.HQ.Hash
|
||||
}
|
||||
}
|
||||
if (item.SQ != undefined) {
|
||||
let size = sizeFormate(item.SQ.FileSize)
|
||||
types.push({ type: 'flac', size, hash: item.SQ.Hash })
|
||||
_types.flac = {
|
||||
size,
|
||||
hash: item.SQ.Hash
|
||||
}
|
||||
}
|
||||
if (item.Res != undefined) {
|
||||
let size = sizeFormate(item.Res.FileSize)
|
||||
types.push({ type: 'flac24bit', size, hash: item.Res.Hash })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
hash: item.Res.Hash
|
||||
}
|
||||
}
|
||||
list.push({
|
||||
singer: decodeName(formatSingerName(item.Singers)),
|
||||
name: decodeName(item.SongName),
|
||||
albumName: decodeName(item.AlbumName),
|
||||
albumId: item.AlbumID,
|
||||
songmid: item.Audioid,
|
||||
source: 'kg',
|
||||
interval: formatPlayTime(item.Duration),
|
||||
_interval: item.Duration,
|
||||
img: null,
|
||||
lrc: null,
|
||||
otherSource: null,
|
||||
hash: item.FileHash,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {}
|
||||
})
|
||||
})
|
||||
|
||||
return list
|
||||
},
|
||||
handleResult(rawData) {
|
||||
const rawList = []
|
||||
rawData.forEach((item) => {
|
||||
rawList.push(item)
|
||||
item.Grp.forEach((e) => rawList.push(e))
|
||||
})
|
||||
|
||||
return this.filterList(rawList)
|
||||
},
|
||||
search(str, page = 1, limit, retryNum = 0) {
|
||||
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
||||
if (limit == null) limit = this.limit
|
||||
|
||||
return this.musicSearch(str, page, limit).then((data) => {
|
||||
let list = this.handleResult(data.lists)
|
||||
if (!list) return this.search(str, page, limit, retryNum)
|
||||
|
||||
this.total = data.total
|
||||
this.page = page
|
||||
this.allPage = Math.ceil(this.total / limit)
|
||||
|
||||
return Promise.resolve({
|
||||
list,
|
||||
allPage: this.allPage,
|
||||
limit,
|
||||
total: this.total,
|
||||
source: 'kg'
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,915 +0,0 @@
|
||||
import { httpFetch } from '../../../request'
|
||||
import { formatSingerName } from '../../utils'
|
||||
import {
|
||||
decodeName,
|
||||
formatPlayTime,
|
||||
sizeFormate,
|
||||
dateFormat,
|
||||
formatPlayCount
|
||||
} from '../../../index'
|
||||
import { signatureParams, createHttpFetch } from './../util'
|
||||
import { getMusicInfosByList } from '../musicInfo'
|
||||
import album from '../album'
|
||||
|
||||
export default {
|
||||
_requestObj_tags: null,
|
||||
_requestObj_listInfo: null,
|
||||
_requestObj_list: null,
|
||||
_requestObj_listRecommend: null,
|
||||
listDetailLimit: 10000,
|
||||
currentTagInfo: {
|
||||
id: undefined,
|
||||
info: undefined
|
||||
},
|
||||
sortList: [
|
||||
{
|
||||
name: '推荐',
|
||||
id: '5'
|
||||
},
|
||||
{
|
||||
name: '最热',
|
||||
id: '6'
|
||||
},
|
||||
{
|
||||
name: '最新',
|
||||
id: '7'
|
||||
},
|
||||
{
|
||||
name: '热藏',
|
||||
id: '3'
|
||||
},
|
||||
{
|
||||
name: '飙升',
|
||||
id: '8'
|
||||
}
|
||||
],
|
||||
cache: new Map(),
|
||||
collectionIdListInfoCache: new Map(),
|
||||
regExps: {
|
||||
listData: /global\.data = (\[.+\]);/,
|
||||
listInfo: /global = {[\s\S]+?name: "(.+)"[\s\S]+?pic: "(.+)"[\s\S]+?};/,
|
||||
// https://www.kugou.com/yy/special/single/1067062.html
|
||||
listDetailLink: /^.+\/(\d+)\.html(?:\?.*|&.*$|#.*$|$)/
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取歌曲列表内的音乐
|
||||
* @param {*} id
|
||||
* @param {*} page
|
||||
*/
|
||||
async getListDetail(id, page) {
|
||||
id = id.toString()
|
||||
|
||||
if (id.includes('special/single/')) id = id.replace(this.regExps.listDetailLink, '$1')
|
||||
// fix https://www.kugou.com/songlist/xxx/?uid=xxx&chl=qq_client&cover=http%3A%2F%2Fimge.kugou.com%xxx.jpg&iszlist=1
|
||||
if (/https?:/.test(id)) {
|
||||
if (id.includes('#')) id = id.replace(/#.*$/, '')
|
||||
if (id.includes('global_collection_id'))
|
||||
return this.getUserListDetailByCollectionId(
|
||||
id.replace(/^.*?global_collection_id=(\w+)(?:&.*$|#.*$|$)/, '$1'),
|
||||
page
|
||||
)
|
||||
if (id.includes('chain='))
|
||||
return this.getUserListDetail3(id.replace(/^.*?chain=(\w+)(?:&.*$|#.*$|$)/, '$1'), page)
|
||||
if (id.includes('.html')) {
|
||||
if (id.includes('zlist.html')) {
|
||||
id = id.replace(/^(.*)zlist\.html/, 'https://m3ws.kugou.com/zlist/list')
|
||||
if (id.includes('pagesize')) {
|
||||
id = id
|
||||
.replace('pagesize=30', 'pagesize=' + this.listDetailLimit)
|
||||
.replace('page=1', 'page=' + page)
|
||||
} else {
|
||||
id += `&pagesize=${this.listDetailLimit}&page=${page}`
|
||||
}
|
||||
} else if (!id.includes('song.html'))
|
||||
return this.getUserListDetail3(
|
||||
id.replace(/.+\/(\w+).html(?:\?.*|&.*$|#.*$|$)/, '$1'),
|
||||
page
|
||||
)
|
||||
}
|
||||
return this.getUserListDetail(id.replace(/^.*?http/, 'http'), page)
|
||||
}
|
||||
if (/^\d+$/.test(id)) return this.getUserListDetailByCode(id, page)
|
||||
if (id.startsWith('gid_'))
|
||||
return this.getUserListDetailByCollectionId(id.replace('gid_', ''), page)
|
||||
if (id.startsWith('id_')) return this.getUserListDetailBySpecialId(id.replace('id_', ''), page)
|
||||
|
||||
return new Error('Failed.')
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取SpecialId歌单
|
||||
* @param {*} id
|
||||
*/
|
||||
async getUserListDetailBySpecialId(id, page, tryNum = 0) {
|
||||
if (tryNum > 2) throw new Error('try max num')
|
||||
|
||||
const { body } = await httpFetch(this.getSongListDetailUrl(id)).promise
|
||||
let listData = body.match(this.regExps.listData)
|
||||
let listInfo = body.match(this.regExps.listInfo)
|
||||
if (!listData) return this.getListDetailBySpecialId(id, page, ++tryNum)
|
||||
let list = await getMusicInfosByList(JSON.parse(listData[1]))
|
||||
let name
|
||||
let pic
|
||||
if (listInfo) {
|
||||
name = listInfo[1]
|
||||
pic = listInfo[2]
|
||||
}
|
||||
let desc = this.parseHtmlDesc(body)
|
||||
|
||||
return {
|
||||
list,
|
||||
page: 1,
|
||||
limit: 10000,
|
||||
total: list.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name,
|
||||
img: pic,
|
||||
desc
|
||||
// author: body.result.info.userinfo.username,
|
||||
// play_count: formatPlayCount(body.result.listen_num),
|
||||
}
|
||||
}
|
||||
},
|
||||
parseHtmlDesc(html) {
|
||||
const prefix = '<div class="pc_specail_text pc_singer_tab_content" id="specailIntroduceWrap">'
|
||||
let index = html.indexOf(prefix)
|
||||
if (index < 0) return null
|
||||
const afterStr = html.substring(index + prefix.length)
|
||||
index = afterStr.indexOf('</div>')
|
||||
if (index < 0) return null
|
||||
return decodeName(afterStr.substring(0, index))
|
||||
},
|
||||
|
||||
/**
|
||||
* 使用SpecialId获取CollectionId
|
||||
* @param {*} specialId
|
||||
*/
|
||||
async getCollectionIdBySpecialId(specialId) {
|
||||
return httpFetch(`http://mobilecdnbj.kugou.com/api/v5/special/info?specialid=${specialId}`, {
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Linux; Android 10; HLK-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Mobile Safari/537.36 EdgA/104.0.1293.70'
|
||||
}
|
||||
}).promise.then(({ body }) => {
|
||||
// console.log('getCollectionIdBySpecialId', body)
|
||||
if (!body.data.global_specialid)
|
||||
return Promise.reject(new Error('Failed to get global collection id.'))
|
||||
return body.data.global_specialid
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取歌单URL
|
||||
* @param {*} sortId
|
||||
* @param {*} tagId
|
||||
* @param {*} page
|
||||
*/
|
||||
getSongListUrl(sortId, tagId, page) {
|
||||
if (tagId == null) tagId = ''
|
||||
return `http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_ajax=1&cdn=cdn&t=${sortId}&c=${tagId}&p=${page}`
|
||||
},
|
||||
getInfoUrl(tagId) {
|
||||
return tagId
|
||||
? `http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_smarty=1&cdn=cdn&t=5&c=${tagId}`
|
||||
: 'http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_smarty=1&'
|
||||
},
|
||||
getSongListDetailUrl(id) {
|
||||
return `http://www2.kugou.kugou.com/yueku/v9/special/single/${id}-5-9999.html`
|
||||
},
|
||||
|
||||
filterInfoHotTag(rawData) {
|
||||
const result = []
|
||||
if (rawData.status !== 1) return result
|
||||
for (const key of Object.keys(rawData.data)) {
|
||||
let tag = rawData.data[key]
|
||||
result.push({
|
||||
id: tag.special_id,
|
||||
name: tag.special_name,
|
||||
source: 'kg'
|
||||
})
|
||||
}
|
||||
return result
|
||||
},
|
||||
|
||||
filterTagInfo(rawData) {
|
||||
const result = []
|
||||
for (const name of Object.keys(rawData)) {
|
||||
result.push({
|
||||
name,
|
||||
list: rawData[name].data.map((tag) => ({
|
||||
parent_id: tag.parent_id,
|
||||
parent_name: tag.pname,
|
||||
id: tag.id,
|
||||
name: tag.name,
|
||||
source: 'kg'
|
||||
}))
|
||||
})
|
||||
}
|
||||
return result
|
||||
},
|
||||
filterSongList(rawData) {
|
||||
return rawData.map((item) => ({
|
||||
play_count: item.total_play_count || formatPlayCount(item.play_count),
|
||||
id: 'id_' + item.specialid,
|
||||
author: item.nickname,
|
||||
name: item.specialname,
|
||||
time: dateFormat(item.publish_time || item.publishtime, 'Y-M-D'),
|
||||
img: item.img || item.imgurl,
|
||||
total: item.songcount,
|
||||
grade: item.grade,
|
||||
desc: item.intro,
|
||||
source: 'kg'
|
||||
}))
|
||||
},
|
||||
|
||||
getSongList(sortId, tagId, page, tryNum = 0) {
|
||||
if (this._requestObj_list) this._requestObj_list.cancelHttp()
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
this._requestObj_list = httpFetch(this.getSongListUrl(sortId, tagId, page))
|
||||
return this._requestObj_list.promise.then(({ body }) => {
|
||||
if (!body || body.status !== 1) return this.getSongList(sortId, tagId, page, ++tryNum)
|
||||
return this.filterSongList(body.special_db)
|
||||
})
|
||||
},
|
||||
getSongListRecommend(tryNum = 0) {
|
||||
if (this._requestObj_listRecommend) this._requestObj_listRecommend.cancelHttp()
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
this._requestObj_listRecommend = httpFetch(
|
||||
'http://everydayrec.service.kugou.com/guess_special_recommend',
|
||||
{
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent': 'KuGou2012-8275-web_browser_event_handler'
|
||||
},
|
||||
body: {
|
||||
appid: 1001,
|
||||
clienttime: 1566798337219,
|
||||
clientver: 8275,
|
||||
key: 'f1f93580115bb106680d2375f8032d96',
|
||||
mid: '21511157a05844bd085308bc76ef3343',
|
||||
platform: 'pc',
|
||||
userid: '262643156',
|
||||
return_min: 6,
|
||||
return_max: 15
|
||||
}
|
||||
}
|
||||
)
|
||||
return this._requestObj_listRecommend.promise.then(({ body }) => {
|
||||
if (body.status !== 1) return this.getSongListRecommend(++tryNum)
|
||||
return this.filterSongList(body.data.special_list)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 通过CollectionId获取歌单详情
|
||||
* @param {*} id
|
||||
*/
|
||||
async getUserListInfoByCollectionId(id) {
|
||||
if (!id || id.length > 1000) return Promise.reject(new Error('get list error'))
|
||||
if (this.collectionIdListInfoCache.has(id)) return this.collectionIdListInfoCache.get(id)
|
||||
|
||||
const params = `appid=1058&specialid=0&global_specialid=${id}&format=jsonp&srcappid=2919&clientver=20000&clienttime=1586163242519&mid=1586163242519&uuid=1586163242519&dfid=-`
|
||||
return createHttpFetch(
|
||||
`https://mobiles.kugou.com/api/v5/special/info_v2?${params}&signature=${signatureParams(params, 'web')}`,
|
||||
{
|
||||
headers: {
|
||||
mid: '1586163242519',
|
||||
Referer: 'https://m3ws.kugou.com/share/index.php',
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
|
||||
dfid: '-',
|
||||
clienttime: '1586163242519'
|
||||
}
|
||||
}
|
||||
).then((body) => {
|
||||
let info = {
|
||||
type: body.type,
|
||||
userName: body.nickname,
|
||||
userAvatar: body.user_avatar,
|
||||
imageUrl: body.imgurl,
|
||||
desc: body.intro,
|
||||
name: body.specialname,
|
||||
globalSpecialid: body.global_specialid,
|
||||
total: body.songcount,
|
||||
playCount: body.playcount
|
||||
}
|
||||
|
||||
this.collectionIdListInfoCache.set(id, info)
|
||||
return info
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 通过SpecialId获取歌单
|
||||
* @param {*} id
|
||||
*/
|
||||
// async getUserListDetailBySpecialId(id, page = 1, limit = 300) {
|
||||
// if (!id || id.length > 1000) return Promise.reject(new Error('get list error.'))
|
||||
// const listInfo = await this.getListInfoBySpecialId(id)
|
||||
|
||||
// const params = `specialid=${id}&need_sort=1&module=CloudMusic&clientver=11589&pagesize=${limit}&userid=0&page=${page}&type=0&area_code=1&appid=1005`
|
||||
// return createHttpFetch(`http://pubsongs.kugou.com/v2/get_other_list_file?${params}&signature=${signatureParams(params, 2)}`, {
|
||||
// headers: {
|
||||
// 'User-Agent': 'Android10-AndroidPhone-11589-201-0-playlist-wifi',
|
||||
// },
|
||||
// }).then(body => {
|
||||
// if (!body.info) return Promise.reject(new Error('Get list failed.'))
|
||||
// const songList = this.filterListByCollectionId(body.info)
|
||||
|
||||
// return {
|
||||
// list: songList || [],
|
||||
// page,
|
||||
// limit,
|
||||
// total: body.count,
|
||||
// source: 'kg',
|
||||
// info: {
|
||||
// name: listInfo.name,
|
||||
// img: listInfo.image,
|
||||
// desc: listInfo.desc,
|
||||
// // author: listInfo.userName,
|
||||
// // play_count: formatPlayCount(listInfo.playCount),
|
||||
// },
|
||||
// }
|
||||
// })
|
||||
// },
|
||||
/**
|
||||
* 通过CollectionId获取歌单
|
||||
* @param {*} id
|
||||
*/
|
||||
async getUserListDetailByCollectionId(id, page = 1, limit = 300) {
|
||||
if (!id || id.length > 1000) return Promise.reject(new Error('ID error.'))
|
||||
const listInfo = await this.getUserListInfoByCollectionId(id)
|
||||
|
||||
const params = `need_sort=1&module=CloudMusic&clientver=11589&pagesize=${limit}&global_collection_id=${id}&userid=0&page=${page}&type=0&area_code=1&appid=1005`
|
||||
return createHttpFetch(
|
||||
`http://pubsongs.kugou.com/v2/get_other_list_file?${params}&signature=${signatureParams(params, 'android')}`,
|
||||
{
|
||||
headers: {
|
||||
'User-Agent': 'Android10-AndroidPhone-11589-201-0-playlist-wifi'
|
||||
}
|
||||
}
|
||||
).then((body) => {
|
||||
if (!body.info) return Promise.reject(new Error('Get list failed.'))
|
||||
const songList = this.filterListByCollectionId(body.info)
|
||||
|
||||
return {
|
||||
list: songList || [],
|
||||
page,
|
||||
limit,
|
||||
total: listInfo.total,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.name,
|
||||
img: listInfo.imageUrl && listInfo.imageUrl.replace('{size}', 240),
|
||||
desc: listInfo.desc,
|
||||
author: listInfo.userName,
|
||||
play_count: formatPlayCount(listInfo.playCount)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 过滤GlobalSpecialId歌单数据
|
||||
* @param {*} rawData
|
||||
*/
|
||||
filterListByCollectionId(rawData) {
|
||||
let ids = new Set()
|
||||
let list = []
|
||||
rawData.forEach((item) => {
|
||||
if (!item) return
|
||||
if (ids.has(item.hash)) return
|
||||
ids.add(item.hash)
|
||||
const types = []
|
||||
const _types = {}
|
||||
|
||||
item.relate_goods.forEach((data) => {
|
||||
let size = sizeFormate(data.size)
|
||||
switch (data.level) {
|
||||
case 2:
|
||||
types.push({ type: '128k', size, hash: data.hash })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
hash: data.hash
|
||||
}
|
||||
break
|
||||
case 4:
|
||||
types.push({ type: '320k', size, hash: data.hash })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
hash: data.hash
|
||||
}
|
||||
break
|
||||
case 5:
|
||||
types.push({ type: 'flac', size, hash: data.hash })
|
||||
_types.flac = {
|
||||
size,
|
||||
hash: data.hash
|
||||
}
|
||||
break
|
||||
case 6:
|
||||
types.push({ type: 'flac24bit', size, hash: data.hash })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
hash: data.hash
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
list.push({
|
||||
singer:
|
||||
formatSingerName(item.singerinfo, 'name') ||
|
||||
decodeName(item.name).split(' - ')[0].replace(/&/g, '、'),
|
||||
name: decodeName(item.name).split(' - ')[1],
|
||||
albumName: decodeName(item.albuminfo.name),
|
||||
albumId: item.albuminfo.id,
|
||||
songmid: item.audio_id,
|
||||
source: 'kg',
|
||||
interval: formatPlayTime(parseInt(item.timelen) / 1000),
|
||||
img: null,
|
||||
lrc: null,
|
||||
hash: item.hash,
|
||||
otherSource: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {}
|
||||
})
|
||||
})
|
||||
return list
|
||||
},
|
||||
/**
|
||||
* 通过酷狗码获取歌单
|
||||
* @param {*} id
|
||||
* @param {*} page
|
||||
*/
|
||||
async getUserListDetailByCode(id, page = 1) {
|
||||
// type 1单曲,2歌单,3电台,4酷狗码,5别人的播放队列
|
||||
const codeData = await createHttpFetch('http://t.kugou.com/command/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'KG-RC': 1,
|
||||
'KG-THash': 'network_super_call.cpp:3676261689:379',
|
||||
'User-Agent': ''
|
||||
},
|
||||
body: {
|
||||
appid: 1001,
|
||||
clientver: 9020,
|
||||
mid: '21511157a05844bd085308bc76ef3343',
|
||||
clienttime: 640612895,
|
||||
key: '36164c4015e704673c588ee202b9ecb8',
|
||||
data: id
|
||||
}
|
||||
})
|
||||
if (!codeData) return Promise.reject(new Error('Get list failed.'))
|
||||
const codeInfo = codeData.info
|
||||
|
||||
switch (codeInfo.type) {
|
||||
case 2:
|
||||
if (!codeInfo.global_collection_id)
|
||||
return this.getUserListDetailBySpecialId(codeInfo.id, page)
|
||||
break
|
||||
case 3:
|
||||
return album.getAlbumDetail(codeInfo.id, page)
|
||||
}
|
||||
if (codeInfo.global_collection_id)
|
||||
return this.getUserListDetailByCollectionId(codeInfo.global_collection_id, page)
|
||||
|
||||
if (codeInfo.userid != null) {
|
||||
const songList = await createHttpFetch(
|
||||
'http://www2.kugou.kugou.com/apps/kucodeAndShare/app/',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'KG-RC': 1,
|
||||
'KG-THash': 'network_super_call.cpp:3676261689:379',
|
||||
'User-Agent': ''
|
||||
},
|
||||
body: {
|
||||
appid: 1001,
|
||||
clientver: 9020,
|
||||
mid: '21511157a05844bd085308bc76ef3343',
|
||||
clienttime: 640612895,
|
||||
key: '36164c4015e704673c588ee202b9ecb8',
|
||||
data: {
|
||||
id: codeInfo.id,
|
||||
type: 3,
|
||||
userid: codeInfo.userid,
|
||||
collect_type: 0,
|
||||
page: 1,
|
||||
pagesize: codeInfo.count
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// console.log(songList)
|
||||
let list = await getMusicInfosByList(songList || codeInfo.list)
|
||||
return {
|
||||
list,
|
||||
page: 1,
|
||||
limit: codeInfo.count,
|
||||
total: list.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: codeInfo.name,
|
||||
img: (codeInfo.img_size && codeInfo.img_size.replace('{size}', 240)) || codeInfo.img,
|
||||
// desc: body.result.info.list_desc,
|
||||
author: codeInfo.username
|
||||
// play_count: formatPlayCount(info.count),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async getUserListDetail3(chain, page) {
|
||||
const songInfo = await createHttpFetch(
|
||||
`http://m.kugou.com/schain/transfer?pagesize=${this.listDetailLimit}&chain=${chain}&su=1&page=${page}&n=0.7928855356604456`,
|
||||
{
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1'
|
||||
}
|
||||
}
|
||||
)
|
||||
if (!songInfo.list) {
|
||||
if (songInfo.global_collection_id)
|
||||
return this.getUserListDetailByCollectionId(songInfo.global_collection_id, page)
|
||||
else
|
||||
return this.getUserListDetail4(songInfo, chain, page).catch(() =>
|
||||
this.getUserListDetail5(chain)
|
||||
)
|
||||
}
|
||||
let list = await getMusicInfosByList(songInfo.list)
|
||||
// console.log(info, songInfo)
|
||||
return {
|
||||
list,
|
||||
page: 1,
|
||||
limit: this.listDetailLimit,
|
||||
total: list.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: songInfo.info.name,
|
||||
img: songInfo.info.img,
|
||||
// desc: body.result.info.list_desc,
|
||||
author: songInfo.info.username
|
||||
// play_count: formatPlayCount(info.count),
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async getUserListDetailByLink({ info }, link) {
|
||||
let listInfo = info['0']
|
||||
let total = listInfo.count
|
||||
let tasks = []
|
||||
let page = 0
|
||||
while (total) {
|
||||
const limit = total > 90 ? 90 : total
|
||||
total -= limit
|
||||
page += 1
|
||||
tasks.push(
|
||||
createHttpFetch(
|
||||
link.replace(/pagesize=\d+/, 'pagesize=' + limit).replace(/page=\d+/, 'page=' + page),
|
||||
{
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||
Referer: link
|
||||
}
|
||||
}
|
||||
).then((data) => data.list.info)
|
||||
)
|
||||
}
|
||||
let result = await Promise.all(tasks).then(([...datas]) => datas.flat())
|
||||
result = await getMusicInfosByList(result)
|
||||
// console.log(result)
|
||||
return {
|
||||
list: result,
|
||||
page,
|
||||
limit: this.listDetailLimit,
|
||||
total: result.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.name,
|
||||
img: listInfo.pic && listInfo.pic.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
author: listInfo.list_create_username
|
||||
// play_count: formatPlayCount(listInfo.count),
|
||||
}
|
||||
}
|
||||
},
|
||||
createGetListDetail2Task(id, total) {
|
||||
let tasks = []
|
||||
let page = 0
|
||||
while (total) {
|
||||
const limit = total > 300 ? 300 : total
|
||||
total -= limit
|
||||
page += 1
|
||||
const params =
|
||||
'appid=1058&global_specialid=' +
|
||||
id +
|
||||
'&specialid=0&plat=0&version=8000&page=' +
|
||||
page +
|
||||
'&pagesize=' +
|
||||
limit +
|
||||
'&srcappid=2919&clientver=20000&clienttime=1586163263991&mid=1586163263991&uuid=1586163263991&dfid=-'
|
||||
tasks.push(
|
||||
createHttpFetch(
|
||||
`https://mobiles.kugou.com/api/v5/special/song_v2?${params}&signature=${signatureParams(params, 'web')}`,
|
||||
{
|
||||
headers: {
|
||||
mid: '1586163263991',
|
||||
Referer: 'https://m3ws.kugou.com/share/index.php',
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
|
||||
dfid: '-',
|
||||
clienttime: '1586163263991'
|
||||
}
|
||||
}
|
||||
).then((data) => data.info)
|
||||
)
|
||||
}
|
||||
return Promise.all(tasks).then(([...datas]) => datas.flat())
|
||||
},
|
||||
async getUserListDetail2(global_collection_id) {
|
||||
let id = global_collection_id
|
||||
if (id.length > 1000) throw new Error('get list error')
|
||||
const params =
|
||||
'appid=1058&specialid=0&global_specialid=' +
|
||||
id +
|
||||
'&format=jsonp&srcappid=2919&clientver=20000&clienttime=1586163242519&mid=1586163242519&uuid=1586163242519&dfid=-'
|
||||
let info = await createHttpFetch(
|
||||
`https://mobiles.kugou.com/api/v5/special/info_v2?${params}&signature=${signatureParams(params, 'web')}`,
|
||||
{
|
||||
headers: {
|
||||
mid: '1586163242519',
|
||||
Referer: 'https://m3ws.kugou.com/share/index.php',
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
|
||||
dfid: '-',
|
||||
clienttime: '1586163242519'
|
||||
}
|
||||
}
|
||||
)
|
||||
const songInfo = await this.createGetListDetail2Task(id, info.songcount)
|
||||
let list = await getMusicInfosByList(songInfo)
|
||||
// console.log(info, songInfo, list)
|
||||
return {
|
||||
list,
|
||||
page: 1,
|
||||
limit: this.listDetailLimit,
|
||||
total: list.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: info.specialname,
|
||||
img: info.imgurl && info.imgurl.replace('{size}', 240),
|
||||
desc: info.intro,
|
||||
author: info.nickname,
|
||||
play_count: formatPlayCount(info.playcount)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async getListInfoByChain(chain) {
|
||||
if (this.cache.has(chain)) return this.cache.get(chain)
|
||||
const { body } = await httpFetch(`https://m.kugou.com/share/?chain=${chain}&id=${chain}`, {
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'
|
||||
}
|
||||
}).promise
|
||||
// console.log(body)
|
||||
let result = body.match(/var\sphpParam\s=\s({.+?});/)
|
||||
if (result) result = JSON.parse(result[1])
|
||||
this.cache.set(chain, result)
|
||||
return result
|
||||
},
|
||||
|
||||
async getUserListDetailByPcChain(chain) {
|
||||
let key = `${chain}_pc_list`
|
||||
if (this.cache.has(key)) return this.cache.get(key)
|
||||
const { body } = await httpFetch(`http://www.kugou.com/share/${chain}.html`, {
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
|
||||
}
|
||||
}).promise
|
||||
let result = body.match(/var\sdataFromSmarty\s=\s(\[.+?\])/)
|
||||
if (result) result = JSON.parse(result[1])
|
||||
this.cache.set(chain, result)
|
||||
result = await getMusicInfosByList(result)
|
||||
// console.log(info, songInfo)
|
||||
return result
|
||||
},
|
||||
|
||||
async getUserListDetail4(songInfo, chain, page) {
|
||||
const limit = 100
|
||||
const [listInfo, list] = await Promise.all([
|
||||
this.getListInfoByChain(chain),
|
||||
this.getUserListDetailBySpecialId(songInfo.id, page, limit)
|
||||
])
|
||||
return {
|
||||
list: list || [],
|
||||
page,
|
||||
limit,
|
||||
total: list.length ?? 0,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.specialname,
|
||||
img: listInfo.imgurl && listInfo.imgurl.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
author: listInfo.nickname
|
||||
// play_count: formatPlayCount(info.count),
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async getUserListDetail5(chain) {
|
||||
const [listInfo, list] = await Promise.all([
|
||||
this.getListInfoByChain(chain),
|
||||
this.getUserListDetailByPcChain(chain)
|
||||
])
|
||||
return {
|
||||
list: list || [],
|
||||
page: 1,
|
||||
limit: this.listDetailLimit,
|
||||
total: list.length ?? 0,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.specialname,
|
||||
img: listInfo.imgurl && listInfo.imgurl.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
author: listInfo.nickname
|
||||
// play_count: formatPlayCount(info.count),
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async getUserListDetail(link, page, retryNum = 0) {
|
||||
if (retryNum > 3) return Promise.reject(new Error('link try max num'))
|
||||
|
||||
const requestLink = httpFetch(link, {
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||
Referer: link
|
||||
},
|
||||
follow_max: 2
|
||||
})
|
||||
const {
|
||||
headers: { location },
|
||||
statusCode,
|
||||
body
|
||||
} = await requestLink.promise
|
||||
// console.log(body, location, statusCode)
|
||||
if (statusCode > 400) return this.getUserListDetail(link, page, ++retryNum)
|
||||
if (typeof body == 'string') {
|
||||
if (body.includes('"global_collection_id":'))
|
||||
return this.getUserListDetailByCollectionId(
|
||||
body.replace(/^[\s\S]+?"global_collection_id":"(\w+)"[\s\S]+?$/, '$1'),
|
||||
page
|
||||
)
|
||||
if (body.includes('"albumid":'))
|
||||
return album.getAlbumDetail(body.replace(/^[\s\S]+?"albumid":(\w+)[\s\S]+?$/, '$1'), page)
|
||||
if (body.includes('"album_id":') && link.includes('album/info'))
|
||||
return album.getAlbumDetail(body.replace(/^[\s\S]+?"album_id":(\w+)[\s\S]+?$/, '$1'), page)
|
||||
if (body.includes('list_id = "') && link.includes('album/info'))
|
||||
return album.getAlbumDetail(body.replace(/^[\s\S]+?list_id = "(\w+)"[\s\S]+?$/, '$1'), page)
|
||||
}
|
||||
if (location) {
|
||||
// 概念版分享链接 https://t1.kugou.com/xxx
|
||||
if (location.includes('global_specialid'))
|
||||
return this.getUserListDetailByCollectionId(
|
||||
location.replace(/^.*?global_specialid=(\w+)(?:&.*$|#.*$|$)/, '$1'),
|
||||
page
|
||||
)
|
||||
if (location.includes('global_collection_id'))
|
||||
return this.getUserListDetailByCollectionId(
|
||||
location.replace(/^.*?global_collection_id=(\w+)(?:&.*$|#.*$|$)/, '$1'),
|
||||
page
|
||||
)
|
||||
if (location.includes('chain='))
|
||||
return this.getUserListDetail3(
|
||||
location.replace(/^.*?chain=(\w+)(?:&.*$|#.*$|$)/, '$1'),
|
||||
page
|
||||
)
|
||||
if (location.includes('.html')) {
|
||||
if (location.includes('zlist.html')) {
|
||||
let link = location.replace(/^(.*)zlist\.html/, 'https://m3ws.kugou.com/zlist/list')
|
||||
if (link.includes('pagesize')) {
|
||||
link = link
|
||||
.replace('pagesize=30', 'pagesize=' + this.listDetailLimit)
|
||||
.replace('page=1', 'page=' + page)
|
||||
} else {
|
||||
link += `&pagesize=${this.listDetailLimit}&page=${page}`
|
||||
}
|
||||
return this.getUserListDetail(link, page, ++retryNum)
|
||||
} else
|
||||
return this.getUserListDetail3(
|
||||
location.replace(/.+\/(\w+).html(?:\?.*|&.*$|#.*$|$)/, '$1'),
|
||||
page
|
||||
)
|
||||
}
|
||||
return this.getUserListDetail(location, page, ++retryNum)
|
||||
}
|
||||
if (body.errcode !== 0) return this.getUserListDetail(link, page, ++retryNum)
|
||||
return this.getUserListDetailByLink(body, link)
|
||||
},
|
||||
|
||||
// 获取列表信息
|
||||
getListInfo(tagId, tryNum = 0) {
|
||||
if (this._requestObj_listInfo) this._requestObj_listInfo.cancelHttp()
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
this._requestObj_listInfo = httpFetch(this.getInfoUrl(tagId))
|
||||
return this._requestObj_listInfo.promise.then(({ body }) => {
|
||||
if (body.status !== 1) return this.getListInfo(tagId, ++tryNum)
|
||||
return {
|
||||
limit: body.data.params.pagesize,
|
||||
page: body.data.params.p,
|
||||
total: body.data.params.total,
|
||||
source: 'kg'
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 获取列表数据
|
||||
getList(sortId, tagId, page) {
|
||||
let tasks = [this.getSongList(sortId, tagId, page)]
|
||||
tasks.push(
|
||||
this.currentTagInfo.id === tagId
|
||||
? Promise.resolve(this.currentTagInfo.info)
|
||||
: this.getListInfo(tagId).then((info) => {
|
||||
this.currentTagInfo.id = tagId
|
||||
this.currentTagInfo.info = Object.assign({}, info)
|
||||
return info
|
||||
})
|
||||
)
|
||||
if (!tagId && page === 1 && sortId === this.sortList[0].id)
|
||||
tasks.push(this.getSongListRecommend()) // 如果是所有类别,则顺便获取推荐列表
|
||||
return Promise.all(tasks).then(([list, info, recommendList]) => {
|
||||
if (recommendList) list.unshift(...recommendList)
|
||||
return {
|
||||
list,
|
||||
...info
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 获取标签
|
||||
getTags(tryNum = 0) {
|
||||
if (this._requestObj_tags) this._requestObj_tags.cancelHttp()
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
this._requestObj_tags = httpFetch(this.getInfoUrl())
|
||||
return this._requestObj_tags.promise.then(({ body }) => {
|
||||
if (body.status !== 1) return this.getTags(++tryNum)
|
||||
return {
|
||||
hotTag: this.filterInfoHotTag(body.data.hotTag),
|
||||
tags: this.filterTagInfo(body.data.tagids),
|
||||
source: 'kg'
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getDetailPageUrl(id) {
|
||||
if (typeof id == 'string') {
|
||||
if (/^https?:\/\//.test(id)) return id
|
||||
id = id.replace('id_', '')
|
||||
}
|
||||
return `https://www.kugou.com/yy/special/single/${id}.html`
|
||||
},
|
||||
|
||||
search(text, page, limit = 20) {
|
||||
const params = `userid=1384394652&req_custom=1&appid=1005&req_multi=1&version=11589&page=${page}&filter=0&pagesize=${limit}&order=0&clienttime=1681779443&iscorrection=1&searchsong=0&keyword=${text}&mid=288799920684148686226285199951543865551&dfid=3eSBsO1u97EY1zeIZd40hH4p&clientver=11589&platform=AndroidFilter`
|
||||
const url = encodeURI(
|
||||
`http://complexsearchretry.kugou.com/v1/search/special?${params}&signature=${signatureParams(params, 'android')}`
|
||||
)
|
||||
return createHttpFetch(url).then((body) => {
|
||||
// console.log(body)
|
||||
return {
|
||||
list: body.lists.map((item) => {
|
||||
return {
|
||||
play_count: formatPlayCount(item.total_play_count),
|
||||
id: item.gid ? `gid_${item.gid}` : `id_${item.specialid}`,
|
||||
author: item.nickname,
|
||||
name: item.specialname,
|
||||
time: dateFormat(item.publish_time, 'Y-M-D'),
|
||||
img: item.img,
|
||||
grade: item.grade,
|
||||
desc: item.intro,
|
||||
total: item.song_count,
|
||||
source: 'kg'
|
||||
}
|
||||
}),
|
||||
limit,
|
||||
total: body.total,
|
||||
source: 'kg'
|
||||
}
|
||||
})
|
||||
// http://msearchretry.kugou.com/api/v3/search/special?version=9209&keyword=%E5%91%A8%E6%9D%B0%E4%BC%A6&pagesize=20&filter=0&page=1&sver=2&with_res_tag=0
|
||||
// http://ioscdn.kugou.com/api/v3/search/special?keyword=${encodeURIComponent(text)}&page=${page}&pagesize=${limit}&showtype=10&plat=2&version=7910&correct=1&sver=5
|
||||
// http://msearchretry.kugou.com/api/v3/search/special?keyword=${encodeURIComponent(text)}&page=${page}&pagesize=${limit}&showtype=10&filter=0&version=7910&sver=2
|
||||
}
|
||||
}
|
||||
|
||||
// getList
|
||||
// getTags
|
||||
// getListDetail
|
||||
@@ -65,7 +65,6 @@ export const matchToken = (headers) => {
|
||||
// })
|
||||
// })
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export const decodeLyric = (options) => {
|
||||
if (typeof options === 'string') {
|
||||
// 兼容旧的调用方式
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
import { httpFetch } from '../../../request'
|
||||
import { formatPlayTime } from '../../../index'
|
||||
// import { sizeFormate } from '../../index'
|
||||
|
||||
// const boardList = [{ id: 'mg__27553319', name: '咪咕尖叫新歌榜', bangid: '27553319' }, { id: 'mg__27186466', name: '咪咕尖叫热歌榜', bangid: '27186466' }, { id: 'mg__27553408', name: '咪咕尖叫原创榜', bangid: '27553408' }, { id: 'mg__23189800', name: '咪咕港台榜', bangid: '23189800' }, { id: 'mg__23189399', name: '咪咕内地榜', bangid: '23189399' }, { id: 'mg__19190036', name: '咪咕欧美榜', bangid: '19190036' }, { id: 'mg__23189813', name: '咪咕日韩榜', bangid: '23189813' }, { id: 'mg__23190126', name: '咪咕彩铃榜', bangid: '23190126' }, { id: 'mg__15140045', name: '咪咕KTV榜', bangid: '15140045' }, { id: 'mg__15140034', name: '咪咕网络榜', bangid: '15140034' }, { id: 'mg__23217754', name: 'MV榜', bangid: '23217754' }, { id: 'mg__23218151', name: '新专辑榜', bangid: '23218151' }, { id: 'mg__21958042', name: 'iTunes榜', bangid: '21958042' }, { id: 'mg__21975570', name: 'billboard榜', bangid: '21975570' }, { id: 'mg__22272815', name: '台湾Hito中文榜', bangid: '22272815' }, { id: 'mg__22272904', name: '中国TOP排行榜', bangid: '22272904' }, { id: 'mg__22272943', name: '韩国Melon榜', bangid: '22272943' }, { id: 'mg__22273437', name: '英国UK榜', bangid: '22273437' }]
|
||||
const boardList = [
|
||||
{ id: 'mg__27553319', name: '尖叫新歌榜', bangid: '27553319', webId: 'jianjiao_newsong' },
|
||||
{ id: 'mg__27186466', name: '尖叫热歌榜', bangid: '27186466', webId: 'jianjiao_hotsong' },
|
||||
{ id: 'mg__27553408', name: '尖叫原创榜', bangid: '27553408', webId: 'jianjiao_original' },
|
||||
{ id: 'mg__migumusic', name: '音乐榜', bangid: 'migumusic', webId: 'migumusic' },
|
||||
{ id: 'mg__movies', name: '影视榜', bangid: 'movies', webId: 'movies' },
|
||||
{ id: 'mg__23189800', name: '港台榜', bangid: '23189800', webId: 'hktw' },
|
||||
{ id: 'mg__23189399', name: '内地榜', bangid: '23189399', webId: 'mainland' },
|
||||
{ id: 'mg__19190036', name: '欧美榜', bangid: '19190036', webId: 'eur_usa' },
|
||||
{ id: 'mg__23189813', name: '日韩榜', bangid: '23189813', webId: 'jpn_kor' },
|
||||
{ id: 'mg__23190126', name: '彩铃榜', bangid: '23190126', webId: 'coloring' },
|
||||
{ id: 'mg__15140045', name: 'KTV榜', bangid: '15140045', webId: 'ktv' },
|
||||
{ id: 'mg__15140034', name: '网络榜', bangid: '15140034', webId: 'network' },
|
||||
{ id: 'mg__23217754', name: 'MV榜', bangid: '23217754', webId: 'mv' },
|
||||
{ id: 'mg__23218151', name: '新专辑榜', bangid: '23218151', webId: 'newalbum' },
|
||||
{ id: 'mg__21958042', name: '美国iTunes榜', bangid: '21958042', webId: 'itunes' },
|
||||
{ id: 'mg__21975570', name: '美国billboard榜', bangid: '21975570', webId: 'billboard' },
|
||||
{ id: 'mg__22272815', name: '台湾Hito中文榜', bangid: '22272815', webId: 'hito' },
|
||||
{ id: 'mg__22272904', name: '中国TOP排行榜', bangid: '22272904' },
|
||||
{ id: 'mg__22272943', name: '韩国Melon榜', bangid: '22272943', webId: 'mnet' },
|
||||
{ id: 'mg__22273437', name: '英国UK榜', bangid: '22273437', webId: 'uk' }
|
||||
]
|
||||
// const boardList = [
|
||||
// { id: 'mg__jianjiao_newsong', bangid: 'jianjiao_newsong', name: '尖叫新歌榜' },
|
||||
// { id: 'mg__jianjiao_hotsong', bangid: 'jianjiao_hotsong', name: '尖叫热歌榜' },
|
||||
// { id: 'mg__jianjiao_original', bangid: 'jianjiao_original', name: '尖叫原创榜' },
|
||||
// { id: 'mg__migumusic', bangid: 'migumusic', name: '音乐榜' },
|
||||
// { id: 'mg__movies', bangid: 'movies', name: '影视榜' },
|
||||
// { id: 'mg__mainland', bangid: 'mainland', name: '内地榜' },
|
||||
// { id: 'mg__hktw', bangid: 'hktw', name: '港台榜' },
|
||||
// { id: 'mg__eur_usa', bangid: 'eur_usa', name: '欧美榜' },
|
||||
// { id: 'mg__jpn_kor', bangid: 'jpn_kor', name: '日韩榜' },
|
||||
// { id: 'mg__coloring', bangid: 'coloring', name: '彩铃榜' },
|
||||
// { id: 'mg__ktv', bangid: 'ktv', name: 'KTV榜' },
|
||||
// { id: 'mg__network', bangid: 'network', name: '网络榜' },
|
||||
// { id: 'mg__newalbum', bangid: 'newalbum', name: '新专辑榜' },
|
||||
// { id: 'mg__mv', bangid: 'mv', name: 'MV榜' },
|
||||
// { id: 'mg__itunes', bangid: 'itunes', name: '美国iTunes榜' },
|
||||
// { id: 'mg__billboard', bangid: 'billboard', name: '美国billboard榜' },
|
||||
// { id: 'mg__hito', bangid: 'hito', name: 'Hito中文榜' },
|
||||
// { id: 'mg__mnet', bangid: 'mnet', name: '韩国Melon榜' },
|
||||
// { id: 'mg__uk', bangid: 'uk', name: '英国UK榜' },
|
||||
// ]
|
||||
|
||||
export default {
|
||||
limit: 10000,
|
||||
getUrl(id, page) {
|
||||
const targetBoard = boardList.find((board) => board.bangid == id)
|
||||
return `https://music.migu.cn/v3/music/top/${targetBoard.webId}`
|
||||
// return `http://m.music.migu.cn/migu/remoting/cms_list_tag?nid=${id}&pageSize=${this.limit}&pageNo=${page - 1}`
|
||||
},
|
||||
successCode: '000000',
|
||||
requestBoardsObj: null,
|
||||
regExps: {
|
||||
listData: /var listData = (\{.+\})<\/script>/
|
||||
},
|
||||
getData(url) {
|
||||
const requestObj = httpFetch(url)
|
||||
return requestObj.promise
|
||||
},
|
||||
getSinger(singers) {
|
||||
let arr = []
|
||||
singers.forEach((singer) => {
|
||||
arr.push(singer.name)
|
||||
})
|
||||
return arr.join('、')
|
||||
},
|
||||
getIntv(interval) {
|
||||
if (!interval) return 0
|
||||
let intvArr = interval.split(':')
|
||||
let intv = 0
|
||||
let unit = 1
|
||||
while (intvArr.length) {
|
||||
intv += intvArr.pop() * unit
|
||||
unit *= 60
|
||||
}
|
||||
return parseInt(intv)
|
||||
},
|
||||
formateIntv() {},
|
||||
filterData(rawData) {
|
||||
// console.log(JSON.stringify(rawData))
|
||||
// console.log(rawData)
|
||||
let ids = new Set()
|
||||
const list = []
|
||||
rawData.forEach((item) => {
|
||||
if (ids.has(item.copyrightId)) return
|
||||
ids.add(item.copyrightId)
|
||||
|
||||
const types = []
|
||||
const _types = {}
|
||||
|
||||
const size = null
|
||||
types.push({ type: '128k', size })
|
||||
_types['128k'] = { size }
|
||||
|
||||
if (item.hq) {
|
||||
const size = null
|
||||
types.push({ type: '320k', size })
|
||||
_types['320k'] = { size }
|
||||
}
|
||||
if (item.sq) {
|
||||
const size = null
|
||||
types.push({ type: 'flac', size })
|
||||
_types.flac = { size }
|
||||
}
|
||||
|
||||
list.push({
|
||||
singer: this.getSinger(item.singers),
|
||||
name: item.name,
|
||||
albumName: item.album && item.album.albumName,
|
||||
albumId: item.album && item.album.albumId,
|
||||
songmid: item.id,
|
||||
copyrightId: item.copyrightId,
|
||||
source: 'mg',
|
||||
interval: item.duration ? formatPlayTime(this.getIntv(item.duration)) : null,
|
||||
img: item.mediumPic ? `https:${item.mediumPic}` : null,
|
||||
lrc: null,
|
||||
// lrcUrl: item.lrcUrl,
|
||||
otherSource: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {}
|
||||
})
|
||||
})
|
||||
return list
|
||||
},
|
||||
filterBoardsData(rawList) {
|
||||
// console.log(rawList)
|
||||
let list = []
|
||||
for (const board of rawList) {
|
||||
if (board.template != 'group1') continue
|
||||
for (const item of board.itemList) {
|
||||
if (
|
||||
(item.template != 'row1' && item.template != 'grid1' && !item.actionUrl) ||
|
||||
!item.actionUrl.includes('rank-info')
|
||||
)
|
||||
continue
|
||||
|
||||
let data = item.displayLogId.param
|
||||
list.push({
|
||||
id: 'mg__' + data.rankId,
|
||||
name: data.rankName,
|
||||
bangid: String(data.rankId)
|
||||
})
|
||||
}
|
||||
}
|
||||
return list
|
||||
},
|
||||
async getBoards(retryNum = 0) {
|
||||
// if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
||||
// let response
|
||||
// try {
|
||||
// response = await this.getBoardsData()
|
||||
// } catch (error) {
|
||||
// return this.getBoards(retryNum)
|
||||
// }
|
||||
// // console.log(response.body.data.contentItemList)
|
||||
// if (response.statusCode !== 200 || response.body.code !== this.successCode) return this.getBoards(retryNum)
|
||||
// const list = this.filterBoardsData(response.body.data.contentItemList)
|
||||
// // console.log(list)
|
||||
// // console.log(JSON.stringify(list))
|
||||
// this.list = list
|
||||
// return {
|
||||
// list,
|
||||
// source: 'mg',
|
||||
// }
|
||||
this.list = boardList
|
||||
return {
|
||||
list: boardList,
|
||||
source: 'mg'
|
||||
}
|
||||
},
|
||||
getList(bangid, page, retryNum = 0) {
|
||||
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
||||
return this.getData(this.getUrl(bangid, page)).then(({ statusCode, body }) => {
|
||||
if (statusCode !== 200) return this.getList(bangid, page, retryNum)
|
||||
let listData = body.match(this.regExps.listData)
|
||||
if (!listData) return this.getList(bangid, page, retryNum)
|
||||
const datas = JSON.parse(RegExp.$1)
|
||||
// console.log(datas)
|
||||
listData = this.filterData(datas.songs.items)
|
||||
return {
|
||||
total: datas.songs.itemTotal,
|
||||
list: this.filterData(datas.songs.items),
|
||||
limit: this.limit,
|
||||
page,
|
||||
source: 'mg'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -153,14 +153,14 @@ export default {
|
||||
_types.flac = {
|
||||
size
|
||||
}
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
|
||||
case 320000:
|
||||
size = item.h ? sizeFormate(item.h.size) : null
|
||||
types.push({ type: '320k', size })
|
||||
_types['320k'] = {
|
||||
size
|
||||
}
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
|
||||
case 192000:
|
||||
case 128000:
|
||||
size = item.l ? sizeFormate(item.l.size) : null
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
import electron from 'electron'
|
||||
import path from 'path'
|
||||
|
||||
function getAppDirPath() {
|
||||
let dirPath: string = electron.app.getAppPath()
|
||||
if (dirPath.endsWith('.asar')) {
|
||||
dirPath = path.join(path.dirname(dirPath), '../')
|
||||
}
|
||||
function getAppDirPath(name?: "home"
|
||||
| "appData"
|
||||
| "assets"
|
||||
| "userData"
|
||||
| "sessionData"
|
||||
| "temp"
|
||||
| "exe"
|
||||
| "module"
|
||||
| "desktop"
|
||||
| "documents"
|
||||
| "downloads"
|
||||
| "music"
|
||||
| "pictures"
|
||||
| "videos"
|
||||
| "recent"
|
||||
| "logs"
|
||||
| "crashDumps") {
|
||||
let dirPath: string = electron.app.getPath(name ?? 'userData')
|
||||
return dirPath
|
||||
}
|
||||
|
||||
|
||||
7
src/preload/index.d.ts
vendored
@@ -2,6 +2,7 @@ import { ElectronAPI } from '@electron-toolkit/preload'
|
||||
import { MainApi, MethodParams } from '../main/services/musicSdk/index'
|
||||
// 自定义 API 接口
|
||||
interface CustomAPI {
|
||||
autoUpdater: any
|
||||
minimize: () => void
|
||||
maximize: () => void
|
||||
close: () => void
|
||||
@@ -19,6 +20,12 @@ interface CustomAPI {
|
||||
) => ReturnType<MainApi[T]>
|
||||
}
|
||||
|
||||
musicCache: {
|
||||
getInfo: () => Promise<any>
|
||||
clear: () => Promise
|
||||
getSize: () => Promise<string>
|
||||
},
|
||||
|
||||
ai: {
|
||||
ask: (prompt: string) => Promise<any>
|
||||
askStream: (prompt: string, streamId: string) => Promise<any>
|
||||
|
||||
@@ -61,7 +61,54 @@ const api = {
|
||||
}
|
||||
},
|
||||
|
||||
getUserConfig: () => ipcRenderer.invoke('get-user-config')
|
||||
musicCache: {
|
||||
getInfo: () => ipcRenderer.invoke('music-cache:get-info'),
|
||||
clear: () => ipcRenderer.invoke('music-cache:clear'),
|
||||
getSize: () => ipcRenderer.invoke('music-cache:get-size')
|
||||
},
|
||||
|
||||
getUserConfig: () => ipcRenderer.invoke('get-user-config'),
|
||||
|
||||
// 自动更新相关
|
||||
autoUpdater: {
|
||||
checkForUpdates: () => ipcRenderer.invoke('auto-updater:check-for-updates'),
|
||||
downloadUpdate: () => ipcRenderer.invoke('auto-updater:download-update'),
|
||||
quitAndInstall: () => ipcRenderer.invoke('auto-updater:quit-and-install'),
|
||||
|
||||
// 监听更新事件
|
||||
onCheckingForUpdate: (callback: () => void) => {
|
||||
ipcRenderer.on('auto-updater:checking-for-update', callback);
|
||||
},
|
||||
onUpdateAvailable: (callback: () => void) => {
|
||||
ipcRenderer.on('auto-updater:update-available', callback);
|
||||
},
|
||||
onUpdateNotAvailable: (callback: () => void) => {
|
||||
ipcRenderer.on('auto-updater:update-not-available', callback);
|
||||
},
|
||||
onDownloadProgress: (callback: (progress: any) => void) => {
|
||||
ipcRenderer.on('auto-updater:download-progress', (_, progress) => callback(progress));
|
||||
},
|
||||
onUpdateDownloaded: (callback: () => void) => {
|
||||
ipcRenderer.on('auto-updater:update-downloaded', callback);
|
||||
},
|
||||
onError: (callback: (error: string) => void) => {
|
||||
ipcRenderer.on('auto-updater:error', (_, error) => callback(error));
|
||||
},
|
||||
onDownloadStarted: (callback: (updateInfo: any) => void) => {
|
||||
ipcRenderer.on('auto-updater:download-started', (_, updateInfo) => callback(updateInfo));
|
||||
},
|
||||
|
||||
// 移除所有监听器
|
||||
removeAllListeners: () => {
|
||||
ipcRenderer.removeAllListeners('auto-updater:checking-for-update');
|
||||
ipcRenderer.removeAllListeners('auto-updater:update-available');
|
||||
ipcRenderer.removeAllListeners('auto-updater:update-not-available');
|
||||
ipcRenderer.removeAllListeners('auto-updater:download-started');
|
||||
ipcRenderer.removeAllListeners('auto-updater:download-progress');
|
||||
ipcRenderer.removeAllListeners('auto-updater:update-downloaded');
|
||||
ipcRenderer.removeAllListeners('auto-updater:error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use `contextBridge` APIs to expose Electron APIs to
|
||||
|
||||
9
src/renderer/components.d.ts
vendored
@@ -11,6 +11,7 @@ declare module 'vue' {
|
||||
FloatBall: typeof import('./src/components/AI/FloatBall.vue')['default']
|
||||
FullPlay: typeof import('./src/components/Play/FullPlay.vue')['default']
|
||||
GlobalAudio: typeof import('./src/components/Play/GlobalAudio.vue')['default']
|
||||
MusicCache: typeof import('./src/components/Settings/MusicCache.vue')['default']
|
||||
PlaylistActions: typeof import('./src/components/Play/PlaylistActions.vue')['default']
|
||||
PlaylistSettings: typeof import('./src/components/Settings/PlaylistSettings.vue')['default']
|
||||
PlayMusic: typeof import('./src/components/Play/PlayMusic.vue')['default']
|
||||
@@ -22,9 +23,13 @@ declare module 'vue' {
|
||||
TAlert: typeof import('tdesign-vue-next')['Alert']
|
||||
TAside: typeof import('tdesign-vue-next')['Aside']
|
||||
TButton: typeof import('tdesign-vue-next')['Button']
|
||||
TCard: typeof import('tdesign-vue-next')['Card']
|
||||
TContent: typeof import('tdesign-vue-next')['Content']
|
||||
TDialog: typeof import('tdesign-vue-next')['Dialog']
|
||||
ThemeDemo: typeof import('./src/components/ThemeDemo.vue')['default']
|
||||
ThemeSelector: typeof import('./src/components/ThemeSelector.vue')['default']
|
||||
TIcon: typeof import('tdesign-vue-next')['Icon']
|
||||
TImage: typeof import('tdesign-vue-next')['Image']
|
||||
TInput: typeof import('tdesign-vue-next')['Input']
|
||||
TitleBarControls: typeof import('./src/components/TitleBarControls.vue')['default']
|
||||
TLayout: typeof import('tdesign-vue-next')['Layout']
|
||||
@@ -32,6 +37,10 @@ declare module 'vue' {
|
||||
TRadioButton: typeof import('tdesign-vue-next')['RadioButton']
|
||||
TRadioGroup: typeof import('tdesign-vue-next')['RadioGroup']
|
||||
TSlider: typeof import('tdesign-vue-next')['Slider']
|
||||
TTooltip: typeof import('tdesign-vue-next')['Tooltip']
|
||||
UpdateExample: typeof import('./src/components/UpdateExample.vue')['default']
|
||||
UpdateProgress: typeof import('./src/components/UpdateProgress.vue')['default']
|
||||
UpdateSettings: typeof import('./src/components/Settings/UpdateSettings.vue')['default']
|
||||
Versions: typeof import('./src/components/Versions.vue')['default']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,62 @@ import { onMounted } from 'vue'
|
||||
import GlobalAudio from './components/Play/GlobalAudio.vue'
|
||||
import FloatBall from './components/AI/FloatBall.vue'
|
||||
import { LocalUserDetailStore } from '@renderer/store/LocalUserDetail'
|
||||
import { useAutoUpdate } from './composables/useAutoUpdate'
|
||||
|
||||
const userInfo = LocalUserDetailStore()
|
||||
const { checkForUpdates } = useAutoUpdate()
|
||||
|
||||
import './assets/main.css'
|
||||
import './assets/theme/blue.css'
|
||||
import './assets/theme/pink.css'
|
||||
import './assets/theme/orange.css'
|
||||
import './assets/theme/cyan.css'
|
||||
|
||||
onMounted(() => {
|
||||
userInfo.init()
|
||||
// 设置测试音频URL
|
||||
loadSavedTheme()
|
||||
|
||||
// 应用启动后延迟3秒检查更新,避免影响启动速度
|
||||
setTimeout(() => {
|
||||
checkForUpdates()
|
||||
}, 3000)
|
||||
})
|
||||
|
||||
// 基于现有主题文件的配置
|
||||
const themes = [
|
||||
{ name: 'default', label: '默认', color: '#2ba55b' },
|
||||
{ name: 'pink', label: '粉色', color: '#fc5e7e' },
|
||||
{ name: 'blue', label: '蓝色', color: '#57b4ff' },
|
||||
{ name: 'cyan', label: '青色', color: '#3ac2b8' },
|
||||
{ name: 'orange', label: '橙色', color: '#fb9458' }
|
||||
]
|
||||
|
||||
const loadSavedTheme = () => {
|
||||
const savedTheme = localStorage.getItem('selected-theme')
|
||||
if (savedTheme && themes.some(t => t.name === savedTheme)) {
|
||||
applyTheme(savedTheme)
|
||||
}
|
||||
}
|
||||
|
||||
const applyTheme = (themeName) => {
|
||||
const documentElement = document.documentElement
|
||||
|
||||
// 移除之前的主题
|
||||
documentElement.removeAttribute('theme-mode')
|
||||
|
||||
// 应用新主题(如果不是默认主题)
|
||||
if (themeName !== 'default') {
|
||||
documentElement.setAttribute('theme-mode', themeName)
|
||||
}
|
||||
|
||||
// 保存到本地存储
|
||||
localStorage.setItem('selected-theme', themeName)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
<GlobalAudio />
|
||||
<FloatBall />
|
||||
<UpdateProgress />
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
|
||||
:root,
|
||||
:root[theme-mode='light'] {
|
||||
--td-brand-color-1: #e2fae2;
|
||||
--td-brand-color-2: #c5f4cb;
|
||||
--td-brand-color-3: #91dca1;
|
||||
--td-brand-color-4: #55c277;
|
||||
--td-brand-color-5: #2ba55b;
|
||||
--td-brand-color-6: #008942;
|
||||
--td-brand-color-7: #006d33;
|
||||
--td-brand-color-8: #005426;
|
||||
--td-brand-color-9: #003c19;
|
||||
--td-brand-color-10: #00260d;
|
||||
--td-brand-color-light: var(--td-brand-color-1);
|
||||
--td-brand-color-focus: var(--td-brand-color-2);
|
||||
--td-brand-color-disabled: var(--td-brand-color-3);
|
||||
--td-brand-color-hover: var(--td-brand-color-3);
|
||||
--td-brand-color: var(--td-brand-color-4);
|
||||
--td-brand-color-active: var(--td-brand-color-5);
|
||||
--td-warning-color-1: #fef3e6;
|
||||
--td-warning-color-2: #f9e0c7;
|
||||
--td-warning-color-3: #f7c797;
|
||||
--td-warning-color-4: #f2995f;
|
||||
--td-warning-color-5: #ed7b2f;
|
||||
--td-warning-color-6: #d35a21;
|
||||
--td-warning-color-7: #ba431b;
|
||||
--td-warning-color-8: #9e3610;
|
||||
--td-warning-color-9: #842b0b;
|
||||
--td-warning-color-10: #5a1907;
|
||||
--td-warning-color: var(--td-warning-color-5);
|
||||
--td-warning-color-hover: var(--td-warning-color-4);
|
||||
--td-warning-color-focus: var(--td-warning-color-2);
|
||||
--td-warning-color-active: var(--td-warning-color-6);
|
||||
--td-warning-color-disabled: var(--td-warning-color-3);
|
||||
--td-warning-color-light: var(--td-warning-color-1);
|
||||
--td-error-color-1: #fdecee;
|
||||
--td-error-color-2: #f9d7d9;
|
||||
--td-error-color-3: #f8b9be;
|
||||
--td-error-color-4: #f78d94;
|
||||
--td-error-color-5: #f36d78;
|
||||
--td-error-color-6: #e34d59;
|
||||
--td-error-color-7: #c9353f;
|
||||
--td-error-color-8: #b11f26;
|
||||
--td-error-color-9: #951114;
|
||||
--td-error-color-10: #680506;
|
||||
--td-error-color: var(--td-error-color-6);
|
||||
--td-error-color-hover: var(--td-error-color-5);
|
||||
--td-error-color-focus: var(--td-error-color-2);
|
||||
--td-error-color-active: var(--td-error-color-7);
|
||||
--td-error-color-disabled: var(--td-error-color-3);
|
||||
--td-error-color-light: var(--td-error-color-1);
|
||||
--td-success-color-1: #e8f8f2;
|
||||
--td-success-color-2: #bcebdc;
|
||||
--td-success-color-3: #85dbbe;
|
||||
--td-success-color-4: #48c79c;
|
||||
--td-success-color-5: #00a870;
|
||||
--td-success-color-6: #078d5c;
|
||||
--td-success-color-7: #067945;
|
||||
--td-success-color-8: #056334;
|
||||
--td-success-color-9: #044f2a;
|
||||
--td-success-color-10: #033017;
|
||||
--td-success-color: var(--td-success-color-5);
|
||||
--td-success-color-hover: var(--td-success-color-4);
|
||||
--td-success-color-focus: var(--td-success-color-2);
|
||||
--td-success-color-active: var(--td-success-color-6);
|
||||
--td-success-color-disabled: var(--td-success-color-3);
|
||||
--td-success-color-light: var(--td-success-color-1);
|
||||
--td-gray-color-1: #f0f4f1;
|
||||
--td-gray-color-2: #e9efeb;
|
||||
--td-gray-color-3: #e1eae4;
|
||||
--td-gray-color-4: #d4dfd8;
|
||||
--td-gray-color-5: #bdc8c1;
|
||||
--td-gray-color-6: #9ca8a1;
|
||||
--td-gray-color-7: #808d86;
|
||||
--td-gray-color-8: #6d7873;
|
||||
--td-gray-color-9: #565f5b;
|
||||
--td-gray-color-10: #454c48;
|
||||
--td-gray-color-11: #383838;
|
||||
--td-gray-color-12: #2c2c2c;
|
||||
--td-gray-color-13: #242424;
|
||||
--td-gray-color-14: #181818;
|
||||
--td-bg-color-container: #fff;
|
||||
--td-bg-color-container-select: #fff;
|
||||
--td-bg-color-page: var(--td-gray-color-2);
|
||||
--td-bg-color-container-hover: var(--td-gray-color-1);
|
||||
--td-bg-color-container-active: var(--td-gray-color-3);
|
||||
--td-bg-color-secondarycontainer: var(--td-gray-color-1);
|
||||
--td-bg-color-secondarycontainer-hover: var(--td-gray-color-2);
|
||||
--td-bg-color-secondarycontainer-active: var(--td-gray-color-4);
|
||||
--td-bg-color-component: var(--td-gray-color-3);
|
||||
--td-bg-color-component-hover: var(--td-gray-color-4);
|
||||
--td-bg-color-component-active: var(--td-gray-color-6);
|
||||
--td-bg-color-component-disabled: var(--td-gray-color-2);
|
||||
--td-component-stroke: var(--td-gray-color-3);
|
||||
--td-component-border: var(--td-gray-color-4);
|
||||
--td-font-white-1: #ffffff;
|
||||
--td-font-white-2: rgba(255, 255, 255, 0.55);
|
||||
--td-font-white-3: rgba(255, 255, 255, 0.35);
|
||||
--td-font-white-4: rgba(255, 255, 255, 0.22);
|
||||
--td-font-gray-1: rgba(0, 0, 0, 0.9);
|
||||
--td-font-gray-2: rgba(0, 0, 0, 0.6);
|
||||
--td-font-gray-3: rgba(0, 0, 0, 0.4);
|
||||
--td-font-gray-4: rgba(0, 0, 0, 0.26);
|
||||
--td-text-color-primary: var(--td-font-gray-1);
|
||||
--td-text-color-secondary: var(--td-font-gray-2);
|
||||
--td-text-color-placeholder: var(--td-font-gray-3);
|
||||
--td-text-color-disabled: var(--td-font-gray-4);
|
||||
--td-text-color-anti: #fff;
|
||||
--td-text-color-brand: var(--td-brand-color);
|
||||
--td-text-color-link: var(--td-brand-color);
|
||||
--td-brand-color-light-hover: var(--td-brand-color-2);
|
||||
--td-warning-color-light-hover: var(--td-warning-color-2);
|
||||
--td-error-color-light-hover: var(--td-error-color-2);
|
||||
--td-success-color-light-hover: var(--td-success-color-2);
|
||||
--td-bg-color-secondarycomponent: var(--td-gray-color-4);
|
||||
--td-bg-color-secondarycomponent-hover: var(--td-gray-color-5);
|
||||
--td-bg-color-secondarycomponent-active: var(--td-gray-color-6);
|
||||
--td-table-shadow-color: rgba(0, 0, 0, 8%);
|
||||
--td-scrollbar-color: rgba(0, 0, 0, 10%);
|
||||
--td-scrollbar-hover-color: rgba(0, 0, 0, 30%);
|
||||
--td-scroll-track-color: #fff;
|
||||
--td-bg-color-specialcomponent: #fff;
|
||||
--td-border-level-1-color: var(--td-gray-color-3);
|
||||
--td-border-level-2-color: var(--td-gray-color-4);
|
||||
--td-shadow-1:
|
||||
0 1px 10px rgba(0, 0, 0, 5%), 0 4px 5px rgba(0, 0, 0, 8%), 0 2px 4px -1px rgba(0, 0, 0, 12%);
|
||||
--td-shadow-2:
|
||||
0 3px 14px 2px rgba(0, 0, 0, 5%), 0 8px 10px 1px rgba(0, 0, 0, 6%),
|
||||
0 5px 5px -3px rgba(0, 0, 0, 10%);
|
||||
--td-shadow-3:
|
||||
0 6px 30px 5px rgba(0, 0, 0, 5%), 0 16px 24px 2px rgba(0, 0, 0, 4%),
|
||||
0 8px 10px -5px rgba(0, 0, 0, 8%);
|
||||
--td-shadow-inset-top: inset 0 0.5px 0 #dcdcdc;
|
||||
--td-shadow-inset-right: inset 0.5px 0 0 #dcdcdc;
|
||||
--td-shadow-inset-bottom: inset 0 -0.5px 0 #dcdcdc;
|
||||
--td-shadow-inset-left: inset -0.5px 0 0 #dcdcdc;
|
||||
--td-mask-active: rgba(0, 0, 0, 0.6);
|
||||
--td-mask-disabled: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
:root[theme-mode='dark'] {
|
||||
--td-brand-color-1: #2ba55b20;
|
||||
--td-brand-color-2: #003c19;
|
||||
--td-brand-color-3: #005426;
|
||||
--td-brand-color-4: #006d33;
|
||||
--td-brand-color-5: #008942;
|
||||
--td-brand-color-6: #2ba55b;
|
||||
--td-brand-color-7: #4cd47c;
|
||||
--td-brand-color-8: #91dca1;
|
||||
--td-brand-color-9: #c5f4cb;
|
||||
--td-brand-color-10: #e2fae2;
|
||||
--td-brand-color-light: var(--td-brand-color-1);
|
||||
--td-brand-color-focus: var(--td-brand-color-2);
|
||||
--td-brand-color-disabled: var(--td-brand-color-3);
|
||||
--td-brand-color-hover: var(--td-brand-color-5);
|
||||
--td-brand-color: var(--td-brand-color-6);
|
||||
--td-brand-color-active: var(--td-brand-color-7);
|
||||
--td-warning-color-1: #4f2a1d;
|
||||
--td-warning-color-2: #582f21;
|
||||
--td-warning-color-3: #733c23;
|
||||
--td-warning-color-4: #a75d2b;
|
||||
--td-warning-color-5: #cf6e2d;
|
||||
--td-warning-color-6: #dc7633;
|
||||
--td-warning-color-7: #e8935c;
|
||||
--td-warning-color-8: #ecbf91;
|
||||
--td-warning-color-9: #eed7bf;
|
||||
--td-warning-color-10: #f3e9dc;
|
||||
--td-error-color-1: #472324;
|
||||
--td-error-color-2: #5e2a2d;
|
||||
--td-error-color-3: #703439;
|
||||
--td-error-color-4: #83383e;
|
||||
--td-error-color-5: #a03f46;
|
||||
--td-error-color-6: #c64751;
|
||||
--td-error-color-7: #de6670;
|
||||
--td-error-color-8: #ec888e;
|
||||
--td-error-color-9: #edb1b6;
|
||||
--td-error-color-10: #eeced0;
|
||||
--td-success-color-1: #193a2a;
|
||||
--td-success-color-2: #1a4230;
|
||||
--td-success-color-3: #17533d;
|
||||
--td-success-color-4: #0d7a55;
|
||||
--td-success-color-5: #059465;
|
||||
--td-success-color-6: #43af8a;
|
||||
--td-success-color-7: #46bf96;
|
||||
--td-success-color-8: #80d2b6;
|
||||
--td-success-color-9: #b4e1d3;
|
||||
--td-success-color-10: #deede8;
|
||||
--td-gray-color-1: #f0f4f1;
|
||||
--td-gray-color-2: #e9efeb;
|
||||
--td-gray-color-3: #e1eae4;
|
||||
--td-gray-color-4: #d4dfd8;
|
||||
--td-gray-color-5: #bdc8c1;
|
||||
--td-gray-color-6: #9ca8a1;
|
||||
--td-gray-color-7: #808d86;
|
||||
--td-gray-color-8: #6d7873;
|
||||
--td-gray-color-9: #565f5b;
|
||||
--td-gray-color-10: #454c48;
|
||||
--td-gray-color-11: #383838;
|
||||
--td-gray-color-12: #2c2c2c;
|
||||
--td-gray-color-13: #242424;
|
||||
--td-gray-color-14: #181818;
|
||||
--td-bg-color-page: var(--td-gray-color-14);
|
||||
--td-bg-color-container: var(--td-gray-color-13);
|
||||
--td-bg-color-container-hover: var(--td-gray-color-12);
|
||||
--td-bg-color-container-active: var(--td-gray-color-10);
|
||||
--td-bg-color-container-select: var(--td-gray-color-9);
|
||||
--td-bg-color-secondarycontainer: var(--td-gray-color-12);
|
||||
--td-bg-color-secondarycontainer-hover: var(--td-gray-color-11);
|
||||
--td-bg-color-secondarycontainer-active: var(--td-gray-color-9);
|
||||
--td-bg-color-component: var(--td-gray-color-11);
|
||||
--td-bg-color-component-hover: var(--td-gray-color-10);
|
||||
--td-bg-color-component-active: var(--td-gray-color-9);
|
||||
--td-bg-color-component-disabled: var(--td-gray-color-12);
|
||||
--td-component-stroke: var(--td-gray-color-11);
|
||||
--td-component-border: var(--td-gray-color-9);
|
||||
--td-font-white-1: rgba(255, 255, 255, 0.9);
|
||||
--td-font-white-2: rgba(255, 255, 255, 0.55);
|
||||
--td-font-white-3: rgba(255, 255, 255, 0.35);
|
||||
--td-font-white-4: rgba(255, 255, 255, 0.22);
|
||||
--td-font-gray-1: rgba(0, 0, 0, 0.9);
|
||||
--td-font-gray-2: rgba(0, 0, 0, 0.6);
|
||||
--td-font-gray-3: rgba(0, 0, 0, 0.4);
|
||||
--td-font-gray-4: rgba(0, 0, 0, 0.26);
|
||||
--td-text-color-primary: var(--td-font-white-1);
|
||||
--td-text-color-secondary: var(--td-font-white-2);
|
||||
--td-text-color-placeholder: var(--td-font-white-3);
|
||||
--td-text-color-disabled: var(--td-font-white-4);
|
||||
--td-text-color-anti: #fff;
|
||||
--td-text-color-brand: var(--td-brand-color);
|
||||
--td-text-color-link: var(--td-brand-color);
|
||||
--td-shadow-1:
|
||||
0 4px 6px rgba(0, 0, 0, 0.06), 0 1px 10px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.12);
|
||||
--td-shadow-2:
|
||||
0 8px 10px rgba(0, 0, 0, 0.12), 0 3px 14px rgba(0, 0, 0, 0.1), 0 5px 5px rgba(0, 0, 0, 0.16);
|
||||
--td-shadow-3:
|
||||
0 16px 24px rgba(0, 0, 0, 0.14), 0 6px 30px rgba(0, 0, 0, 0.12), 0 8px 10px rgba(0, 0, 0, 0.2);
|
||||
--td-shadow-inset-top: inset 0 0.5px 0 #5e5e5e;
|
||||
--td-shadow-inset-right: inset 0.5px 0 0 #5e5e5e;
|
||||
--td-shadow-inset-bottom: inset 0 -0.5px 0 #5e5e5e;
|
||||
--td-shadow-inset-left: inset -0.5px 0 0 #5e5e5e;
|
||||
--td-table-shadow-color: rgba(0, 0, 0, 55%);
|
||||
--td-scrollbar-color: rgba(255, 255, 255, 10%);
|
||||
--td-scrollbar-hover-color: rgba(255, 255, 255, 30%);
|
||||
--td-scroll-track-color: #333;
|
||||
--td-bg-color-specialcomponent: transparent;
|
||||
--td-border-level-1-color: var(--td-gray-color-11);
|
||||
--td-border-level-2-color: var(--td-gray-color-9);
|
||||
--td-mask-active: rgba(0, 0, 0, 0.4);
|
||||
--td-mask-disabled: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
:root {
|
||||
--td-font-family: pingfang sc, microsoft yahei, arial regular;
|
||||
--td-font-family-medium: pingfang sc, microsoft yahei, arial medium;
|
||||
--td-font-size-link-small: 12px;
|
||||
--td-font-size-link-medium: 14px;
|
||||
--td-font-size-link-large: 16px;
|
||||
--td-font-size-mark-small: 12px;
|
||||
--td-font-size-mark-medium: 14px;
|
||||
--td-font-size-body-small: 12px;
|
||||
--td-font-size-body-medium: 14px;
|
||||
--td-font-size-body-large: 16px;
|
||||
--td-font-size-title-small: 14px;
|
||||
--td-font-size-title-medium: 16px;
|
||||
--td-font-size-title-large: 20px;
|
||||
--td-font-size-headline-small: 24px;
|
||||
--td-font-size-headline-medium: 28px;
|
||||
--td-font-size-headline-large: 36px;
|
||||
--td-font-size-display-medium: 48px;
|
||||
--td-font-size-display-large: 64px;
|
||||
--td-line-height-link-small: 20px;
|
||||
--td-line-height-link-medium: 22px;
|
||||
--td-line-height-link-large: 24px;
|
||||
--td-line-height-mark-small: 20px;
|
||||
--td-line-height-mark-medium: 22px;
|
||||
--td-line-height-body-small: 20px;
|
||||
--td-line-height-body-medium: 22px;
|
||||
--td-line-height-body-large: 24px;
|
||||
--td-line-height-title-small: 22px;
|
||||
--td-line-height-title-medium: 24px;
|
||||
--td-line-height-title-large: 28px;
|
||||
--td-line-height-headline-small: 32px;
|
||||
--td-line-height-headline-medium: 36px;
|
||||
--td-line-height-headline-large: 44px;
|
||||
--td-line-height-display-medium: 56px;
|
||||
--td-line-height-display-large: 72px;
|
||||
--td-font-link-small: var(--td-font-size-link-small) / var(--td-line-height-link-small)
|
||||
var(--td-font-family);
|
||||
--td-font-link-medium: var(--td-font-size-link-medium) / var(--td-line-height-link-medium)
|
||||
var(--td-font-family);
|
||||
--td-font-link-large: var(--td-font-size-link-large) / var(--td-line-height-link-large)
|
||||
var(--td-font-family);
|
||||
--td-font-mark-small: 600 var(--td-font-size-mark-small) / var(--td-line-height-mark-small)
|
||||
var(--td-font-family);
|
||||
--td-font-mark-medium: 600 var(--td-font-size-mark-medium) / var(--td-line-height-mark-medium)
|
||||
var(--td-font-family);
|
||||
--td-font-body-small: var(--td-font-size-body-small) / var(--td-line-height-body-small)
|
||||
var(--td-font-family);
|
||||
--td-font-body-medium: var(--td-font-size-body-medium) / var(--td-line-height-body-medium)
|
||||
var(--td-font-family);
|
||||
--td-font-body-large: var(--td-font-size-body-large) / var(--td-line-height-body-large)
|
||||
var(--td-font-family);
|
||||
--td-font-title-small: 600 var(--td-font-size-title-small) / var(--td-line-height-title-small)
|
||||
var(--td-font-family);
|
||||
--td-font-title-medium: 600 var(--td-font-size-title-medium) / var(--td-line-height-title-medium)
|
||||
var(--td-font-family);
|
||||
--td-font-title-large: 600 var(--td-font-size-title-large) / var(--td-line-height-title-large)
|
||||
var(--td-font-family);
|
||||
--td-font-headline-small: 600 var(--td-font-size-headline-small) /
|
||||
var(--td-line-height-headline-small) var(--td-font-family);
|
||||
--td-font-headline-medium: 600 var(--td-font-size-headline-medium) /
|
||||
var(--td-line-height-headline-medium) var(--td-font-family);
|
||||
--td-font-headline-large: 600 var(--td-font-size-headline-large) /
|
||||
var(--td-line-height-headline-large) var(--td-font-family);
|
||||
--td-font-display-medium: 600 var(--td-font-size-display-medium) /
|
||||
var(--td-line-height-display-medium) var(--td-font-family);
|
||||
--td-font-display-large: 600 var(--td-font-size-display-large) /
|
||||
var(--td-line-height-display-large) var(--td-font-family);
|
||||
--td-radius-small: 2px;
|
||||
--td-radius-default: 3px;
|
||||
--td-radius-medium: 6px;
|
||||
--td-radius-large: 9px;
|
||||
--td-radius-extraLarge: 12px;
|
||||
--td-radius-round: 999px;
|
||||
--td-radius-circle: 50%;
|
||||
--td-size-1: 2px;
|
||||
--td-size-2: 4px;
|
||||
--td-size-3: 6px;
|
||||
--td-size-4: 8px;
|
||||
--td-size-5: 12px;
|
||||
--td-size-6: 16px;
|
||||
--td-size-7: 20px;
|
||||
--td-size-8: 24px;
|
||||
--td-size-9: 28px;
|
||||
--td-size-10: 32px;
|
||||
--td-size-11: 36px;
|
||||
--td-size-12: 40px;
|
||||
--td-size-13: 48px;
|
||||
--td-size-14: 56px;
|
||||
--td-size-15: 64px;
|
||||
--td-size-16: 72px;
|
||||
--td-comp-size-xxxs: var(--td-size-6);
|
||||
--td-comp-size-xxs: var(--td-size-7);
|
||||
--td-comp-size-xs: var(--td-size-8);
|
||||
--td-comp-size-s: var(--td-size-9);
|
||||
--td-comp-size-m: var(--td-size-10);
|
||||
--td-comp-size-l: var(--td-size-11);
|
||||
--td-comp-size-xl: var(--td-size-12);
|
||||
--td-comp-size-xxl: var(--td-size-13);
|
||||
--td-comp-size-xxxl: var(--td-size-14);
|
||||
--td-comp-size-xxxxl: var(--td-size-15);
|
||||
--td-comp-size-xxxxxl: var(--td-size-16);
|
||||
--td-pop-padding-s: var(--td-size-2);
|
||||
--td-pop-padding-m: var(--td-size-3);
|
||||
--td-pop-padding-l: var(--td-size-4);
|
||||
--td-pop-padding-xl: var(--td-size-5);
|
||||
--td-pop-padding-xxl: var(--td-size-6);
|
||||
--td-comp-paddingLR-xxs: var(--td-size-1);
|
||||
--td-comp-paddingLR-xs: var(--td-size-2);
|
||||
--td-comp-paddingLR-s: var(--td-size-4);
|
||||
--td-comp-paddingLR-m: var(--td-size-5);
|
||||
--td-comp-paddingLR-l: var(--td-size-6);
|
||||
--td-comp-paddingLR-xl: var(--td-size-8);
|
||||
--td-comp-paddingLR-xxl: var(--td-size-10);
|
||||
--td-comp-paddingTB-xxs: var(--td-size-1);
|
||||
--td-comp-paddingTB-xs: var(--td-size-2);
|
||||
--td-comp-paddingTB-s: var(--td-size-4);
|
||||
--td-comp-paddingTB-m: var(--td-size-5);
|
||||
--td-comp-paddingTB-l: var(--td-size-6);
|
||||
--td-comp-paddingTB-xl: var(--td-size-8);
|
||||
--td-comp-paddingTB-xxl: var(--td-size-10);
|
||||
--td-comp-margin-xxs: var(--td-size-1);
|
||||
--td-comp-margin-xs: var(--td-size-2);
|
||||
--td-comp-margin-s: var(--td-size-4);
|
||||
--td-comp-margin-m: var(--td-size-5);
|
||||
--td-comp-margin-l: var(--td-size-6);
|
||||
--td-comp-margin-xl: var(--td-size-7);
|
||||
--td-comp-margin-xxl: var(--td-size-8);
|
||||
--td-comp-margin-xxxl: var(--td-size-10);
|
||||
--td-comp-margin-xxxxl: var(--td-size-12);
|
||||
}
|
||||
|
||||
355
src/renderer/src/assets/theme/blue.css
Normal file
@@ -0,0 +1,355 @@
|
||||
:root[theme-mode="blue"] {
|
||||
--td-brand-color-1: #ecf4ff;
|
||||
--td-brand-color-2: #cde5ff;
|
||||
--td-brand-color-3: #9aceff;
|
||||
--td-brand-color-4: #57b4ff;
|
||||
--td-brand-color-5: #3198e2;
|
||||
--td-brand-color-6: #007dc5;
|
||||
--td-brand-color-7: #00629c;
|
||||
--td-brand-color-8: #004a77;
|
||||
--td-brand-color-9: #003355;
|
||||
--td-brand-color-10: #00213a;
|
||||
--td-brand-color-light: var(--td-brand-color-1);
|
||||
--td-brand-color-focus: var(--td-brand-color-2);
|
||||
--td-brand-color-disabled: var(--td-brand-color-3);
|
||||
--td-brand-color-hover: var(--td-brand-color-3);
|
||||
--td-brand-color: var(--td-brand-color-4);
|
||||
--td-brand-color-active: var(--td-brand-color-5);
|
||||
--td-warning-color-1: #fef3e6;
|
||||
--td-warning-color-2: #f9e0c7;
|
||||
--td-warning-color-3: #f7c797;
|
||||
--td-warning-color-4: #f2995f;
|
||||
--td-warning-color-5: #ed7b2f;
|
||||
--td-warning-color-6: #d35a21;
|
||||
--td-warning-color-7: #ba431b;
|
||||
--td-warning-color-8: #9e3610;
|
||||
--td-warning-color-9: #842b0b;
|
||||
--td-warning-color-10: #5a1907;
|
||||
--td-warning-color: var(--td-warning-color-5);
|
||||
--td-warning-color-hover: var(--td-warning-color-4);
|
||||
--td-warning-color-focus: var(--td-warning-color-2);
|
||||
--td-warning-color-active: var(--td-warning-color-6);
|
||||
--td-warning-color-disabled: var(--td-warning-color-3);
|
||||
--td-warning-color-light: var(--td-warning-color-1);
|
||||
--td-error-color-1: #fdecee;
|
||||
--td-error-color-2: #f9d7d9;
|
||||
--td-error-color-3: #f8b9be;
|
||||
--td-error-color-4: #f78d94;
|
||||
--td-error-color-5: #f36d78;
|
||||
--td-error-color-6: #e34d59;
|
||||
--td-error-color-7: #c9353f;
|
||||
--td-error-color-8: #b11f26;
|
||||
--td-error-color-9: #951114;
|
||||
--td-error-color-10: #680506;
|
||||
--td-error-color: var(--td-error-color-6);
|
||||
--td-error-color-hover: var(--td-error-color-5);
|
||||
--td-error-color-focus: var(--td-error-color-2);
|
||||
--td-error-color-active: var(--td-error-color-7);
|
||||
--td-error-color-disabled: var(--td-error-color-3);
|
||||
--td-error-color-light: var(--td-error-color-1);
|
||||
--td-success-color-1: #e8f8f2;
|
||||
--td-success-color-2: #bcebdc;
|
||||
--td-success-color-3: #85dbbe;
|
||||
--td-success-color-4: #48c79c;
|
||||
--td-success-color-5: #00a870;
|
||||
--td-success-color-6: #078d5c;
|
||||
--td-success-color-7: #067945;
|
||||
--td-success-color-8: #056334;
|
||||
--td-success-color-9: #044f2a;
|
||||
--td-success-color-10: #033017;
|
||||
--td-success-color: var(--td-success-color-5);
|
||||
--td-success-color-hover: var(--td-success-color-4);
|
||||
--td-success-color-focus: var(--td-success-color-2);
|
||||
--td-success-color-active: var(--td-success-color-6);
|
||||
--td-success-color-disabled: var(--td-success-color-3);
|
||||
--td-success-color-light: var(--td-success-color-1);
|
||||
--td-gray-color-1: #f3f3f3;
|
||||
--td-gray-color-2: #eee;
|
||||
--td-gray-color-3: #e8e8e8;
|
||||
--td-gray-color-4: #ddd;
|
||||
--td-gray-color-5: #c5c5c5;
|
||||
--td-gray-color-6: #a6a6a6;
|
||||
--td-gray-color-7: #8b8b8b;
|
||||
--td-gray-color-8: #777;
|
||||
--td-gray-color-9: #5e5e5e;
|
||||
--td-gray-color-10: #4b4b4b;
|
||||
--td-gray-color-11: #383838;
|
||||
--td-gray-color-12: #2c2c2c;
|
||||
--td-gray-color-13: #242424;
|
||||
--td-gray-color-14: #181818;
|
||||
--td-bg-color-container: #fff;
|
||||
--td-bg-color-container-select: #fff;
|
||||
--td-bg-color-page: var(--td-gray-color-2);
|
||||
--td-bg-color-container-hover: var(--td-gray-color-1);
|
||||
--td-bg-color-container-active: var(--td-gray-color-3);
|
||||
--td-bg-color-secondarycontainer: var(--td-gray-color-1);
|
||||
--td-bg-color-secondarycontainer-hover: var(--td-gray-color-2);
|
||||
--td-bg-color-secondarycontainer-active: var(--td-gray-color-4);
|
||||
--td-bg-color-component: var(--td-gray-color-3);
|
||||
--td-bg-color-component-hover: var(--td-gray-color-4);
|
||||
--td-bg-color-component-active: var(--td-gray-color-6);
|
||||
--td-bg-color-component-disabled: var(--td-gray-color-2);
|
||||
--td-component-stroke: var(--td-gray-color-3);
|
||||
--td-component-border: var(--td-gray-color-4);
|
||||
--td-font-white-1: #ffffff;
|
||||
--td-font-white-2: rgba(255, 255, 255, 0.55);
|
||||
--td-font-white-3: rgba(255, 255, 255, 0.35);
|
||||
--td-font-white-4: rgba(255, 255, 255, 0.22);
|
||||
--td-font-gray-1: rgba(0, 0, 0, 0.9);
|
||||
--td-font-gray-2: rgba(0, 0, 0, 0.6);
|
||||
--td-font-gray-3: rgba(0, 0, 0, 0.4);
|
||||
--td-font-gray-4: rgba(0, 0, 0, 0.26);
|
||||
--td-text-color-primary: var(--td-font-gray-1);
|
||||
--td-text-color-secondary: var(--td-font-gray-2);
|
||||
--td-text-color-placeholder: var(--td-font-gray-3);
|
||||
--td-text-color-disabled: var(--td-font-gray-4);
|
||||
--td-text-color-anti: #fff;
|
||||
--td-text-color-brand: var(--td-brand-color);
|
||||
--td-text-color-link: var(--td-brand-color);
|
||||
--td-brand-color-light-hover: var(--td-brand-color-2);
|
||||
--td-warning-color-light-hover: var(--td-warning-color-2);
|
||||
--td-error-color-light-hover: var(--td-error-color-2);
|
||||
--td-success-color-light-hover: var(--td-success-color-2);
|
||||
--td-bg-color-secondarycomponent: var(--td-gray-color-4);
|
||||
--td-bg-color-secondarycomponent-hover: var(--td-gray-color-5);
|
||||
--td-bg-color-secondarycomponent-active: var(--td-gray-color-6);
|
||||
--td-table-shadow-color: rgba(0, 0, 0, 8%);
|
||||
--td-scrollbar-color: rgba(0, 0, 0, 10%);
|
||||
--td-scrollbar-hover-color: rgba(0, 0, 0, 30%);
|
||||
--td-scroll-track-color: #fff;
|
||||
--td-bg-color-specialcomponent: #fff;
|
||||
--td-border-level-1-color: var(--td-gray-color-3);
|
||||
--td-border-level-2-color: var(--td-gray-color-4);
|
||||
--td-shadow-1: 0 1px 10px rgba(0, 0, 0, 5%), 0 4px 5px rgba(0, 0, 0, 8%), 0 2px 4px -1px rgba(0, 0, 0, 12%);
|
||||
--td-shadow-2: 0 3px 14px 2px rgba(0, 0, 0, 5%), 0 8px 10px 1px rgba(0, 0, 0, 6%), 0 5px 5px -3px rgba(0, 0, 0, 10%);
|
||||
--td-shadow-3: 0 6px 30px 5px rgba(0, 0, 0, 5%), 0 16px 24px 2px rgba(0, 0, 0, 4%), 0 8px 10px -5px rgba(0, 0, 0, 8%);
|
||||
--td-shadow-inset-top: inset 0 0.5px 0 #dcdcdc;
|
||||
--td-shadow-inset-right: inset 0.5px 0 0 #dcdcdc;
|
||||
--td-shadow-inset-bottom: inset 0 -0.5px 0 #dcdcdc;
|
||||
--td-shadow-inset-left: inset -0.5px 0 0 #dcdcdc;
|
||||
--td-mask-active: rgba(0, 0, 0, 0.6);
|
||||
--td-mask-disabled: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
:root[theme-mode="dark"] {
|
||||
--td-brand-color-1: #3198e220;
|
||||
--td-brand-color-2: #003355;
|
||||
--td-brand-color-3: #004a77;
|
||||
--td-brand-color-4: #00629c;
|
||||
--td-brand-color-5: #007dc5;
|
||||
--td-brand-color-6: #3198e2;
|
||||
--td-brand-color-7: #53b1fd;
|
||||
--td-brand-color-8: #9aceff;
|
||||
--td-brand-color-9: #cde5ff;
|
||||
--td-brand-color-10: #ecf4ff;
|
||||
--td-brand-color-light: var(--td-brand-color-1);
|
||||
--td-brand-color-focus: var(--td-brand-color-2);
|
||||
--td-brand-color-disabled: var(--td-brand-color-3);
|
||||
--td-brand-color-hover: var(--td-brand-color-5);
|
||||
--td-brand-color: var(--td-brand-color-6);
|
||||
--td-brand-color-active: var(--td-brand-color-7);
|
||||
--td-warning-color-1: #4f2a1d;
|
||||
--td-warning-color-2: #582f21;
|
||||
--td-warning-color-3: #733c23;
|
||||
--td-warning-color-4: #a75d2b;
|
||||
--td-warning-color-5: #cf6e2d;
|
||||
--td-warning-color-6: #dc7633;
|
||||
--td-warning-color-7: #e8935c;
|
||||
--td-warning-color-8: #ecbf91;
|
||||
--td-warning-color-9: #eed7bf;
|
||||
--td-warning-color-10: #f3e9dc;
|
||||
--td-error-color-1: #472324;
|
||||
--td-error-color-2: #5e2a2d;
|
||||
--td-error-color-3: #703439;
|
||||
--td-error-color-4: #83383e;
|
||||
--td-error-color-5: #a03f46;
|
||||
--td-error-color-6: #c64751;
|
||||
--td-error-color-7: #de6670;
|
||||
--td-error-color-8: #ec888e;
|
||||
--td-error-color-9: #edb1b6;
|
||||
--td-error-color-10: #eeced0;
|
||||
--td-success-color-1: #193a2a;
|
||||
--td-success-color-2: #1a4230;
|
||||
--td-success-color-3: #17533d;
|
||||
--td-success-color-4: #0d7a55;
|
||||
--td-success-color-5: #059465;
|
||||
--td-success-color-6: #43af8a;
|
||||
--td-success-color-7: #46bf96;
|
||||
--td-success-color-8: #80d2b6;
|
||||
--td-success-color-9: #b4e1d3;
|
||||
--td-success-color-10: #deede8;
|
||||
--td-gray-color-1: #f3f3f3;
|
||||
--td-gray-color-2: #eee;
|
||||
--td-gray-color-3: #e8e8e8;
|
||||
--td-gray-color-4: #ddd;
|
||||
--td-gray-color-5: #c5c5c5;
|
||||
--td-gray-color-6: #a6a6a6;
|
||||
--td-gray-color-7: #8b8b8b;
|
||||
--td-gray-color-8: #777;
|
||||
--td-gray-color-9: #5e5e5e;
|
||||
--td-gray-color-10: #4b4b4b;
|
||||
--td-gray-color-11: #383838;
|
||||
--td-gray-color-12: #2c2c2c;
|
||||
--td-gray-color-13: #242424;
|
||||
--td-gray-color-14: #181818;
|
||||
--td-bg-color-page: var(--td-gray-color-14);
|
||||
--td-bg-color-container: var(--td-gray-color-13);
|
||||
--td-bg-color-container-hover: var(--td-gray-color-12);
|
||||
--td-bg-color-container-active: var(--td-gray-color-10);
|
||||
--td-bg-color-container-select: var(--td-gray-color-9);
|
||||
--td-bg-color-secondarycontainer: var(--td-gray-color-12);
|
||||
--td-bg-color-secondarycontainer-hover: var(--td-gray-color-11);
|
||||
--td-bg-color-secondarycontainer-active: var(--td-gray-color-9);
|
||||
--td-bg-color-component: var(--td-gray-color-11);
|
||||
--td-bg-color-component-hover: var(--td-gray-color-10);
|
||||
--td-bg-color-component-active: var(--td-gray-color-9);
|
||||
--td-bg-color-component-disabled: var(--td-gray-color-12);
|
||||
--td-component-stroke: var(--td-gray-color-11);
|
||||
--td-component-border: var(--td-gray-color-9);
|
||||
--td-font-white-1: rgba(255, 255, 255, 0.9);
|
||||
--td-font-white-2: rgba(255, 255, 255, 0.55);
|
||||
--td-font-white-3: rgba(255, 255, 255, 0.35);
|
||||
--td-font-white-4: rgba(255, 255, 255, 0.22);
|
||||
--td-font-gray-1: rgba(0, 0, 0, 0.9);
|
||||
--td-font-gray-2: rgba(0, 0, 0, 0.6);
|
||||
--td-font-gray-3: rgba(0, 0, 0, 0.4);
|
||||
--td-font-gray-4: rgba(0, 0, 0, 0.26);
|
||||
--td-text-color-primary: var(--td-font-white-1);
|
||||
--td-text-color-secondary: var(--td-font-white-2);
|
||||
--td-text-color-placeholder: var(--td-font-white-3);
|
||||
--td-text-color-disabled: var(--td-font-white-4);
|
||||
--td-text-color-anti: #fff;
|
||||
--td-text-color-brand: var(--td-brand-color);
|
||||
--td-text-color-link: var(--td-brand-color);
|
||||
--td-shadow-1: 0 4px 6px rgba(0, 0, 0, 0.06), 0 1px 10px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.12);
|
||||
--td-shadow-2: 0 8px 10px rgba(0, 0, 0, 0.12), 0 3px 14px rgba(0, 0, 0, 0.10), 0 5px 5px rgba(0, 0, 0, 0.16);
|
||||
--td-shadow-3: 0 16px 24px rgba(0, 0, 0, 0.14), 0 6px 30px rgba(0, 0, 0, 0.12), 0 8px 10px rgba(0, 0, 0, 0.20);
|
||||
--td-shadow-inset-top: inset 0 0.5px 0 #5e5e5e;
|
||||
--td-shadow-inset-right: inset 0.5px 0 0 #5e5e5e;
|
||||
--td-shadow-inset-bottom: inset 0 -0.5px 0 #5e5e5e;
|
||||
--td-shadow-inset-left: inset -0.5px 0 0 #5e5e5e;
|
||||
--td-table-shadow-color: rgba(0, 0, 0, 55%);
|
||||
--td-scrollbar-color: rgba(255, 255, 255, 10%);
|
||||
--td-scrollbar-hover-color: rgba(255, 255, 255, 30%);
|
||||
--td-scroll-track-color: #333;
|
||||
--td-bg-color-specialcomponent: transparent;
|
||||
--td-border-level-1-color: var(--td-gray-color-11);
|
||||
--td-border-level-2-color: var(--td-gray-color-9);
|
||||
--td-mask-active: rgba(0, 0, 0, 0.4);
|
||||
--td-mask-disabled: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
:root {
|
||||
--td-font-family: pingfang sc, microsoft yahei, arial regular;
|
||||
--td-font-family-medium: pingfang sc, microsoft yahei, arial medium;
|
||||
--td-font-size-link-small: 12px;
|
||||
--td-font-size-link-medium: 14px;
|
||||
--td-font-size-link-large: 16px;
|
||||
--td-font-size-mark-small: 12px;
|
||||
--td-font-size-mark-medium: 14px;
|
||||
--td-font-size-body-small: 12px;
|
||||
--td-font-size-body-medium: 14px;
|
||||
--td-font-size-body-large: 16px;
|
||||
--td-font-size-title-small: 14px;
|
||||
--td-font-size-title-medium: 16px;
|
||||
--td-font-size-title-large: 20px;
|
||||
--td-font-size-headline-small: 24px;
|
||||
--td-font-size-headline-medium: 28px;
|
||||
--td-font-size-headline-large: 36px;
|
||||
--td-font-size-display-medium: 48px;
|
||||
--td-font-size-display-large: 64px;
|
||||
--td-line-height-link-small: 20px;
|
||||
--td-line-height-link-medium: 22px;
|
||||
--td-line-height-link-large: 24px;
|
||||
--td-line-height-mark-small: 20px;
|
||||
--td-line-height-mark-medium: 22px;
|
||||
--td-line-height-body-small: 20px;
|
||||
--td-line-height-body-medium: 22px;
|
||||
--td-line-height-body-large: 24px;
|
||||
--td-line-height-title-small: 22px;
|
||||
--td-line-height-title-medium: 24px;
|
||||
--td-line-height-title-large: 28px;
|
||||
--td-line-height-headline-small: 32px;
|
||||
--td-line-height-headline-medium: 36px;
|
||||
--td-line-height-headline-large: 44px;
|
||||
--td-line-height-display-medium: 56px;
|
||||
--td-line-height-display-large: 72px;
|
||||
--td-font-link-small: var(--td-font-size-link-small) / var(--td-line-height-link-small) var(--td-font-family);
|
||||
--td-font-link-medium: var(--td-font-size-link-medium) / var(--td-line-height-link-medium) var(--td-font-family);
|
||||
--td-font-link-large: var(--td-font-size-link-large) / var(--td-line-height-link-large) var(--td-font-family);
|
||||
--td-font-mark-small: 600 var(--td-font-size-mark-small) / var(--td-line-height-mark-small) var(--td-font-family);
|
||||
--td-font-mark-medium: 600 var(--td-font-size-mark-medium) / var(--td-line-height-mark-medium) var(--td-font-family);
|
||||
--td-font-body-small: var(--td-font-size-body-small) / var(--td-line-height-body-small) var(--td-font-family);
|
||||
--td-font-body-medium: var(--td-font-size-body-medium) / var(--td-line-height-body-medium) var(--td-font-family);
|
||||
--td-font-body-large: var(--td-font-size-body-large) / var(--td-line-height-body-large) var(--td-font-family);
|
||||
--td-font-title-small: 600 var(--td-font-size-title-small) / var(--td-line-height-title-small) var(--td-font-family);
|
||||
--td-font-title-medium: 600 var(--td-font-size-title-medium) / var(--td-line-height-title-medium) var(--td-font-family);
|
||||
--td-font-title-large: 600 var(--td-font-size-title-large) / var(--td-line-height-title-large) var(--td-font-family);
|
||||
--td-font-headline-small: 600 var(--td-font-size-headline-small) / var(--td-line-height-headline-small) var(--td-font-family);
|
||||
--td-font-headline-medium: 600 var(--td-font-size-headline-medium) / var(--td-line-height-headline-medium) var(--td-font-family);
|
||||
--td-font-headline-large: 600 var(--td-font-size-headline-large) / var(--td-line-height-headline-large) var(--td-font-family);
|
||||
--td-font-display-medium: 600 var(--td-font-size-display-medium) / var(--td-line-height-display-medium) var(--td-font-family);
|
||||
--td-font-display-large: 600 var(--td-font-size-display-large) / var(--td-line-height-display-large) var(--td-font-family);
|
||||
--td-radius-small: 2px;
|
||||
--td-radius-default: 3px;
|
||||
--td-radius-medium: 6px;
|
||||
--td-radius-large: 9px;
|
||||
--td-radius-extraLarge: 12px;
|
||||
--td-radius-round: 999px;
|
||||
--td-radius-circle: 50%;
|
||||
--td-size-1: 2px;
|
||||
--td-size-2: 4px;
|
||||
--td-size-3: 6px;
|
||||
--td-size-4: 8px;
|
||||
--td-size-5: 12px;
|
||||
--td-size-6: 16px;
|
||||
--td-size-7: 20px;
|
||||
--td-size-8: 24px;
|
||||
--td-size-9: 28px;
|
||||
--td-size-10: 32px;
|
||||
--td-size-11: 36px;
|
||||
--td-size-12: 40px;
|
||||
--td-size-13: 48px;
|
||||
--td-size-14: 56px;
|
||||
--td-size-15: 64px;
|
||||
--td-size-16: 72px;
|
||||
--td-comp-size-xxxs: var(--td-size-6);
|
||||
--td-comp-size-xxs: var(--td-size-7);
|
||||
--td-comp-size-xs: var(--td-size-8);
|
||||
--td-comp-size-s: var(--td-size-9);
|
||||
--td-comp-size-m: var(--td-size-10);
|
||||
--td-comp-size-l: var(--td-size-11);
|
||||
--td-comp-size-xl: var(--td-size-12);
|
||||
--td-comp-size-xxl: var(--td-size-13);
|
||||
--td-comp-size-xxxl: var(--td-size-14);
|
||||
--td-comp-size-xxxxl: var(--td-size-15);
|
||||
--td-comp-size-xxxxxl: var(--td-size-16);
|
||||
--td-pop-padding-s: var(--td-size-2);
|
||||
--td-pop-padding-m: var(--td-size-3);
|
||||
--td-pop-padding-l: var(--td-size-4);
|
||||
--td-pop-padding-xl: var(--td-size-5);
|
||||
--td-pop-padding-xxl: var(--td-size-6);
|
||||
--td-comp-paddingLR-xxs: var(--td-size-1);
|
||||
--td-comp-paddingLR-xs: var(--td-size-2);
|
||||
--td-comp-paddingLR-s: var(--td-size-4);
|
||||
--td-comp-paddingLR-m: var(--td-size-5);
|
||||
--td-comp-paddingLR-l: var(--td-size-6);
|
||||
--td-comp-paddingLR-xl: var(--td-size-8);
|
||||
--td-comp-paddingLR-xxl: var(--td-size-10);
|
||||
--td-comp-paddingTB-xxs: var(--td-size-1);
|
||||
--td-comp-paddingTB-xs: var(--td-size-2);
|
||||
--td-comp-paddingTB-s: var(--td-size-4);
|
||||
--td-comp-paddingTB-m: var(--td-size-5);
|
||||
--td-comp-paddingTB-l: var(--td-size-6);
|
||||
--td-comp-paddingTB-xl: var(--td-size-8);
|
||||
--td-comp-paddingTB-xxl: var(--td-size-10);
|
||||
--td-comp-margin-xxs: var(--td-size-1);
|
||||
--td-comp-margin-xs: var(--td-size-2);
|
||||
--td-comp-margin-s: var(--td-size-4);
|
||||
--td-comp-margin-m: var(--td-size-5);
|
||||
--td-comp-margin-l: var(--td-size-6);
|
||||
--td-comp-margin-xl: var(--td-size-7);
|
||||
--td-comp-margin-xxl: var(--td-size-8);
|
||||
--td-comp-margin-xxxl: var(--td-size-10);
|
||||
--td-comp-margin-xxxxl: var(--td-size-12);
|
||||
}
|
||||
355
src/renderer/src/assets/theme/cyan.css
Normal file
@@ -0,0 +1,355 @@
|
||||
:root[theme-mode="cyan"] {
|
||||
--td-brand-color-1: #e3fcf8;
|
||||
--td-brand-color-2: #beefe9;
|
||||
--td-brand-color-3: #86dad1;
|
||||
--td-brand-color-4: #3ac2b8;
|
||||
--td-brand-color-5: #00a59b;
|
||||
--td-brand-color-6: #00877e;
|
||||
--td-brand-color-7: #006b64;
|
||||
--td-brand-color-8: #00524c;
|
||||
--td-brand-color-9: #003b36;
|
||||
--td-brand-color-10: #002724;
|
||||
--td-brand-color-light: var(--td-brand-color-1);
|
||||
--td-brand-color-focus: var(--td-brand-color-2);
|
||||
--td-brand-color-disabled: var(--td-brand-color-3);
|
||||
--td-brand-color-hover: var(--td-brand-color-3);
|
||||
--td-brand-color: var(--td-brand-color-4);
|
||||
--td-brand-color-active: var(--td-brand-color-5);
|
||||
--td-warning-color-1: #fef3e6;
|
||||
--td-warning-color-2: #f9e0c7;
|
||||
--td-warning-color-3: #f7c797;
|
||||
--td-warning-color-4: #f2995f;
|
||||
--td-warning-color-5: #ed7b2f;
|
||||
--td-warning-color-6: #d35a21;
|
||||
--td-warning-color-7: #ba431b;
|
||||
--td-warning-color-8: #9e3610;
|
||||
--td-warning-color-9: #842b0b;
|
||||
--td-warning-color-10: #5a1907;
|
||||
--td-warning-color: var(--td-warning-color-5);
|
||||
--td-warning-color-hover: var(--td-warning-color-4);
|
||||
--td-warning-color-focus: var(--td-warning-color-2);
|
||||
--td-warning-color-active: var(--td-warning-color-6);
|
||||
--td-warning-color-disabled: var(--td-warning-color-3);
|
||||
--td-warning-color-light: var(--td-warning-color-1);
|
||||
--td-error-color-1: #fdecee;
|
||||
--td-error-color-2: #f9d7d9;
|
||||
--td-error-color-3: #f8b9be;
|
||||
--td-error-color-4: #f78d94;
|
||||
--td-error-color-5: #f36d78;
|
||||
--td-error-color-6: #e34d59;
|
||||
--td-error-color-7: #c9353f;
|
||||
--td-error-color-8: #b11f26;
|
||||
--td-error-color-9: #951114;
|
||||
--td-error-color-10: #680506;
|
||||
--td-error-color: var(--td-error-color-6);
|
||||
--td-error-color-hover: var(--td-error-color-5);
|
||||
--td-error-color-focus: var(--td-error-color-2);
|
||||
--td-error-color-active: var(--td-error-color-7);
|
||||
--td-error-color-disabled: var(--td-error-color-3);
|
||||
--td-error-color-light: var(--td-error-color-1);
|
||||
--td-success-color-1: #e8f8f2;
|
||||
--td-success-color-2: #bcebdc;
|
||||
--td-success-color-3: #85dbbe;
|
||||
--td-success-color-4: #48c79c;
|
||||
--td-success-color-5: #00a870;
|
||||
--td-success-color-6: #078d5c;
|
||||
--td-success-color-7: #067945;
|
||||
--td-success-color-8: #056334;
|
||||
--td-success-color-9: #044f2a;
|
||||
--td-success-color-10: #033017;
|
||||
--td-success-color: var(--td-success-color-5);
|
||||
--td-success-color-hover: var(--td-success-color-4);
|
||||
--td-success-color-focus: var(--td-success-color-2);
|
||||
--td-success-color-active: var(--td-success-color-6);
|
||||
--td-success-color-disabled: var(--td-success-color-3);
|
||||
--td-success-color-light: var(--td-success-color-1);
|
||||
--td-gray-color-1: #f3f3f3;
|
||||
--td-gray-color-2: #eee;
|
||||
--td-gray-color-3: #e8e8e8;
|
||||
--td-gray-color-4: #ddd;
|
||||
--td-gray-color-5: #c5c5c5;
|
||||
--td-gray-color-6: #a6a6a6;
|
||||
--td-gray-color-7: #8b8b8b;
|
||||
--td-gray-color-8: #777;
|
||||
--td-gray-color-9: #5e5e5e;
|
||||
--td-gray-color-10: #4b4b4b;
|
||||
--td-gray-color-11: #383838;
|
||||
--td-gray-color-12: #2c2c2c;
|
||||
--td-gray-color-13: #242424;
|
||||
--td-gray-color-14: #181818;
|
||||
--td-bg-color-container: #fff;
|
||||
--td-bg-color-container-select: #fff;
|
||||
--td-bg-color-page: var(--td-gray-color-2);
|
||||
--td-bg-color-container-hover: var(--td-gray-color-1);
|
||||
--td-bg-color-container-active: var(--td-gray-color-3);
|
||||
--td-bg-color-secondarycontainer: var(--td-gray-color-1);
|
||||
--td-bg-color-secondarycontainer-hover: var(--td-gray-color-2);
|
||||
--td-bg-color-secondarycontainer-active: var(--td-gray-color-4);
|
||||
--td-bg-color-component: var(--td-gray-color-3);
|
||||
--td-bg-color-component-hover: var(--td-gray-color-4);
|
||||
--td-bg-color-component-active: var(--td-gray-color-6);
|
||||
--td-bg-color-component-disabled: var(--td-gray-color-2);
|
||||
--td-component-stroke: var(--td-gray-color-3);
|
||||
--td-component-border: var(--td-gray-color-4);
|
||||
--td-font-white-1: #ffffff;
|
||||
--td-font-white-2: rgba(255, 255, 255, 0.55);
|
||||
--td-font-white-3: rgba(255, 255, 255, 0.35);
|
||||
--td-font-white-4: rgba(255, 255, 255, 0.22);
|
||||
--td-font-gray-1: rgba(0, 0, 0, 0.9);
|
||||
--td-font-gray-2: rgba(0, 0, 0, 0.6);
|
||||
--td-font-gray-3: rgba(0, 0, 0, 0.4);
|
||||
--td-font-gray-4: rgba(0, 0, 0, 0.26);
|
||||
--td-text-color-primary: var(--td-font-gray-1);
|
||||
--td-text-color-secondary: var(--td-font-gray-2);
|
||||
--td-text-color-placeholder: var(--td-font-gray-3);
|
||||
--td-text-color-disabled: var(--td-font-gray-4);
|
||||
--td-text-color-anti: #fff;
|
||||
--td-text-color-brand: var(--td-brand-color);
|
||||
--td-text-color-link: var(--td-brand-color);
|
||||
--td-brand-color-light-hover: var(--td-brand-color-2);
|
||||
--td-warning-color-light-hover: var(--td-warning-color-2);
|
||||
--td-error-color-light-hover: var(--td-error-color-2);
|
||||
--td-success-color-light-hover: var(--td-success-color-2);
|
||||
--td-bg-color-secondarycomponent: var(--td-gray-color-4);
|
||||
--td-bg-color-secondarycomponent-hover: var(--td-gray-color-5);
|
||||
--td-bg-color-secondarycomponent-active: var(--td-gray-color-6);
|
||||
--td-table-shadow-color: rgba(0, 0, 0, 8%);
|
||||
--td-scrollbar-color: rgba(0, 0, 0, 10%);
|
||||
--td-scrollbar-hover-color: rgba(0, 0, 0, 30%);
|
||||
--td-scroll-track-color: #fff;
|
||||
--td-bg-color-specialcomponent: #fff;
|
||||
--td-border-level-1-color: var(--td-gray-color-3);
|
||||
--td-border-level-2-color: var(--td-gray-color-4);
|
||||
--td-shadow-1: 0 1px 10px rgba(0, 0, 0, 5%), 0 4px 5px rgba(0, 0, 0, 8%), 0 2px 4px -1px rgba(0, 0, 0, 12%);
|
||||
--td-shadow-2: 0 3px 14px 2px rgba(0, 0, 0, 5%), 0 8px 10px 1px rgba(0, 0, 0, 6%), 0 5px 5px -3px rgba(0, 0, 0, 10%);
|
||||
--td-shadow-3: 0 6px 30px 5px rgba(0, 0, 0, 5%), 0 16px 24px 2px rgba(0, 0, 0, 4%), 0 8px 10px -5px rgba(0, 0, 0, 8%);
|
||||
--td-shadow-inset-top: inset 0 0.5px 0 #dcdcdc;
|
||||
--td-shadow-inset-right: inset 0.5px 0 0 #dcdcdc;
|
||||
--td-shadow-inset-bottom: inset 0 -0.5px 0 #dcdcdc;
|
||||
--td-shadow-inset-left: inset -0.5px 0 0 #dcdcdc;
|
||||
--td-mask-active: rgba(0, 0, 0, 0.6);
|
||||
--td-mask-disabled: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
:root[theme-mode="dark"] {
|
||||
--td-brand-color-1: #00a59b20;
|
||||
--td-brand-color-2: #003b36;
|
||||
--td-brand-color-3: #00524c;
|
||||
--td-brand-color-4: #006b64;
|
||||
--td-brand-color-5: #00877e;
|
||||
--td-brand-color-6: #00a59b;
|
||||
--td-brand-color-7: #0ed6ca;
|
||||
--td-brand-color-8: #86dad1;
|
||||
--td-brand-color-9: #beefe9;
|
||||
--td-brand-color-10: #e3fcf8;
|
||||
--td-brand-color-light: var(--td-brand-color-1);
|
||||
--td-brand-color-focus: var(--td-brand-color-2);
|
||||
--td-brand-color-disabled: var(--td-brand-color-3);
|
||||
--td-brand-color-hover: var(--td-brand-color-5);
|
||||
--td-brand-color: var(--td-brand-color-6);
|
||||
--td-brand-color-active: var(--td-brand-color-7);
|
||||
--td-warning-color-1: #4f2a1d;
|
||||
--td-warning-color-2: #582f21;
|
||||
--td-warning-color-3: #733c23;
|
||||
--td-warning-color-4: #a75d2b;
|
||||
--td-warning-color-5: #cf6e2d;
|
||||
--td-warning-color-6: #dc7633;
|
||||
--td-warning-color-7: #e8935c;
|
||||
--td-warning-color-8: #ecbf91;
|
||||
--td-warning-color-9: #eed7bf;
|
||||
--td-warning-color-10: #f3e9dc;
|
||||
--td-error-color-1: #472324;
|
||||
--td-error-color-2: #5e2a2d;
|
||||
--td-error-color-3: #703439;
|
||||
--td-error-color-4: #83383e;
|
||||
--td-error-color-5: #a03f46;
|
||||
--td-error-color-6: #c64751;
|
||||
--td-error-color-7: #de6670;
|
||||
--td-error-color-8: #ec888e;
|
||||
--td-error-color-9: #edb1b6;
|
||||
--td-error-color-10: #eeced0;
|
||||
--td-success-color-1: #193a2a;
|
||||
--td-success-color-2: #1a4230;
|
||||
--td-success-color-3: #17533d;
|
||||
--td-success-color-4: #0d7a55;
|
||||
--td-success-color-5: #059465;
|
||||
--td-success-color-6: #43af8a;
|
||||
--td-success-color-7: #46bf96;
|
||||
--td-success-color-8: #80d2b6;
|
||||
--td-success-color-9: #b4e1d3;
|
||||
--td-success-color-10: #deede8;
|
||||
--td-gray-color-1: #f3f3f3;
|
||||
--td-gray-color-2: #eee;
|
||||
--td-gray-color-3: #e8e8e8;
|
||||
--td-gray-color-4: #ddd;
|
||||
--td-gray-color-5: #c5c5c5;
|
||||
--td-gray-color-6: #a6a6a6;
|
||||
--td-gray-color-7: #8b8b8b;
|
||||
--td-gray-color-8: #777;
|
||||
--td-gray-color-9: #5e5e5e;
|
||||
--td-gray-color-10: #4b4b4b;
|
||||
--td-gray-color-11: #383838;
|
||||
--td-gray-color-12: #2c2c2c;
|
||||
--td-gray-color-13: #242424;
|
||||
--td-gray-color-14: #181818;
|
||||
--td-bg-color-page: var(--td-gray-color-14);
|
||||
--td-bg-color-container: var(--td-gray-color-13);
|
||||
--td-bg-color-container-hover: var(--td-gray-color-12);
|
||||
--td-bg-color-container-active: var(--td-gray-color-10);
|
||||
--td-bg-color-container-select: var(--td-gray-color-9);
|
||||
--td-bg-color-secondarycontainer: var(--td-gray-color-12);
|
||||
--td-bg-color-secondarycontainer-hover: var(--td-gray-color-11);
|
||||
--td-bg-color-secondarycontainer-active: var(--td-gray-color-9);
|
||||
--td-bg-color-component: var(--td-gray-color-11);
|
||||
--td-bg-color-component-hover: var(--td-gray-color-10);
|
||||
--td-bg-color-component-active: var(--td-gray-color-9);
|
||||
--td-bg-color-component-disabled: var(--td-gray-color-12);
|
||||
--td-component-stroke: var(--td-gray-color-11);
|
||||
--td-component-border: var(--td-gray-color-9);
|
||||
--td-font-white-1: rgba(255, 255, 255, 0.9);
|
||||
--td-font-white-2: rgba(255, 255, 255, 0.55);
|
||||
--td-font-white-3: rgba(255, 255, 255, 0.35);
|
||||
--td-font-white-4: rgba(255, 255, 255, 0.22);
|
||||
--td-font-gray-1: rgba(0, 0, 0, 0.9);
|
||||
--td-font-gray-2: rgba(0, 0, 0, 0.6);
|
||||
--td-font-gray-3: rgba(0, 0, 0, 0.4);
|
||||
--td-font-gray-4: rgba(0, 0, 0, 0.26);
|
||||
--td-text-color-primary: var(--td-font-white-1);
|
||||
--td-text-color-secondary: var(--td-font-white-2);
|
||||
--td-text-color-placeholder: var(--td-font-white-3);
|
||||
--td-text-color-disabled: var(--td-font-white-4);
|
||||
--td-text-color-anti: #fff;
|
||||
--td-text-color-brand: var(--td-brand-color);
|
||||
--td-text-color-link: var(--td-brand-color);
|
||||
--td-shadow-1: 0 4px 6px rgba(0, 0, 0, 0.06), 0 1px 10px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.12);
|
||||
--td-shadow-2: 0 8px 10px rgba(0, 0, 0, 0.12), 0 3px 14px rgba(0, 0, 0, 0.10), 0 5px 5px rgba(0, 0, 0, 0.16);
|
||||
--td-shadow-3: 0 16px 24px rgba(0, 0, 0, 0.14), 0 6px 30px rgba(0, 0, 0, 0.12), 0 8px 10px rgba(0, 0, 0, 0.20);
|
||||
--td-shadow-inset-top: inset 0 0.5px 0 #5e5e5e;
|
||||
--td-shadow-inset-right: inset 0.5px 0 0 #5e5e5e;
|
||||
--td-shadow-inset-bottom: inset 0 -0.5px 0 #5e5e5e;
|
||||
--td-shadow-inset-left: inset -0.5px 0 0 #5e5e5e;
|
||||
--td-table-shadow-color: rgba(0, 0, 0, 55%);
|
||||
--td-scrollbar-color: rgba(255, 255, 255, 10%);
|
||||
--td-scrollbar-hover-color: rgba(255, 255, 255, 30%);
|
||||
--td-scroll-track-color: #333;
|
||||
--td-bg-color-specialcomponent: transparent;
|
||||
--td-border-level-1-color: var(--td-gray-color-11);
|
||||
--td-border-level-2-color: var(--td-gray-color-9);
|
||||
--td-mask-active: rgba(0, 0, 0, 0.4);
|
||||
--td-mask-disabled: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
:root {
|
||||
--td-font-family: pingfang sc, microsoft yahei, arial regular;
|
||||
--td-font-family-medium: pingfang sc, microsoft yahei, arial medium;
|
||||
--td-font-size-link-small: 12px;
|
||||
--td-font-size-link-medium: 14px;
|
||||
--td-font-size-link-large: 16px;
|
||||
--td-font-size-mark-small: 12px;
|
||||
--td-font-size-mark-medium: 14px;
|
||||
--td-font-size-body-small: 12px;
|
||||
--td-font-size-body-medium: 14px;
|
||||
--td-font-size-body-large: 16px;
|
||||
--td-font-size-title-small: 14px;
|
||||
--td-font-size-title-medium: 16px;
|
||||
--td-font-size-title-large: 20px;
|
||||
--td-font-size-headline-small: 24px;
|
||||
--td-font-size-headline-medium: 28px;
|
||||
--td-font-size-headline-large: 36px;
|
||||
--td-font-size-display-medium: 48px;
|
||||
--td-font-size-display-large: 64px;
|
||||
--td-line-height-link-small: 20px;
|
||||
--td-line-height-link-medium: 22px;
|
||||
--td-line-height-link-large: 24px;
|
||||
--td-line-height-mark-small: 20px;
|
||||
--td-line-height-mark-medium: 22px;
|
||||
--td-line-height-body-small: 20px;
|
||||
--td-line-height-body-medium: 22px;
|
||||
--td-line-height-body-large: 24px;
|
||||
--td-line-height-title-small: 22px;
|
||||
--td-line-height-title-medium: 24px;
|
||||
--td-line-height-title-large: 28px;
|
||||
--td-line-height-headline-small: 32px;
|
||||
--td-line-height-headline-medium: 36px;
|
||||
--td-line-height-headline-large: 44px;
|
||||
--td-line-height-display-medium: 56px;
|
||||
--td-line-height-display-large: 72px;
|
||||
--td-font-link-small: var(--td-font-size-link-small) / var(--td-line-height-link-small) var(--td-font-family);
|
||||
--td-font-link-medium: var(--td-font-size-link-medium) / var(--td-line-height-link-medium) var(--td-font-family);
|
||||
--td-font-link-large: var(--td-font-size-link-large) / var(--td-line-height-link-large) var(--td-font-family);
|
||||
--td-font-mark-small: 600 var(--td-font-size-mark-small) / var(--td-line-height-mark-small) var(--td-font-family);
|
||||
--td-font-mark-medium: 600 var(--td-font-size-mark-medium) / var(--td-line-height-mark-medium) var(--td-font-family);
|
||||
--td-font-body-small: var(--td-font-size-body-small) / var(--td-line-height-body-small) var(--td-font-family);
|
||||
--td-font-body-medium: var(--td-font-size-body-medium) / var(--td-line-height-body-medium) var(--td-font-family);
|
||||
--td-font-body-large: var(--td-font-size-body-large) / var(--td-line-height-body-large) var(--td-font-family);
|
||||
--td-font-title-small: 600 var(--td-font-size-title-small) / var(--td-line-height-title-small) var(--td-font-family);
|
||||
--td-font-title-medium: 600 var(--td-font-size-title-medium) / var(--td-line-height-title-medium) var(--td-font-family);
|
||||
--td-font-title-large: 600 var(--td-font-size-title-large) / var(--td-line-height-title-large) var(--td-font-family);
|
||||
--td-font-headline-small: 600 var(--td-font-size-headline-small) / var(--td-line-height-headline-small) var(--td-font-family);
|
||||
--td-font-headline-medium: 600 var(--td-font-size-headline-medium) / var(--td-line-height-headline-medium) var(--td-font-family);
|
||||
--td-font-headline-large: 600 var(--td-font-size-headline-large) / var(--td-line-height-headline-large) var(--td-font-family);
|
||||
--td-font-display-medium: 600 var(--td-font-size-display-medium) / var(--td-line-height-display-medium) var(--td-font-family);
|
||||
--td-font-display-large: 600 var(--td-font-size-display-large) / var(--td-line-height-display-large) var(--td-font-family);
|
||||
--td-radius-small: 2px;
|
||||
--td-radius-default: 3px;
|
||||
--td-radius-medium: 6px;
|
||||
--td-radius-large: 9px;
|
||||
--td-radius-extraLarge: 12px;
|
||||
--td-radius-round: 999px;
|
||||
--td-radius-circle: 50%;
|
||||
--td-size-1: 2px;
|
||||
--td-size-2: 4px;
|
||||
--td-size-3: 6px;
|
||||
--td-size-4: 8px;
|
||||
--td-size-5: 12px;
|
||||
--td-size-6: 16px;
|
||||
--td-size-7: 20px;
|
||||
--td-size-8: 24px;
|
||||
--td-size-9: 28px;
|
||||
--td-size-10: 32px;
|
||||
--td-size-11: 36px;
|
||||
--td-size-12: 40px;
|
||||
--td-size-13: 48px;
|
||||
--td-size-14: 56px;
|
||||
--td-size-15: 64px;
|
||||
--td-size-16: 72px;
|
||||
--td-comp-size-xxxs: var(--td-size-6);
|
||||
--td-comp-size-xxs: var(--td-size-7);
|
||||
--td-comp-size-xs: var(--td-size-8);
|
||||
--td-comp-size-s: var(--td-size-9);
|
||||
--td-comp-size-m: var(--td-size-10);
|
||||
--td-comp-size-l: var(--td-size-11);
|
||||
--td-comp-size-xl: var(--td-size-12);
|
||||
--td-comp-size-xxl: var(--td-size-13);
|
||||
--td-comp-size-xxxl: var(--td-size-14);
|
||||
--td-comp-size-xxxxl: var(--td-size-15);
|
||||
--td-comp-size-xxxxxl: var(--td-size-16);
|
||||
--td-pop-padding-s: var(--td-size-2);
|
||||
--td-pop-padding-m: var(--td-size-3);
|
||||
--td-pop-padding-l: var(--td-size-4);
|
||||
--td-pop-padding-xl: var(--td-size-5);
|
||||
--td-pop-padding-xxl: var(--td-size-6);
|
||||
--td-comp-paddingLR-xxs: var(--td-size-1);
|
||||
--td-comp-paddingLR-xs: var(--td-size-2);
|
||||
--td-comp-paddingLR-s: var(--td-size-4);
|
||||
--td-comp-paddingLR-m: var(--td-size-5);
|
||||
--td-comp-paddingLR-l: var(--td-size-6);
|
||||
--td-comp-paddingLR-xl: var(--td-size-8);
|
||||
--td-comp-paddingLR-xxl: var(--td-size-10);
|
||||
--td-comp-paddingTB-xxs: var(--td-size-1);
|
||||
--td-comp-paddingTB-xs: var(--td-size-2);
|
||||
--td-comp-paddingTB-s: var(--td-size-4);
|
||||
--td-comp-paddingTB-m: var(--td-size-5);
|
||||
--td-comp-paddingTB-l: var(--td-size-6);
|
||||
--td-comp-paddingTB-xl: var(--td-size-8);
|
||||
--td-comp-paddingTB-xxl: var(--td-size-10);
|
||||
--td-comp-margin-xxs: var(--td-size-1);
|
||||
--td-comp-margin-xs: var(--td-size-2);
|
||||
--td-comp-margin-s: var(--td-size-4);
|
||||
--td-comp-margin-m: var(--td-size-5);
|
||||
--td-comp-margin-l: var(--td-size-6);
|
||||
--td-comp-margin-xl: var(--td-size-7);
|
||||
--td-comp-margin-xxl: var(--td-size-8);
|
||||
--td-comp-margin-xxxl: var(--td-size-10);
|
||||
--td-comp-margin-xxxxl: var(--td-size-12);
|
||||
}
|
||||
355
src/renderer/src/assets/theme/orange.css
Normal file
@@ -0,0 +1,355 @@
|
||||
:root[theme-mode="orange"] {
|
||||
--td-brand-color-1: #fff1ea;
|
||||
--td-brand-color-2: #ffd9c5;
|
||||
--td-brand-color-3: #ffb991;
|
||||
--td-brand-color-4: #fb9458;
|
||||
--td-brand-color-5: #e47228;
|
||||
--td-brand-color-6: #c05708;
|
||||
--td-brand-color-7: #9a4200;
|
||||
--td-brand-color-8: #753000;
|
||||
--td-brand-color-9: #552100;
|
||||
--td-brand-color-10: #3d1600;
|
||||
--td-brand-color-light: var(--td-brand-color-1);
|
||||
--td-brand-color-focus: var(--td-brand-color-2);
|
||||
--td-brand-color-disabled: var(--td-brand-color-3);
|
||||
--td-brand-color-hover: var(--td-brand-color-3);
|
||||
--td-brand-color: var(--td-brand-color-4);
|
||||
--td-brand-color-active: var(--td-brand-color-5);
|
||||
--td-warning-color-1: #fef3e6;
|
||||
--td-warning-color-2: #f9e0c7;
|
||||
--td-warning-color-3: #f7c797;
|
||||
--td-warning-color-4: #f2995f;
|
||||
--td-warning-color-5: #ed7b2f;
|
||||
--td-warning-color-6: #d35a21;
|
||||
--td-warning-color-7: #ba431b;
|
||||
--td-warning-color-8: #9e3610;
|
||||
--td-warning-color-9: #842b0b;
|
||||
--td-warning-color-10: #5a1907;
|
||||
--td-warning-color: var(--td-warning-color-5);
|
||||
--td-warning-color-hover: var(--td-warning-color-4);
|
||||
--td-warning-color-focus: var(--td-warning-color-2);
|
||||
--td-warning-color-active: var(--td-warning-color-6);
|
||||
--td-warning-color-disabled: var(--td-warning-color-3);
|
||||
--td-warning-color-light: var(--td-warning-color-1);
|
||||
--td-error-color-1: #fdecee;
|
||||
--td-error-color-2: #f9d7d9;
|
||||
--td-error-color-3: #f8b9be;
|
||||
--td-error-color-4: #f78d94;
|
||||
--td-error-color-5: #f36d78;
|
||||
--td-error-color-6: #e34d59;
|
||||
--td-error-color-7: #c9353f;
|
||||
--td-error-color-8: #b11f26;
|
||||
--td-error-color-9: #951114;
|
||||
--td-error-color-10: #680506;
|
||||
--td-error-color: var(--td-error-color-6);
|
||||
--td-error-color-hover: var(--td-error-color-5);
|
||||
--td-error-color-focus: var(--td-error-color-2);
|
||||
--td-error-color-active: var(--td-error-color-7);
|
||||
--td-error-color-disabled: var(--td-error-color-3);
|
||||
--td-error-color-light: var(--td-error-color-1);
|
||||
--td-success-color-1: #e8f8f2;
|
||||
--td-success-color-2: #bcebdc;
|
||||
--td-success-color-3: #85dbbe;
|
||||
--td-success-color-4: #48c79c;
|
||||
--td-success-color-5: #00a870;
|
||||
--td-success-color-6: #078d5c;
|
||||
--td-success-color-7: #067945;
|
||||
--td-success-color-8: #056334;
|
||||
--td-success-color-9: #044f2a;
|
||||
--td-success-color-10: #033017;
|
||||
--td-success-color: var(--td-success-color-5);
|
||||
--td-success-color-hover: var(--td-success-color-4);
|
||||
--td-success-color-focus: var(--td-success-color-2);
|
||||
--td-success-color-active: var(--td-success-color-6);
|
||||
--td-success-color-disabled: var(--td-success-color-3);
|
||||
--td-success-color-light: var(--td-success-color-1);
|
||||
--td-gray-color-1: #f3f3f3;
|
||||
--td-gray-color-2: #eee;
|
||||
--td-gray-color-3: #e8e8e8;
|
||||
--td-gray-color-4: #ddd;
|
||||
--td-gray-color-5: #c5c5c5;
|
||||
--td-gray-color-6: #a6a6a6;
|
||||
--td-gray-color-7: #8b8b8b;
|
||||
--td-gray-color-8: #777;
|
||||
--td-gray-color-9: #5e5e5e;
|
||||
--td-gray-color-10: #4b4b4b;
|
||||
--td-gray-color-11: #383838;
|
||||
--td-gray-color-12: #2c2c2c;
|
||||
--td-gray-color-13: #242424;
|
||||
--td-gray-color-14: #181818;
|
||||
--td-bg-color-container: #fff;
|
||||
--td-bg-color-container-select: #fff;
|
||||
--td-bg-color-page: var(--td-gray-color-2);
|
||||
--td-bg-color-container-hover: var(--td-gray-color-1);
|
||||
--td-bg-color-container-active: var(--td-gray-color-3);
|
||||
--td-bg-color-secondarycontainer: var(--td-gray-color-1);
|
||||
--td-bg-color-secondarycontainer-hover: var(--td-gray-color-2);
|
||||
--td-bg-color-secondarycontainer-active: var(--td-gray-color-4);
|
||||
--td-bg-color-component: var(--td-gray-color-3);
|
||||
--td-bg-color-component-hover: var(--td-gray-color-4);
|
||||
--td-bg-color-component-active: var(--td-gray-color-6);
|
||||
--td-bg-color-component-disabled: var(--td-gray-color-2);
|
||||
--td-component-stroke: var(--td-gray-color-3);
|
||||
--td-component-border: var(--td-gray-color-4);
|
||||
--td-font-white-1: #ffffff;
|
||||
--td-font-white-2: rgba(255, 255, 255, 0.55);
|
||||
--td-font-white-3: rgba(255, 255, 255, 0.35);
|
||||
--td-font-white-4: rgba(255, 255, 255, 0.22);
|
||||
--td-font-gray-1: rgba(0, 0, 0, 0.9);
|
||||
--td-font-gray-2: rgba(0, 0, 0, 0.6);
|
||||
--td-font-gray-3: rgba(0, 0, 0, 0.4);
|
||||
--td-font-gray-4: rgba(0, 0, 0, 0.26);
|
||||
--td-text-color-primary: var(--td-font-gray-1);
|
||||
--td-text-color-secondary: var(--td-font-gray-2);
|
||||
--td-text-color-placeholder: var(--td-font-gray-3);
|
||||
--td-text-color-disabled: var(--td-font-gray-4);
|
||||
--td-text-color-anti: #fff;
|
||||
--td-text-color-brand: var(--td-brand-color);
|
||||
--td-text-color-link: var(--td-brand-color);
|
||||
--td-brand-color-light-hover: var(--td-brand-color-2);
|
||||
--td-warning-color-light-hover: var(--td-warning-color-2);
|
||||
--td-error-color-light-hover: var(--td-error-color-2);
|
||||
--td-success-color-light-hover: var(--td-success-color-2);
|
||||
--td-bg-color-secondarycomponent: var(--td-gray-color-4);
|
||||
--td-bg-color-secondarycomponent-hover: var(--td-gray-color-5);
|
||||
--td-bg-color-secondarycomponent-active: var(--td-gray-color-6);
|
||||
--td-table-shadow-color: rgba(0, 0, 0, 8%);
|
||||
--td-scrollbar-color: rgba(0, 0, 0, 10%);
|
||||
--td-scrollbar-hover-color: rgba(0, 0, 0, 30%);
|
||||
--td-scroll-track-color: #fff;
|
||||
--td-bg-color-specialcomponent: #fff;
|
||||
--td-border-level-1-color: var(--td-gray-color-3);
|
||||
--td-border-level-2-color: var(--td-gray-color-4);
|
||||
--td-shadow-1: 0 1px 10px rgba(0, 0, 0, 5%), 0 4px 5px rgba(0, 0, 0, 8%), 0 2px 4px -1px rgba(0, 0, 0, 12%);
|
||||
--td-shadow-2: 0 3px 14px 2px rgba(0, 0, 0, 5%), 0 8px 10px 1px rgba(0, 0, 0, 6%), 0 5px 5px -3px rgba(0, 0, 0, 10%);
|
||||
--td-shadow-3: 0 6px 30px 5px rgba(0, 0, 0, 5%), 0 16px 24px 2px rgba(0, 0, 0, 4%), 0 8px 10px -5px rgba(0, 0, 0, 8%);
|
||||
--td-shadow-inset-top: inset 0 0.5px 0 #dcdcdc;
|
||||
--td-shadow-inset-right: inset 0.5px 0 0 #dcdcdc;
|
||||
--td-shadow-inset-bottom: inset 0 -0.5px 0 #dcdcdc;
|
||||
--td-shadow-inset-left: inset -0.5px 0 0 #dcdcdc;
|
||||
--td-mask-active: rgba(0, 0, 0, 0.6);
|
||||
--td-mask-disabled: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
:root[theme-mode="dark"] {
|
||||
--td-brand-color-1: #e4722820;
|
||||
--td-brand-color-2: #552100;
|
||||
--td-brand-color-3: #753000;
|
||||
--td-brand-color-4: #9a4200;
|
||||
--td-brand-color-5: #c05708;
|
||||
--td-brand-color-6: #e47228;
|
||||
--td-brand-color-7: #fd853a;
|
||||
--td-brand-color-8: #ffb991;
|
||||
--td-brand-color-9: #ffd9c5;
|
||||
--td-brand-color-10: #fff1ea;
|
||||
--td-brand-color-light: var(--td-brand-color-1);
|
||||
--td-brand-color-focus: var(--td-brand-color-2);
|
||||
--td-brand-color-disabled: var(--td-brand-color-3);
|
||||
--td-brand-color-hover: var(--td-brand-color-5);
|
||||
--td-brand-color: var(--td-brand-color-6);
|
||||
--td-brand-color-active: var(--td-brand-color-7);
|
||||
--td-warning-color-1: #4f2a1d;
|
||||
--td-warning-color-2: #582f21;
|
||||
--td-warning-color-3: #733c23;
|
||||
--td-warning-color-4: #a75d2b;
|
||||
--td-warning-color-5: #cf6e2d;
|
||||
--td-warning-color-6: #dc7633;
|
||||
--td-warning-color-7: #e8935c;
|
||||
--td-warning-color-8: #ecbf91;
|
||||
--td-warning-color-9: #eed7bf;
|
||||
--td-warning-color-10: #f3e9dc;
|
||||
--td-error-color-1: #472324;
|
||||
--td-error-color-2: #5e2a2d;
|
||||
--td-error-color-3: #703439;
|
||||
--td-error-color-4: #83383e;
|
||||
--td-error-color-5: #a03f46;
|
||||
--td-error-color-6: #c64751;
|
||||
--td-error-color-7: #de6670;
|
||||
--td-error-color-8: #ec888e;
|
||||
--td-error-color-9: #edb1b6;
|
||||
--td-error-color-10: #eeced0;
|
||||
--td-success-color-1: #193a2a;
|
||||
--td-success-color-2: #1a4230;
|
||||
--td-success-color-3: #17533d;
|
||||
--td-success-color-4: #0d7a55;
|
||||
--td-success-color-5: #059465;
|
||||
--td-success-color-6: #43af8a;
|
||||
--td-success-color-7: #46bf96;
|
||||
--td-success-color-8: #80d2b6;
|
||||
--td-success-color-9: #b4e1d3;
|
||||
--td-success-color-10: #deede8;
|
||||
--td-gray-color-1: #f3f3f3;
|
||||
--td-gray-color-2: #eee;
|
||||
--td-gray-color-3: #e8e8e8;
|
||||
--td-gray-color-4: #ddd;
|
||||
--td-gray-color-5: #c5c5c5;
|
||||
--td-gray-color-6: #a6a6a6;
|
||||
--td-gray-color-7: #8b8b8b;
|
||||
--td-gray-color-8: #777;
|
||||
--td-gray-color-9: #5e5e5e;
|
||||
--td-gray-color-10: #4b4b4b;
|
||||
--td-gray-color-11: #383838;
|
||||
--td-gray-color-12: #2c2c2c;
|
||||
--td-gray-color-13: #242424;
|
||||
--td-gray-color-14: #181818;
|
||||
--td-bg-color-page: var(--td-gray-color-14);
|
||||
--td-bg-color-container: var(--td-gray-color-13);
|
||||
--td-bg-color-container-hover: var(--td-gray-color-12);
|
||||
--td-bg-color-container-active: var(--td-gray-color-10);
|
||||
--td-bg-color-container-select: var(--td-gray-color-9);
|
||||
--td-bg-color-secondarycontainer: var(--td-gray-color-12);
|
||||
--td-bg-color-secondarycontainer-hover: var(--td-gray-color-11);
|
||||
--td-bg-color-secondarycontainer-active: var(--td-gray-color-9);
|
||||
--td-bg-color-component: var(--td-gray-color-11);
|
||||
--td-bg-color-component-hover: var(--td-gray-color-10);
|
||||
--td-bg-color-component-active: var(--td-gray-color-9);
|
||||
--td-bg-color-component-disabled: var(--td-gray-color-12);
|
||||
--td-component-stroke: var(--td-gray-color-11);
|
||||
--td-component-border: var(--td-gray-color-9);
|
||||
--td-font-white-1: rgba(255, 255, 255, 0.9);
|
||||
--td-font-white-2: rgba(255, 255, 255, 0.55);
|
||||
--td-font-white-3: rgba(255, 255, 255, 0.35);
|
||||
--td-font-white-4: rgba(255, 255, 255, 0.22);
|
||||
--td-font-gray-1: rgba(0, 0, 0, 0.9);
|
||||
--td-font-gray-2: rgba(0, 0, 0, 0.6);
|
||||
--td-font-gray-3: rgba(0, 0, 0, 0.4);
|
||||
--td-font-gray-4: rgba(0, 0, 0, 0.26);
|
||||
--td-text-color-primary: var(--td-font-white-1);
|
||||
--td-text-color-secondary: var(--td-font-white-2);
|
||||
--td-text-color-placeholder: var(--td-font-white-3);
|
||||
--td-text-color-disabled: var(--td-font-white-4);
|
||||
--td-text-color-anti: #fff;
|
||||
--td-text-color-brand: var(--td-brand-color);
|
||||
--td-text-color-link: var(--td-brand-color);
|
||||
--td-shadow-1: 0 4px 6px rgba(0, 0, 0, 0.06), 0 1px 10px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.12);
|
||||
--td-shadow-2: 0 8px 10px rgba(0, 0, 0, 0.12), 0 3px 14px rgba(0, 0, 0, 0.10), 0 5px 5px rgba(0, 0, 0, 0.16);
|
||||
--td-shadow-3: 0 16px 24px rgba(0, 0, 0, 0.14), 0 6px 30px rgba(0, 0, 0, 0.12), 0 8px 10px rgba(0, 0, 0, 0.20);
|
||||
--td-shadow-inset-top: inset 0 0.5px 0 #5e5e5e;
|
||||
--td-shadow-inset-right: inset 0.5px 0 0 #5e5e5e;
|
||||
--td-shadow-inset-bottom: inset 0 -0.5px 0 #5e5e5e;
|
||||
--td-shadow-inset-left: inset -0.5px 0 0 #5e5e5e;
|
||||
--td-table-shadow-color: rgba(0, 0, 0, 55%);
|
||||
--td-scrollbar-color: rgba(255, 255, 255, 10%);
|
||||
--td-scrollbar-hover-color: rgba(255, 255, 255, 30%);
|
||||
--td-scroll-track-color: #333;
|
||||
--td-bg-color-specialcomponent: transparent;
|
||||
--td-border-level-1-color: var(--td-gray-color-11);
|
||||
--td-border-level-2-color: var(--td-gray-color-9);
|
||||
--td-mask-active: rgba(0, 0, 0, 0.4);
|
||||
--td-mask-disabled: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
:root {
|
||||
--td-font-family: pingfang sc, microsoft yahei, arial regular;
|
||||
--td-font-family-medium: pingfang sc, microsoft yahei, arial medium;
|
||||
--td-font-size-link-small: 12px;
|
||||
--td-font-size-link-medium: 14px;
|
||||
--td-font-size-link-large: 16px;
|
||||
--td-font-size-mark-small: 12px;
|
||||
--td-font-size-mark-medium: 14px;
|
||||
--td-font-size-body-small: 12px;
|
||||
--td-font-size-body-medium: 14px;
|
||||
--td-font-size-body-large: 16px;
|
||||
--td-font-size-title-small: 14px;
|
||||
--td-font-size-title-medium: 16px;
|
||||
--td-font-size-title-large: 20px;
|
||||
--td-font-size-headline-small: 24px;
|
||||
--td-font-size-headline-medium: 28px;
|
||||
--td-font-size-headline-large: 36px;
|
||||
--td-font-size-display-medium: 48px;
|
||||
--td-font-size-display-large: 64px;
|
||||
--td-line-height-link-small: 20px;
|
||||
--td-line-height-link-medium: 22px;
|
||||
--td-line-height-link-large: 24px;
|
||||
--td-line-height-mark-small: 20px;
|
||||
--td-line-height-mark-medium: 22px;
|
||||
--td-line-height-body-small: 20px;
|
||||
--td-line-height-body-medium: 22px;
|
||||
--td-line-height-body-large: 24px;
|
||||
--td-line-height-title-small: 22px;
|
||||
--td-line-height-title-medium: 24px;
|
||||
--td-line-height-title-large: 28px;
|
||||
--td-line-height-headline-small: 32px;
|
||||
--td-line-height-headline-medium: 36px;
|
||||
--td-line-height-headline-large: 44px;
|
||||
--td-line-height-display-medium: 56px;
|
||||
--td-line-height-display-large: 72px;
|
||||
--td-font-link-small: var(--td-font-size-link-small) / var(--td-line-height-link-small) var(--td-font-family);
|
||||
--td-font-link-medium: var(--td-font-size-link-medium) / var(--td-line-height-link-medium) var(--td-font-family);
|
||||
--td-font-link-large: var(--td-font-size-link-large) / var(--td-line-height-link-large) var(--td-font-family);
|
||||
--td-font-mark-small: 600 var(--td-font-size-mark-small) / var(--td-line-height-mark-small) var(--td-font-family);
|
||||
--td-font-mark-medium: 600 var(--td-font-size-mark-medium) / var(--td-line-height-mark-medium) var(--td-font-family);
|
||||
--td-font-body-small: var(--td-font-size-body-small) / var(--td-line-height-body-small) var(--td-font-family);
|
||||
--td-font-body-medium: var(--td-font-size-body-medium) / var(--td-line-height-body-medium) var(--td-font-family);
|
||||
--td-font-body-large: var(--td-font-size-body-large) / var(--td-line-height-body-large) var(--td-font-family);
|
||||
--td-font-title-small: 600 var(--td-font-size-title-small) / var(--td-line-height-title-small) var(--td-font-family);
|
||||
--td-font-title-medium: 600 var(--td-font-size-title-medium) / var(--td-line-height-title-medium) var(--td-font-family);
|
||||
--td-font-title-large: 600 var(--td-font-size-title-large) / var(--td-line-height-title-large) var(--td-font-family);
|
||||
--td-font-headline-small: 600 var(--td-font-size-headline-small) / var(--td-line-height-headline-small) var(--td-font-family);
|
||||
--td-font-headline-medium: 600 var(--td-font-size-headline-medium) / var(--td-line-height-headline-medium) var(--td-font-family);
|
||||
--td-font-headline-large: 600 var(--td-font-size-headline-large) / var(--td-line-height-headline-large) var(--td-font-family);
|
||||
--td-font-display-medium: 600 var(--td-font-size-display-medium) / var(--td-line-height-display-medium) var(--td-font-family);
|
||||
--td-font-display-large: 600 var(--td-font-size-display-large) / var(--td-line-height-display-large) var(--td-font-family);
|
||||
--td-radius-small: 2px;
|
||||
--td-radius-default: 3px;
|
||||
--td-radius-medium: 6px;
|
||||
--td-radius-large: 9px;
|
||||
--td-radius-extraLarge: 12px;
|
||||
--td-radius-round: 999px;
|
||||
--td-radius-circle: 50%;
|
||||
--td-size-1: 2px;
|
||||
--td-size-2: 4px;
|
||||
--td-size-3: 6px;
|
||||
--td-size-4: 8px;
|
||||
--td-size-5: 12px;
|
||||
--td-size-6: 16px;
|
||||
--td-size-7: 20px;
|
||||
--td-size-8: 24px;
|
||||
--td-size-9: 28px;
|
||||
--td-size-10: 32px;
|
||||
--td-size-11: 36px;
|
||||
--td-size-12: 40px;
|
||||
--td-size-13: 48px;
|
||||
--td-size-14: 56px;
|
||||
--td-size-15: 64px;
|
||||
--td-size-16: 72px;
|
||||
--td-comp-size-xxxs: var(--td-size-6);
|
||||
--td-comp-size-xxs: var(--td-size-7);
|
||||
--td-comp-size-xs: var(--td-size-8);
|
||||
--td-comp-size-s: var(--td-size-9);
|
||||
--td-comp-size-m: var(--td-size-10);
|
||||
--td-comp-size-l: var(--td-size-11);
|
||||
--td-comp-size-xl: var(--td-size-12);
|
||||
--td-comp-size-xxl: var(--td-size-13);
|
||||
--td-comp-size-xxxl: var(--td-size-14);
|
||||
--td-comp-size-xxxxl: var(--td-size-15);
|
||||
--td-comp-size-xxxxxl: var(--td-size-16);
|
||||
--td-pop-padding-s: var(--td-size-2);
|
||||
--td-pop-padding-m: var(--td-size-3);
|
||||
--td-pop-padding-l: var(--td-size-4);
|
||||
--td-pop-padding-xl: var(--td-size-5);
|
||||
--td-pop-padding-xxl: var(--td-size-6);
|
||||
--td-comp-paddingLR-xxs: var(--td-size-1);
|
||||
--td-comp-paddingLR-xs: var(--td-size-2);
|
||||
--td-comp-paddingLR-s: var(--td-size-4);
|
||||
--td-comp-paddingLR-m: var(--td-size-5);
|
||||
--td-comp-paddingLR-l: var(--td-size-6);
|
||||
--td-comp-paddingLR-xl: var(--td-size-8);
|
||||
--td-comp-paddingLR-xxl: var(--td-size-10);
|
||||
--td-comp-paddingTB-xxs: var(--td-size-1);
|
||||
--td-comp-paddingTB-xs: var(--td-size-2);
|
||||
--td-comp-paddingTB-s: var(--td-size-4);
|
||||
--td-comp-paddingTB-m: var(--td-size-5);
|
||||
--td-comp-paddingTB-l: var(--td-size-6);
|
||||
--td-comp-paddingTB-xl: var(--td-size-8);
|
||||
--td-comp-paddingTB-xxl: var(--td-size-10);
|
||||
--td-comp-margin-xxs: var(--td-size-1);
|
||||
--td-comp-margin-xs: var(--td-size-2);
|
||||
--td-comp-margin-s: var(--td-size-4);
|
||||
--td-comp-margin-m: var(--td-size-5);
|
||||
--td-comp-margin-l: var(--td-size-6);
|
||||
--td-comp-margin-xl: var(--td-size-7);
|
||||
--td-comp-margin-xxl: var(--td-size-8);
|
||||
--td-comp-margin-xxxl: var(--td-size-10);
|
||||
--td-comp-margin-xxxxl: var(--td-size-12);
|
||||
}
|
||||
355
src/renderer/src/assets/theme/pink.css
Normal file
@@ -0,0 +1,355 @@
|
||||
:root[theme-mode="pink"] {
|
||||
--td-brand-color-1: #fff0f1;
|
||||
--td-brand-color-2: #ffd8dd;
|
||||
--td-brand-color-3: #ffb7c1;
|
||||
--td-brand-color-4: #ff8fa2;
|
||||
--td-brand-color-5: #fc5e7e;
|
||||
--td-brand-color-6: #db3d62;
|
||||
--td-brand-color-7: #b2294b;
|
||||
--td-brand-color-8: #8d1135;
|
||||
--td-brand-color-9: #690021;
|
||||
--td-brand-color-10: #480014;
|
||||
--td-brand-color-light: var(--td-brand-color-1);
|
||||
--td-brand-color-focus: var(--td-brand-color-2);
|
||||
--td-brand-color-disabled: var(--td-brand-color-3);
|
||||
--td-brand-color-hover: var(--td-brand-color-4);
|
||||
--td-brand-color: var(--td-brand-color-5);
|
||||
--td-brand-color-active: var(--td-brand-color-6);
|
||||
--td-warning-color-1: #fef3e6;
|
||||
--td-warning-color-2: #f9e0c7;
|
||||
--td-warning-color-3: #f7c797;
|
||||
--td-warning-color-4: #f2995f;
|
||||
--td-warning-color-5: #ed7b2f;
|
||||
--td-warning-color-6: #d35a21;
|
||||
--td-warning-color-7: #ba431b;
|
||||
--td-warning-color-8: #9e3610;
|
||||
--td-warning-color-9: #842b0b;
|
||||
--td-warning-color-10: #5a1907;
|
||||
--td-warning-color: var(--td-warning-color-5);
|
||||
--td-warning-color-hover: var(--td-warning-color-4);
|
||||
--td-warning-color-focus: var(--td-warning-color-2);
|
||||
--td-warning-color-active: var(--td-warning-color-6);
|
||||
--td-warning-color-disabled: var(--td-warning-color-3);
|
||||
--td-warning-color-light: var(--td-warning-color-1);
|
||||
--td-error-color-1: #fdecee;
|
||||
--td-error-color-2: #f9d7d9;
|
||||
--td-error-color-3: #f8b9be;
|
||||
--td-error-color-4: #f78d94;
|
||||
--td-error-color-5: #f36d78;
|
||||
--td-error-color-6: #e34d59;
|
||||
--td-error-color-7: #c9353f;
|
||||
--td-error-color-8: #b11f26;
|
||||
--td-error-color-9: #951114;
|
||||
--td-error-color-10: #680506;
|
||||
--td-error-color: var(--td-error-color-6);
|
||||
--td-error-color-hover: var(--td-error-color-5);
|
||||
--td-error-color-focus: var(--td-error-color-2);
|
||||
--td-error-color-active: var(--td-error-color-7);
|
||||
--td-error-color-disabled: var(--td-error-color-3);
|
||||
--td-error-color-light: var(--td-error-color-1);
|
||||
--td-success-color-1: #e8f8f2;
|
||||
--td-success-color-2: #bcebdc;
|
||||
--td-success-color-3: #85dbbe;
|
||||
--td-success-color-4: #48c79c;
|
||||
--td-success-color-5: #00a870;
|
||||
--td-success-color-6: #078d5c;
|
||||
--td-success-color-7: #067945;
|
||||
--td-success-color-8: #056334;
|
||||
--td-success-color-9: #044f2a;
|
||||
--td-success-color-10: #033017;
|
||||
--td-success-color: var(--td-success-color-5);
|
||||
--td-success-color-hover: var(--td-success-color-4);
|
||||
--td-success-color-focus: var(--td-success-color-2);
|
||||
--td-success-color-active: var(--td-success-color-6);
|
||||
--td-success-color-disabled: var(--td-success-color-3);
|
||||
--td-success-color-light: var(--td-success-color-1);
|
||||
--td-gray-color-1: #f3f3f3;
|
||||
--td-gray-color-2: #eee;
|
||||
--td-gray-color-3: #e8e8e8;
|
||||
--td-gray-color-4: #ddd;
|
||||
--td-gray-color-5: #c5c5c5;
|
||||
--td-gray-color-6: #a6a6a6;
|
||||
--td-gray-color-7: #8b8b8b;
|
||||
--td-gray-color-8: #777;
|
||||
--td-gray-color-9: #5e5e5e;
|
||||
--td-gray-color-10: #4b4b4b;
|
||||
--td-gray-color-11: #383838;
|
||||
--td-gray-color-12: #2c2c2c;
|
||||
--td-gray-color-13: #242424;
|
||||
--td-gray-color-14: #181818;
|
||||
--td-bg-color-container: #fff;
|
||||
--td-bg-color-container-select: #fff;
|
||||
--td-bg-color-page: var(--td-gray-color-2);
|
||||
--td-bg-color-container-hover: var(--td-gray-color-1);
|
||||
--td-bg-color-container-active: var(--td-gray-color-3);
|
||||
--td-bg-color-secondarycontainer: var(--td-gray-color-1);
|
||||
--td-bg-color-secondarycontainer-hover: var(--td-gray-color-2);
|
||||
--td-bg-color-secondarycontainer-active: var(--td-gray-color-4);
|
||||
--td-bg-color-component: var(--td-gray-color-3);
|
||||
--td-bg-color-component-hover: var(--td-gray-color-4);
|
||||
--td-bg-color-component-active: var(--td-gray-color-6);
|
||||
--td-bg-color-component-disabled: var(--td-gray-color-2);
|
||||
--td-component-stroke: var(--td-gray-color-3);
|
||||
--td-component-border: var(--td-gray-color-4);
|
||||
--td-font-white-1: #ffffff;
|
||||
--td-font-white-2: rgba(255, 255, 255, 0.55);
|
||||
--td-font-white-3: rgba(255, 255, 255, 0.35);
|
||||
--td-font-white-4: rgba(255, 255, 255, 0.22);
|
||||
--td-font-gray-1: rgba(0, 0, 0, 0.9);
|
||||
--td-font-gray-2: rgba(0, 0, 0, 0.6);
|
||||
--td-font-gray-3: rgba(0, 0, 0, 0.4);
|
||||
--td-font-gray-4: rgba(0, 0, 0, 0.26);
|
||||
--td-text-color-primary: var(--td-font-gray-1);
|
||||
--td-text-color-secondary: var(--td-font-gray-2);
|
||||
--td-text-color-placeholder: var(--td-font-gray-3);
|
||||
--td-text-color-disabled: var(--td-font-gray-4);
|
||||
--td-text-color-anti: #fff;
|
||||
--td-text-color-brand: var(--td-brand-color);
|
||||
--td-text-color-link: var(--td-brand-color);
|
||||
--td-brand-color-light-hover: var(--td-brand-color-2);
|
||||
--td-warning-color-light-hover: var(--td-warning-color-2);
|
||||
--td-error-color-light-hover: var(--td-error-color-2);
|
||||
--td-success-color-light-hover: var(--td-success-color-2);
|
||||
--td-bg-color-secondarycomponent: var(--td-gray-color-4);
|
||||
--td-bg-color-secondarycomponent-hover: var(--td-gray-color-5);
|
||||
--td-bg-color-secondarycomponent-active: var(--td-gray-color-6);
|
||||
--td-table-shadow-color: rgba(0, 0, 0, 8%);
|
||||
--td-scrollbar-color: rgba(0, 0, 0, 10%);
|
||||
--td-scrollbar-hover-color: rgba(0, 0, 0, 30%);
|
||||
--td-scroll-track-color: #fff;
|
||||
--td-bg-color-specialcomponent: #fff;
|
||||
--td-border-level-1-color: var(--td-gray-color-3);
|
||||
--td-border-level-2-color: var(--td-gray-color-4);
|
||||
--td-shadow-1: 0 1px 10px rgba(0, 0, 0, 5%), 0 4px 5px rgba(0, 0, 0, 8%), 0 2px 4px -1px rgba(0, 0, 0, 12%);
|
||||
--td-shadow-2: 0 3px 14px 2px rgba(0, 0, 0, 5%), 0 8px 10px 1px rgba(0, 0, 0, 6%), 0 5px 5px -3px rgba(0, 0, 0, 10%);
|
||||
--td-shadow-3: 0 6px 30px 5px rgba(0, 0, 0, 5%), 0 16px 24px 2px rgba(0, 0, 0, 4%), 0 8px 10px -5px rgba(0, 0, 0, 8%);
|
||||
--td-shadow-inset-top: inset 0 0.5px 0 #dcdcdc;
|
||||
--td-shadow-inset-right: inset 0.5px 0 0 #dcdcdc;
|
||||
--td-shadow-inset-bottom: inset 0 -0.5px 0 #dcdcdc;
|
||||
--td-shadow-inset-left: inset -0.5px 0 0 #dcdcdc;
|
||||
--td-mask-active: rgba(0, 0, 0, 0.6);
|
||||
--td-mask-disabled: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
:root[theme-mode="dark"] {
|
||||
--td-brand-color-1: #ff547920;
|
||||
--td-brand-color-2: #690021;
|
||||
--td-brand-color-3: #8d1135;
|
||||
--td-brand-color-4: #b2294b;
|
||||
--td-brand-color-5: #db3d62;
|
||||
--td-brand-color-6: #ff5479;
|
||||
--td-brand-color-7: #ff8fa2;
|
||||
--td-brand-color-8: #ffb7c1;
|
||||
--td-brand-color-9: #ffd8dd;
|
||||
--td-brand-color-10: #fff0f1;
|
||||
--td-brand-color-light: var(--td-brand-color-1);
|
||||
--td-brand-color-focus: var(--td-brand-color-2);
|
||||
--td-brand-color-disabled: var(--td-brand-color-3);
|
||||
--td-brand-color-hover: var(--td-brand-color-5);
|
||||
--td-brand-color: var(--td-brand-color-6);
|
||||
--td-brand-color-active: var(--td-brand-color-7);
|
||||
--td-warning-color-1: #4f2a1d;
|
||||
--td-warning-color-2: #582f21;
|
||||
--td-warning-color-3: #733c23;
|
||||
--td-warning-color-4: #a75d2b;
|
||||
--td-warning-color-5: #cf6e2d;
|
||||
--td-warning-color-6: #dc7633;
|
||||
--td-warning-color-7: #e8935c;
|
||||
--td-warning-color-8: #ecbf91;
|
||||
--td-warning-color-9: #eed7bf;
|
||||
--td-warning-color-10: #f3e9dc;
|
||||
--td-error-color-1: #472324;
|
||||
--td-error-color-2: #5e2a2d;
|
||||
--td-error-color-3: #703439;
|
||||
--td-error-color-4: #83383e;
|
||||
--td-error-color-5: #a03f46;
|
||||
--td-error-color-6: #c64751;
|
||||
--td-error-color-7: #de6670;
|
||||
--td-error-color-8: #ec888e;
|
||||
--td-error-color-9: #edb1b6;
|
||||
--td-error-color-10: #eeced0;
|
||||
--td-success-color-1: #193a2a;
|
||||
--td-success-color-2: #1a4230;
|
||||
--td-success-color-3: #17533d;
|
||||
--td-success-color-4: #0d7a55;
|
||||
--td-success-color-5: #059465;
|
||||
--td-success-color-6: #43af8a;
|
||||
--td-success-color-7: #46bf96;
|
||||
--td-success-color-8: #80d2b6;
|
||||
--td-success-color-9: #b4e1d3;
|
||||
--td-success-color-10: #deede8;
|
||||
--td-gray-color-1: #f3f3f3;
|
||||
--td-gray-color-2: #eee;
|
||||
--td-gray-color-3: #e8e8e8;
|
||||
--td-gray-color-4: #ddd;
|
||||
--td-gray-color-5: #c5c5c5;
|
||||
--td-gray-color-6: #a6a6a6;
|
||||
--td-gray-color-7: #8b8b8b;
|
||||
--td-gray-color-8: #777;
|
||||
--td-gray-color-9: #5e5e5e;
|
||||
--td-gray-color-10: #4b4b4b;
|
||||
--td-gray-color-11: #383838;
|
||||
--td-gray-color-12: #2c2c2c;
|
||||
--td-gray-color-13: #242424;
|
||||
--td-gray-color-14: #181818;
|
||||
--td-bg-color-page: var(--td-gray-color-14);
|
||||
--td-bg-color-container: var(--td-gray-color-13);
|
||||
--td-bg-color-container-hover: var(--td-gray-color-12);
|
||||
--td-bg-color-container-active: var(--td-gray-color-10);
|
||||
--td-bg-color-container-select: var(--td-gray-color-9);
|
||||
--td-bg-color-secondarycontainer: var(--td-gray-color-12);
|
||||
--td-bg-color-secondarycontainer-hover: var(--td-gray-color-11);
|
||||
--td-bg-color-secondarycontainer-active: var(--td-gray-color-9);
|
||||
--td-bg-color-component: var(--td-gray-color-11);
|
||||
--td-bg-color-component-hover: var(--td-gray-color-10);
|
||||
--td-bg-color-component-active: var(--td-gray-color-9);
|
||||
--td-bg-color-component-disabled: var(--td-gray-color-12);
|
||||
--td-component-stroke: var(--td-gray-color-11);
|
||||
--td-component-border: var(--td-gray-color-9);
|
||||
--td-font-white-1: rgba(255, 255, 255, 0.9);
|
||||
--td-font-white-2: rgba(255, 255, 255, 0.55);
|
||||
--td-font-white-3: rgba(255, 255, 255, 0.35);
|
||||
--td-font-white-4: rgba(255, 255, 255, 0.22);
|
||||
--td-font-gray-1: rgba(0, 0, 0, 0.9);
|
||||
--td-font-gray-2: rgba(0, 0, 0, 0.6);
|
||||
--td-font-gray-3: rgba(0, 0, 0, 0.4);
|
||||
--td-font-gray-4: rgba(0, 0, 0, 0.26);
|
||||
--td-text-color-primary: var(--td-font-white-1);
|
||||
--td-text-color-secondary: var(--td-font-white-2);
|
||||
--td-text-color-placeholder: var(--td-font-white-3);
|
||||
--td-text-color-disabled: var(--td-font-white-4);
|
||||
--td-text-color-anti: #fff;
|
||||
--td-text-color-brand: var(--td-brand-color);
|
||||
--td-text-color-link: var(--td-brand-color);
|
||||
--td-shadow-1: 0 4px 6px rgba(0, 0, 0, 0.06), 0 1px 10px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.12);
|
||||
--td-shadow-2: 0 8px 10px rgba(0, 0, 0, 0.12), 0 3px 14px rgba(0, 0, 0, 0.10), 0 5px 5px rgba(0, 0, 0, 0.16);
|
||||
--td-shadow-3: 0 16px 24px rgba(0, 0, 0, 0.14), 0 6px 30px rgba(0, 0, 0, 0.12), 0 8px 10px rgba(0, 0, 0, 0.20);
|
||||
--td-shadow-inset-top: inset 0 0.5px 0 #5e5e5e;
|
||||
--td-shadow-inset-right: inset 0.5px 0 0 #5e5e5e;
|
||||
--td-shadow-inset-bottom: inset 0 -0.5px 0 #5e5e5e;
|
||||
--td-shadow-inset-left: inset -0.5px 0 0 #5e5e5e;
|
||||
--td-table-shadow-color: rgba(0, 0, 0, 55%);
|
||||
--td-scrollbar-color: rgba(255, 255, 255, 10%);
|
||||
--td-scrollbar-hover-color: rgba(255, 255, 255, 30%);
|
||||
--td-scroll-track-color: #333;
|
||||
--td-bg-color-specialcomponent: transparent;
|
||||
--td-border-level-1-color: var(--td-gray-color-11);
|
||||
--td-border-level-2-color: var(--td-gray-color-9);
|
||||
--td-mask-active: rgba(0, 0, 0, 0.4);
|
||||
--td-mask-disabled: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
:root {
|
||||
--td-font-family: pingfang sc, microsoft yahei, arial regular;
|
||||
--td-font-family-medium: pingfang sc, microsoft yahei, arial medium;
|
||||
--td-font-size-link-small: 12px;
|
||||
--td-font-size-link-medium: 14px;
|
||||
--td-font-size-link-large: 16px;
|
||||
--td-font-size-mark-small: 12px;
|
||||
--td-font-size-mark-medium: 14px;
|
||||
--td-font-size-body-small: 12px;
|
||||
--td-font-size-body-medium: 14px;
|
||||
--td-font-size-body-large: 16px;
|
||||
--td-font-size-title-small: 14px;
|
||||
--td-font-size-title-medium: 16px;
|
||||
--td-font-size-title-large: 20px;
|
||||
--td-font-size-headline-small: 24px;
|
||||
--td-font-size-headline-medium: 28px;
|
||||
--td-font-size-headline-large: 36px;
|
||||
--td-font-size-display-medium: 48px;
|
||||
--td-font-size-display-large: 64px;
|
||||
--td-line-height-link-small: 20px;
|
||||
--td-line-height-link-medium: 22px;
|
||||
--td-line-height-link-large: 24px;
|
||||
--td-line-height-mark-small: 20px;
|
||||
--td-line-height-mark-medium: 22px;
|
||||
--td-line-height-body-small: 20px;
|
||||
--td-line-height-body-medium: 22px;
|
||||
--td-line-height-body-large: 24px;
|
||||
--td-line-height-title-small: 22px;
|
||||
--td-line-height-title-medium: 24px;
|
||||
--td-line-height-title-large: 28px;
|
||||
--td-line-height-headline-small: 32px;
|
||||
--td-line-height-headline-medium: 36px;
|
||||
--td-line-height-headline-large: 44px;
|
||||
--td-line-height-display-medium: 56px;
|
||||
--td-line-height-display-large: 72px;
|
||||
--td-font-link-small: var(--td-font-size-link-small) / var(--td-line-height-link-small) var(--td-font-family);
|
||||
--td-font-link-medium: var(--td-font-size-link-medium) / var(--td-line-height-link-medium) var(--td-font-family);
|
||||
--td-font-link-large: var(--td-font-size-link-large) / var(--td-line-height-link-large) var(--td-font-family);
|
||||
--td-font-mark-small: 600 var(--td-font-size-mark-small) / var(--td-line-height-mark-small) var(--td-font-family);
|
||||
--td-font-mark-medium: 600 var(--td-font-size-mark-medium) / var(--td-line-height-mark-medium) var(--td-font-family);
|
||||
--td-font-body-small: var(--td-font-size-body-small) / var(--td-line-height-body-small) var(--td-font-family);
|
||||
--td-font-body-medium: var(--td-font-size-body-medium) / var(--td-line-height-body-medium) var(--td-font-family);
|
||||
--td-font-body-large: var(--td-font-size-body-large) / var(--td-line-height-body-large) var(--td-font-family);
|
||||
--td-font-title-small: 600 var(--td-font-size-title-small) / var(--td-line-height-title-small) var(--td-font-family);
|
||||
--td-font-title-medium: 600 var(--td-font-size-title-medium) / var(--td-line-height-title-medium) var(--td-font-family);
|
||||
--td-font-title-large: 600 var(--td-font-size-title-large) / var(--td-line-height-title-large) var(--td-font-family);
|
||||
--td-font-headline-small: 600 var(--td-font-size-headline-small) / var(--td-line-height-headline-small) var(--td-font-family);
|
||||
--td-font-headline-medium: 600 var(--td-font-size-headline-medium) / var(--td-line-height-headline-medium) var(--td-font-family);
|
||||
--td-font-headline-large: 600 var(--td-font-size-headline-large) / var(--td-line-height-headline-large) var(--td-font-family);
|
||||
--td-font-display-medium: 600 var(--td-font-size-display-medium) / var(--td-line-height-display-medium) var(--td-font-family);
|
||||
--td-font-display-large: 600 var(--td-font-size-display-large) / var(--td-line-height-display-large) var(--td-font-family);
|
||||
--td-radius-small: 2px;
|
||||
--td-radius-default: 3px;
|
||||
--td-radius-medium: 6px;
|
||||
--td-radius-large: 9px;
|
||||
--td-radius-extraLarge: 12px;
|
||||
--td-radius-round: 999px;
|
||||
--td-radius-circle: 50%;
|
||||
--td-size-1: 2px;
|
||||
--td-size-2: 4px;
|
||||
--td-size-3: 6px;
|
||||
--td-size-4: 8px;
|
||||
--td-size-5: 12px;
|
||||
--td-size-6: 16px;
|
||||
--td-size-7: 20px;
|
||||
--td-size-8: 24px;
|
||||
--td-size-9: 28px;
|
||||
--td-size-10: 32px;
|
||||
--td-size-11: 36px;
|
||||
--td-size-12: 40px;
|
||||
--td-size-13: 48px;
|
||||
--td-size-14: 56px;
|
||||
--td-size-15: 64px;
|
||||
--td-size-16: 72px;
|
||||
--td-comp-size-xxxs: var(--td-size-6);
|
||||
--td-comp-size-xxs: var(--td-size-7);
|
||||
--td-comp-size-xs: var(--td-size-8);
|
||||
--td-comp-size-s: var(--td-size-9);
|
||||
--td-comp-size-m: var(--td-size-10);
|
||||
--td-comp-size-l: var(--td-size-11);
|
||||
--td-comp-size-xl: var(--td-size-12);
|
||||
--td-comp-size-xxl: var(--td-size-13);
|
||||
--td-comp-size-xxxl: var(--td-size-14);
|
||||
--td-comp-size-xxxxl: var(--td-size-15);
|
||||
--td-comp-size-xxxxxl: var(--td-size-16);
|
||||
--td-pop-padding-s: var(--td-size-2);
|
||||
--td-pop-padding-m: var(--td-size-3);
|
||||
--td-pop-padding-l: var(--td-size-4);
|
||||
--td-pop-padding-xl: var(--td-size-5);
|
||||
--td-pop-padding-xxl: var(--td-size-6);
|
||||
--td-comp-paddingLR-xxs: var(--td-size-1);
|
||||
--td-comp-paddingLR-xs: var(--td-size-2);
|
||||
--td-comp-paddingLR-s: var(--td-size-4);
|
||||
--td-comp-paddingLR-m: var(--td-size-5);
|
||||
--td-comp-paddingLR-l: var(--td-size-6);
|
||||
--td-comp-paddingLR-xl: var(--td-size-8);
|
||||
--td-comp-paddingLR-xxl: var(--td-size-10);
|
||||
--td-comp-paddingTB-xxs: var(--td-size-1);
|
||||
--td-comp-paddingTB-xs: var(--td-size-2);
|
||||
--td-comp-paddingTB-s: var(--td-size-4);
|
||||
--td-comp-paddingTB-m: var(--td-size-5);
|
||||
--td-comp-paddingTB-l: var(--td-size-6);
|
||||
--td-comp-paddingTB-xl: var(--td-size-8);
|
||||
--td-comp-paddingTB-xxl: var(--td-size-10);
|
||||
--td-comp-margin-xxs: var(--td-size-1);
|
||||
--td-comp-margin-xs: var(--td-size-2);
|
||||
--td-comp-margin-s: var(--td-size-4);
|
||||
--td-comp-margin-m: var(--td-size-5);
|
||||
--td-comp-margin-l: var(--td-size-6);
|
||||
--td-comp-margin-xl: var(--td-size-7);
|
||||
--td-comp-margin-xxl: var(--td-size-8);
|
||||
--td-comp-margin-xxxl: var(--td-size-10);
|
||||
--td-comp-margin-xxxxl: var(--td-size-12);
|
||||
}
|
||||
@@ -59,6 +59,7 @@ onDeactivated(() => {
|
||||
const handleEnded = (): void => {
|
||||
audioStore.Audio.isPlay = false
|
||||
audioStore.publish('ended')
|
||||
console.log('eddddddddd')
|
||||
}
|
||||
|
||||
const handleSeeked = (): void => {
|
||||
|
||||
@@ -412,7 +412,7 @@ const playNext = async () => {
|
||||
|
||||
// 定期保存当前播放位置
|
||||
let savePositionInterval: number | null = null
|
||||
|
||||
let unEnded:()=>any = ()=>{}
|
||||
// 初始化播放器
|
||||
onMounted(async () => {
|
||||
console.log('加载')
|
||||
@@ -420,8 +420,10 @@ onMounted(async () => {
|
||||
initPlaylistEventListeners(localUserStore, playSong)
|
||||
|
||||
// 监听音频结束事件,根据播放模式播放下一首
|
||||
controlAudio.subscribe('ended', () => {
|
||||
playNext()
|
||||
unEnded = controlAudio.subscribe('ended', () => {
|
||||
window.requestAnimationFrame(()=>{
|
||||
playNext()
|
||||
})
|
||||
})
|
||||
|
||||
// 检查是否有上次播放的歌曲
|
||||
@@ -471,6 +473,7 @@ onUnmounted(() => {
|
||||
if (savePositionInterval !== null) {
|
||||
clearInterval(savePositionInterval)
|
||||
}
|
||||
unEnded()
|
||||
})
|
||||
|
||||
// 组件被激活时(从缓存中恢复)
|
||||
|
||||
161
src/renderer/src/components/README-ThemeSelector.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# 主题切换组件使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
ThemeSelector 是一个现代化的主题切换组件,支持在多个预设主题色之间切换。组件与现有的 TDesign 主题系统完全兼容。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 支持多种预设主题色(默认、粉色、蓝色、青色、橙色)
|
||||
- ✅ 使用 `theme-mode` 属性实现主题切换
|
||||
- ✅ 自动保存用户选择到本地存储
|
||||
- ✅ 现代化的下拉选择界面
|
||||
- ✅ 平滑的过渡动画效果
|
||||
- ✅ 响应式设计,支持移动端
|
||||
- ✅ 与 TDesign 主题系统完全兼容
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 基本使用
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<!-- 在任何需要的地方使用主题切换器 -->
|
||||
<ThemeSelector />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ThemeSelector from '@/components/ThemeSelector.vue'
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2. 在导航栏中使用
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<header class="app-header">
|
||||
<h1>应用标题</h1>
|
||||
<div class="header-actions">
|
||||
<ThemeSelector />
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 3. 在设置页面中使用
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="settings-page">
|
||||
<div class="setting-item">
|
||||
<label>主题色</label>
|
||||
<ThemeSelector />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 主题切换原理
|
||||
|
||||
组件通过以下方式实现主题切换:
|
||||
|
||||
1. **默认主题**: 移除 `theme-mode` 属性
|
||||
```javascript
|
||||
document.documentElement.removeAttribute('theme-mode')
|
||||
```
|
||||
|
||||
2. **其他主题**: 设置对应的 `theme-mode` 属性
|
||||
```javascript
|
||||
document.documentElement.setAttribute('theme-mode', 'pink')
|
||||
```
|
||||
|
||||
## 支持的主题
|
||||
|
||||
| 主题名称 | 属性值 | 主色调 |
|
||||
|---------|--------|--------|
|
||||
| 默认 | `default` | #57b4ff |
|
||||
| 粉色 | `pink` | #fc5e7e |
|
||||
| 蓝色 | `blue` | #57b4ff |
|
||||
| 青色 | `cyan` | #3ac2b8 |
|
||||
| 橙色 | `orange` | #fb9458 |
|
||||
|
||||
## 自定义配置
|
||||
|
||||
如果需要添加新的主题,请按以下步骤操作:
|
||||
|
||||
### 1. 创建主题CSS文件
|
||||
|
||||
在 `src/renderer/src/assets/theme/` 目录下创建新的主题文件,例如 `green.css`:
|
||||
|
||||
```css
|
||||
:root[theme-mode="green"] {
|
||||
--td-brand-color: #10b981;
|
||||
--td-brand-color-hover: #059669;
|
||||
/* 其他主题变量... */
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 更新组件配置
|
||||
|
||||
在 `ThemeSelector.vue` 中添加新主题:
|
||||
|
||||
```javascript
|
||||
const themes = [
|
||||
// 现有主题...
|
||||
{ name: 'green', label: '绿色', color: '#10b981' }
|
||||
]
|
||||
```
|
||||
|
||||
## 样式自定义
|
||||
|
||||
组件使用 TDesign 的 CSS 变量,可以通过覆盖这些变量来自定义样式:
|
||||
|
||||
```css
|
||||
.theme-selector {
|
||||
/* 自定义触发器样式 */
|
||||
--td-radius-medium: 8px;
|
||||
}
|
||||
|
||||
.theme-dropdown {
|
||||
/* 自定义下拉菜单样式 */
|
||||
--td-shadow-2: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
```
|
||||
|
||||
## 事件和回调
|
||||
|
||||
组件会自动处理主题切换和本地存储,无需额外配置。如果需要监听主题变化,可以监听 `localStorage` 的变化:
|
||||
|
||||
```javascript
|
||||
// 监听主题变化
|
||||
window.addEventListener('storage', (e) => {
|
||||
if (e.key === 'selected-theme') {
|
||||
console.log('主题已切换到:', e.newValue)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保项目中已引入对应的主题CSS文件
|
||||
2. 组件会自动加载用户上次选择的主题
|
||||
3. 主题切换是全局的,会影响整个应用
|
||||
4. 建议在应用的主入口处使用,避免重复渲染
|
||||
|
||||
## 演示组件
|
||||
|
||||
项目还包含一个 `ThemeDemo.vue` 组件,展示了主题切换的效果:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<ThemeDemo />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ThemeDemo from '@/components/ThemeDemo.vue'
|
||||
</script>
|
||||
```
|
||||
|
||||
这个演示组件展示了不同UI元素在各种主题下的表现。
|
||||
54
src/renderer/src/components/Settings/MusicCache.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="music-cache">
|
||||
<t-card hover-shadow :loading="cacheInfo ? false : true" title="本地歌曲缓存配置">
|
||||
<template #actions>
|
||||
已有歌曲缓存大小:{{ cacheInfo.sizeFormatted }}
|
||||
</template>
|
||||
<div class="card-body">
|
||||
<t-button size="large" @click="clearCache">
|
||||
清除本地缓存
|
||||
</t-button>
|
||||
</div>
|
||||
</t-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DialogPlugin } from 'tdesign-vue-next';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
const cacheInfo: any = ref({})
|
||||
onMounted(() => {
|
||||
window.api.musicCache.getInfo().then(res => cacheInfo.value = res)
|
||||
})
|
||||
const clearCache = () => {
|
||||
const confirm = DialogPlugin.confirm({
|
||||
header: '确认清除缓存吗',
|
||||
body: '这可能会导致歌曲加载缓慢,你确定要清除所有缓存吗?',
|
||||
confirmBtn: '确定清除',
|
||||
cancelBtn: '我再想想',
|
||||
placement:'center',
|
||||
onClose: () => {
|
||||
confirm.hide()
|
||||
},
|
||||
onConfirm: async () => {
|
||||
confirm.hide()
|
||||
cacheInfo.value = {}
|
||||
await window.api.musicCache.clear()
|
||||
window.api.musicCache.getInfo().then(res => cacheInfo.value = res)
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.music-cache {
|
||||
width: 100%;
|
||||
|
||||
.card-body {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -82,6 +82,7 @@ const handleImportFromFile = async () => {
|
||||
const importedPlaylist = await importPlaylistFromFile(uploadedFile.value)
|
||||
|
||||
if (!validateImportedPlaylist(importedPlaylist)) {
|
||||
console.log(importedPlaylist)
|
||||
throw new Error('导入的播放列表格式不正确')
|
||||
}
|
||||
|
||||
@@ -238,7 +239,7 @@ watch(
|
||||
<t-card title="播放列表统计" hover-shadow>
|
||||
<div class="stats-content">
|
||||
<div class="stat-item">
|
||||
<span class="iconfont icon-bofang"></span>
|
||||
<t-icon name="play" />
|
||||
<div class="stat-info">
|
||||
<div class="stat-label">歌曲数量</div>
|
||||
<div class="stat-value">{{ playlistStats.totalSongs }} 首</div>
|
||||
|
||||
73
src/renderer/src/components/Settings/UpdateSettings.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div class="update-settings">
|
||||
<div class="update-section">
|
||||
<h3>自动更新</h3>
|
||||
<div class="update-info">
|
||||
<p>当前版本: {{ currentVersion }}</p>
|
||||
<t-button
|
||||
theme="primary"
|
||||
:loading="isChecking"
|
||||
@click="handleCheckUpdate"
|
||||
>
|
||||
{{ isChecking ? '检查中...' : '检查更新' }}
|
||||
</t-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useAutoUpdate } from '../../composables/useAutoUpdate'
|
||||
import { Button as TButton } from 'tdesign-vue-next'
|
||||
|
||||
const { checkForUpdates } = useAutoUpdate()
|
||||
|
||||
const currentVersion = ref('1.0.9') // 从package.json获取
|
||||
const isChecking = ref(false)
|
||||
|
||||
const handleCheckUpdate = async () => {
|
||||
isChecking.value = true
|
||||
try {
|
||||
await checkForUpdates()
|
||||
} finally {
|
||||
// 延迟重置状态,给用户足够时间看到通知
|
||||
setTimeout(() => {
|
||||
isChecking.value = false
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 可以在这里获取当前版本号
|
||||
// currentVersion.value = await window.api.getAppVersion()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.update-settings {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.update-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.update-section h3 {
|
||||
margin-bottom: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--td-text-color-primary);
|
||||
}
|
||||
|
||||
.update-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.update-info p {
|
||||
margin: 0;
|
||||
color: var(--td-text-color-secondary);
|
||||
}
|
||||
</style>
|
||||
195
src/renderer/src/components/ThemeDemo.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div class="theme-demo">
|
||||
<div class="demo-header">
|
||||
<h1 class="demo-title">主题切换演示</h1>
|
||||
<ThemeSelector />
|
||||
</div>
|
||||
|
||||
<div class="demo-content">
|
||||
<div class="demo-card">
|
||||
<h3 class="card-title">主要功能</h3>
|
||||
<p class="card-text">这是一个现代化的主题切换组件,支持多种预设主题色。</p>
|
||||
<button class="btn btn-primary">主要按钮</button>
|
||||
</div>
|
||||
|
||||
<div class="demo-card">
|
||||
<h3 class="card-title">设计特点</h3>
|
||||
<ul class="feature-list">
|
||||
<li>简约美观的界面设计</li>
|
||||
<li>平滑的过渡动画效果</li>
|
||||
<li>响应式布局适配</li>
|
||||
<li>深色模式支持</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="demo-card">
|
||||
<h3 class="card-title">交互元素</h3>
|
||||
<div class="demo-controls">
|
||||
<input type="text" class="form-control" placeholder="输入框示例" />
|
||||
<button class="btn btn-secondary">次要按钮</button>
|
||||
<a href="#" class="demo-link">链接示例</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ThemeSelector from './ThemeSelector.vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.theme-demo {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 32px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--td-component-border);
|
||||
}
|
||||
|
||||
.demo-title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--td-text-color-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.demo-content {
|
||||
display: grid;
|
||||
gap: 24px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
}
|
||||
|
||||
.demo-card {
|
||||
background: var(--td-bg-color-container);
|
||||
border: 1px solid var(--td-component-border);
|
||||
border-radius: var(--td-radius-large);
|
||||
padding: 24px;
|
||||
box-shadow: var(--td-shadow-1);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--td-text-color-primary);
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
|
||||
.card-text {
|
||||
color: var(--td-text-color-secondary);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.feature-list li {
|
||||
color: var(--td-text-color-secondary);
|
||||
padding: 8px 0;
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.feature-list li::before {
|
||||
content: '•';
|
||||
color: var(--td-brand-color);
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.demo-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 16px;
|
||||
border-radius: var(--td-radius-medium);
|
||||
border: 1px solid transparent;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--td-brand-color);
|
||||
border-color: var(--td-brand-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--td-brand-color-hover);
|
||||
border-color: var(--td-brand-color-hover);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--td-bg-color-component);
|
||||
border-color: var(--td-component-border);
|
||||
color: var(--td-text-color-primary);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--td-bg-color-component-hover);
|
||||
border-color: var(--td-component-border);
|
||||
}
|
||||
|
||||
.form-control {
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--td-component-border);
|
||||
border-radius: var(--td-radius-medium);
|
||||
background-color: var(--td-bg-color-container);
|
||||
color: var(--td-text-color-primary);
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: var(--td-brand-color);
|
||||
box-shadow: 0 0 0 3px var(--td-brand-color-light);
|
||||
}
|
||||
|
||||
.demo-link {
|
||||
color: var(--td-brand-color);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.demo-link:hover {
|
||||
color: var(--td-brand-color-hover);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.demo-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.demo-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.demo-controls {
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
253
src/renderer/src/components/ThemeSelector.vue
Normal file
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<div class="theme-selector">
|
||||
<div class="theme-selector-trigger" @click="toggleDropdown">
|
||||
<div class="current-theme">
|
||||
<div class="theme-color-preview" :style="{ backgroundColor: currentThemeColor }"></div>
|
||||
<span class="theme-name">{{ currentThemeName }}</span>
|
||||
</div>
|
||||
<svg class="dropdown-icon" :class="{ 'rotated': isDropdownOpen }" viewBox="0 0 24 24" width="16" height="16">
|
||||
<path d="M7 10l5 5 5-5z" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<transition name="dropdown">
|
||||
<div v-if="isDropdownOpen" class="theme-dropdown">
|
||||
<div
|
||||
v-for="theme in themes"
|
||||
:key="theme.name"
|
||||
class="theme-option"
|
||||
:class="{ 'active': currentTheme === theme.name }"
|
||||
@click="selectTheme(theme.name)"
|
||||
>
|
||||
<div class="theme-color-dot" :style="{ backgroundColor: theme.color }"></div>
|
||||
<span class="theme-label">{{ theme.label }}</span>
|
||||
<svg v-if="currentTheme === theme.name" class="check-icon" viewBox="0 0 24 24" width="16" height="16">
|
||||
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const isDropdownOpen = ref(false)
|
||||
const currentTheme = ref('default')
|
||||
|
||||
// 基于现有主题文件的配置
|
||||
const themes = [
|
||||
{ name: 'default', label: '默认', color: '#2ba55b' },
|
||||
{ name: 'pink', label: '粉色', color: '#fc5e7e' },
|
||||
{ name: 'blue', label: '蓝色', color: '#57b4ff' },
|
||||
{ name: 'cyan', label: '青色', color: '#3ac2b8' },
|
||||
{ name: 'orange', label: '橙色', color: '#fb9458' }
|
||||
]
|
||||
|
||||
const loadSavedTheme = () => {
|
||||
const savedTheme = localStorage.getItem('selected-theme')
|
||||
if (savedTheme && themes.some(t => t.name === savedTheme)) {
|
||||
currentTheme.value = savedTheme
|
||||
applyTheme(savedTheme)
|
||||
}
|
||||
}
|
||||
|
||||
const applyTheme = (themeName) => {
|
||||
const documentElement = document.documentElement
|
||||
|
||||
// 移除之前的主题
|
||||
documentElement.removeAttribute('theme-mode')
|
||||
|
||||
// 应用新主题(如果不是默认主题)
|
||||
if (themeName !== 'default') {
|
||||
documentElement.setAttribute('theme-mode', themeName)
|
||||
}
|
||||
|
||||
// 保存到本地存储
|
||||
localStorage.setItem('selected-theme', themeName)
|
||||
}
|
||||
|
||||
const currentThemeColor = computed(() => {
|
||||
const theme = themes.find(t => t.name === currentTheme.value)
|
||||
return theme ? theme.color : '#2ba55b'
|
||||
})
|
||||
|
||||
const currentThemeName = computed(() => {
|
||||
const theme = themes.find(t => t.name === currentTheme.value)
|
||||
return theme ? theme.label : '默认'
|
||||
})
|
||||
|
||||
const toggleDropdown = () => {
|
||||
isDropdownOpen.value = !isDropdownOpen.value
|
||||
}
|
||||
|
||||
const selectTheme = (themeName) => {
|
||||
if (themeName === currentTheme.value) {
|
||||
isDropdownOpen.value = false
|
||||
return
|
||||
}
|
||||
|
||||
currentTheme.value = themeName
|
||||
applyTheme(themeName)
|
||||
isDropdownOpen.value = false
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const handleClickOutside = (event) => {
|
||||
const themeSelector = event.target.closest('.theme-selector')
|
||||
if (!themeSelector) {
|
||||
isDropdownOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSavedTheme()
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.theme-selector {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.theme-selector-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: var(--td-bg-color-container, #ffffff);
|
||||
border: 1px solid var(--td-component-border, #e2e8f0);
|
||||
border-radius: var(--td-radius-medium, 6px);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.theme-selector-trigger:hover {
|
||||
background: var(--td-bg-color-container-hover, #f8fafc);
|
||||
border-color: var(--td-brand-color-hover, #cbd5e1);
|
||||
}
|
||||
|
||||
.current-theme {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.theme-color-preview {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--td-bg-color-container, #ffffff);
|
||||
box-shadow: 0 0 0 1px var(--td-component-border, #e2e8f0);
|
||||
}
|
||||
|
||||
.theme-name {
|
||||
font-size: 14px;
|
||||
color: var(--td-text-color-primary, #1e293b);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dropdown-icon {
|
||||
color: var(--td-text-color-secondary, #64748b);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.dropdown-icon.rotated {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.theme-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-top: 4px;
|
||||
background: var(--td-bg-color-container, #ffffff);
|
||||
border: 1px solid var(--td-component-border, #e2e8f0);
|
||||
border-radius: var(--td-radius-medium, 6px);
|
||||
box-shadow: var(--td-shadow-2, 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06));
|
||||
z-index: 1000;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.theme-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.theme-option:hover {
|
||||
background: var(--td-bg-color-container-hover, #f8fafc);
|
||||
}
|
||||
|
||||
.theme-option.active {
|
||||
background: var(--td-brand-color-light, #eff6ff);
|
||||
color: var(--td-text-color-primary, #1e293b);
|
||||
}
|
||||
|
||||
.theme-color-dot {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--td-bg-color-container, #ffffff);
|
||||
box-shadow: 0 0 0 1px var(--td-component-border, #e2e8f0);
|
||||
}
|
||||
|
||||
.theme-label {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: var(--td-text-color-primary, #1e293b);
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
color: var(--td-brand-color, #3b82f6);
|
||||
}
|
||||
|
||||
/* 下拉动画 */
|
||||
.dropdown-enter-active,
|
||||
.dropdown-leave-active {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.dropdown-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(-8px) scale(0.95);
|
||||
}
|
||||
|
||||
.dropdown-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-8px) scale(0.95);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 640px) {
|
||||
.theme-selector-trigger {
|
||||
min-width: 100px;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
.theme-name {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.theme-option {
|
||||
padding: 10px 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
145
src/renderer/src/components/UpdateExample.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div class="update-example">
|
||||
<div class="update-section">
|
||||
<h3>自动更新</h3>
|
||||
<div class="update-info">
|
||||
<p>当前版本: {{ currentVersion }}</p>
|
||||
<t-button theme="primary" :loading="isChecking" @click="handleCheckUpdate">
|
||||
{{ isChecking ? '检查中...' : '检查更新' }}
|
||||
</t-button>
|
||||
|
||||
<!-- 测试按钮 -->
|
||||
<t-button theme="default" @click="testProgress">
|
||||
测试进度显示
|
||||
</t-button>
|
||||
</div>
|
||||
|
||||
<!-- 显示当前下载状态 -->
|
||||
<div class="debug-info">
|
||||
<p>下载状态: {{ downloadState.isDownloading ? '下载中' : '未下载' }}</p>
|
||||
<p>进度: {{ Math.round(downloadState.progress.percent) }}%</p>
|
||||
<p>已下载: {{ formatBytes(downloadState.progress.transferred) }}</p>
|
||||
<p>总大小: {{ formatBytes(downloadState.progress.total) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自定义进度组件 -->
|
||||
<UpdateProgress />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useAutoUpdate } from '../composables/useAutoUpdate'
|
||||
import UpdateProgress from './UpdateProgress.vue'
|
||||
import { downloadState } from '../services/autoUpdateService'
|
||||
|
||||
const { checkForUpdates } = useAutoUpdate()
|
||||
|
||||
const currentVersion = ref('1.0.8') // 从 package.json 获取
|
||||
const isChecking = ref(false)
|
||||
|
||||
const handleCheckUpdate = async () => {
|
||||
isChecking.value = true
|
||||
try {
|
||||
await checkForUpdates()
|
||||
} finally {
|
||||
// 延迟重置状态,给用户足够时间看到通知
|
||||
setTimeout(() => {
|
||||
isChecking.value = false
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
|
||||
// 测试进度显示
|
||||
const testProgress = () => {
|
||||
console.log('开始测试进度显示')
|
||||
|
||||
// 模拟下载开始
|
||||
downloadState.isDownloading = true
|
||||
downloadState.updateInfo = {
|
||||
url: 'https://example.com/test.zip',
|
||||
name: '1.0.9',
|
||||
notes: '测试更新',
|
||||
pub_date: new Date().toISOString()
|
||||
}
|
||||
downloadState.progress = {
|
||||
percent: 0,
|
||||
transferred: 0,
|
||||
total: 10 * 1024 * 1024 // 10MB
|
||||
}
|
||||
|
||||
// 模拟进度更新
|
||||
let progress = 0
|
||||
const interval = setInterval(() => {
|
||||
progress += Math.random() * 10
|
||||
if (progress >= 100) {
|
||||
progress = 100
|
||||
clearInterval(interval)
|
||||
|
||||
// 3秒后停止下载状态
|
||||
setTimeout(() => {
|
||||
downloadState.isDownloading = false
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
downloadState.progress = {
|
||||
percent: progress,
|
||||
transferred: (downloadState.progress.total * progress) / 100,
|
||||
total: downloadState.progress.total
|
||||
}
|
||||
}, 200)
|
||||
}
|
||||
|
||||
// 格式化字节大小
|
||||
const formatBytes = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B'
|
||||
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.update-example {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.update-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.update-section h3 {
|
||||
margin-bottom: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--td-text-color-primary);
|
||||
}
|
||||
|
||||
.update-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.update-info p {
|
||||
margin: 0;
|
||||
color: var(--td-text-color-secondary);
|
||||
}
|
||||
|
||||
.debug-info {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.debug-info p {
|
||||
margin: 4px 0;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
225
src/renderer/src/components/UpdateProgress.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<div v-if="downloadState.isDownloading" class="update-progress-overlay">
|
||||
<div class="update-progress-modal">
|
||||
<div class="progress-header">
|
||||
<h3>正在下载更新</h3>
|
||||
<p v-if="downloadState.updateInfo">版本 {{ downloadState.updateInfo.name }}</p>
|
||||
</div>
|
||||
|
||||
<div class="progress-content">
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-bar">
|
||||
<div
|
||||
class="progress-fill"
|
||||
:style="{ width: `${downloadState.progress.percent}%` }"
|
||||
></div>
|
||||
</div>
|
||||
<div class="progress-text">
|
||||
{{ Math.round(downloadState.progress.percent) }}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress-details">
|
||||
<div class="download-info">
|
||||
<span>已下载: {{ formatBytes(downloadState.progress.transferred) }}</span>
|
||||
<span>总大小: {{ formatBytes(downloadState.progress.total) }}</span>
|
||||
</div>
|
||||
<div class="download-speed" v-if="downloadSpeed > 0">
|
||||
下载速度: {{ formatBytes(downloadSpeed) }}/s
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onUnmounted } from 'vue'
|
||||
import { downloadState } from '../services/autoUpdateService'
|
||||
|
||||
const downloadSpeed = ref(0)
|
||||
let lastTransferred = 0
|
||||
let lastTime = 0
|
||||
let speedInterval: NodeJS.Timeout | null = null
|
||||
|
||||
// 计算下载速度
|
||||
const calculateSpeed = () => {
|
||||
const currentTime = Date.now()
|
||||
const currentTransferred = downloadState.progress.transferred
|
||||
|
||||
if (lastTime > 0) {
|
||||
const timeDiff = (currentTime - lastTime) / 1000 // 秒
|
||||
const sizeDiff = currentTransferred - lastTransferred // 字节
|
||||
|
||||
if (timeDiff > 0) {
|
||||
downloadSpeed.value = sizeDiff / timeDiff
|
||||
}
|
||||
}
|
||||
|
||||
lastTransferred = currentTransferred
|
||||
lastTime = currentTime
|
||||
}
|
||||
|
||||
// 监听下载进度变化
|
||||
watch(() => downloadState.progress.transferred, () => {
|
||||
calculateSpeed()
|
||||
})
|
||||
|
||||
// 开始监听时重置速度计算
|
||||
watch(() => downloadState.isDownloading, (isDownloading) => {
|
||||
if (isDownloading) {
|
||||
lastTransferred = 0
|
||||
lastTime = 0
|
||||
downloadSpeed.value = 0
|
||||
|
||||
// 每秒更新一次速度显示
|
||||
speedInterval = setInterval(() => {
|
||||
if (!downloadState.isDownloading) {
|
||||
downloadSpeed.value = 0
|
||||
}
|
||||
}, 1000)
|
||||
} else {
|
||||
if (speedInterval) {
|
||||
clearInterval(speedInterval)
|
||||
speedInterval = null
|
||||
}
|
||||
downloadSpeed.value = 0
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (speedInterval) {
|
||||
clearInterval(speedInterval)
|
||||
}
|
||||
})
|
||||
|
||||
// 格式化字节大小
|
||||
const formatBytes = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B'
|
||||
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.update-progress-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.update-progress-modal {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
min-width: 400px;
|
||||
max-width: 500px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.progress-header {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.progress-header h3 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.progress-header p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.progress-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #0052d9, #266fe8);
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-weight: 600;
|
||||
color: #0052d9;
|
||||
min-width: 40px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.progress-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.download-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.download-speed {
|
||||
text-align: center;
|
||||
color: #0052d9;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 暗色主题适配 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.update-progress-modal {
|
||||
background: #2d2d2d;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.progress-header h3 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.progress-header p {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
background-color: #404040;
|
||||
}
|
||||
|
||||
.progress-details {
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
25
src/renderer/src/composables/useAutoUpdate.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { autoUpdateService, downloadState } from '../services/autoUpdateService';
|
||||
|
||||
export function useAutoUpdate() {
|
||||
// 检查更新
|
||||
const checkForUpdates = async () => {
|
||||
await autoUpdateService.checkForUpdates();
|
||||
};
|
||||
|
||||
// 下载更新
|
||||
const downloadUpdate = async () => {
|
||||
await autoUpdateService.downloadUpdate();
|
||||
};
|
||||
|
||||
// 安装更新
|
||||
const quitAndInstall = async () => {
|
||||
await autoUpdateService.quitAndInstall();
|
||||
};
|
||||
|
||||
return {
|
||||
checkForUpdates,
|
||||
downloadUpdate,
|
||||
quitAndInstall,
|
||||
downloadState // 导出下载状态供组件使用
|
||||
};
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
import './assets/main.css'
|
||||
import './assets/base.css'
|
||||
|
||||
// 引入组件库的少量全局样式变量
|
||||
import 'tdesign-vue-next/es/style/index.css' //tdesign 组件样式
|
||||
// import 'tdesign-vue-next/es/style/index.css' //tdesign 组件样式
|
||||
|
||||
// 引入iconfont图标样式
|
||||
import './assets/icon_font/iconfont.css'
|
||||
import './assets/icon_font/iconfont.js'
|
||||
|
||||
import App from './App.vue'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
const app = createApp(App)
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
237
src/renderer/src/services/autoUpdateService.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
import { NotifyPlugin, DialogPlugin } from 'tdesign-vue-next';
|
||||
|
||||
import { reactive } from 'vue';
|
||||
|
||||
export interface DownloadProgress {
|
||||
percent: number;
|
||||
transferred: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface UpdateInfo {
|
||||
url: string;
|
||||
name: string;
|
||||
notes: string;
|
||||
pub_date: string;
|
||||
}
|
||||
|
||||
// 响应式的下载状态
|
||||
export const downloadState = reactive({
|
||||
isDownloading: false,
|
||||
progress: {
|
||||
percent: 0,
|
||||
transferred: 0,
|
||||
total: 0
|
||||
} as DownloadProgress,
|
||||
updateInfo: null as UpdateInfo | null
|
||||
});
|
||||
|
||||
export class AutoUpdateService {
|
||||
private static instance: AutoUpdateService;
|
||||
private isListening = false;
|
||||
|
||||
constructor() {
|
||||
// 构造函数中自动开始监听
|
||||
this.startListening();
|
||||
}
|
||||
|
||||
static getInstance(): AutoUpdateService {
|
||||
if (!AutoUpdateService.instance) {
|
||||
AutoUpdateService.instance = new AutoUpdateService();
|
||||
}
|
||||
return AutoUpdateService.instance;
|
||||
}
|
||||
|
||||
// 开始监听更新消息
|
||||
startListening() {
|
||||
if (this.isListening) return;
|
||||
|
||||
this.isListening = true;
|
||||
|
||||
// 监听各种更新事件
|
||||
window.api.autoUpdater.onCheckingForUpdate(() => {
|
||||
this.showCheckingNotification();
|
||||
});
|
||||
|
||||
window.api.autoUpdater.onUpdateAvailable((_,updateInfo: UpdateInfo) => {
|
||||
this.showUpdateAvailableDialog(updateInfo);
|
||||
});
|
||||
|
||||
window.api.autoUpdater.onUpdateNotAvailable(() => {
|
||||
this.showNoUpdateNotification();
|
||||
});
|
||||
|
||||
window.api.autoUpdater.onDownloadStarted((updateInfo: UpdateInfo) => {
|
||||
this.handleDownloadStarted(updateInfo);
|
||||
});
|
||||
|
||||
window.api.autoUpdater.onDownloadProgress((progress: DownloadProgress) => {
|
||||
console.log(progress)
|
||||
|
||||
this.showDownloadProgressNotification(progress);
|
||||
});
|
||||
|
||||
window.api.autoUpdater.onUpdateDownloaded(() => {
|
||||
this.showUpdateDownloadedDialog();
|
||||
});
|
||||
|
||||
window.api.autoUpdater.onError((_,error: string) => {
|
||||
this.showUpdateErrorNotification(error);
|
||||
});
|
||||
}
|
||||
|
||||
// 停止监听更新消息
|
||||
stopListening() {
|
||||
if (!this.isListening) return;
|
||||
|
||||
this.isListening = false;
|
||||
window.api.autoUpdater.removeAllListeners();
|
||||
}
|
||||
|
||||
// 检查更新
|
||||
async checkForUpdates() {
|
||||
try {
|
||||
await window.api.autoUpdater.checkForUpdates();
|
||||
} catch (error) {
|
||||
console.error('检查更新失败:', error);
|
||||
NotifyPlugin.error({
|
||||
title: '更新检查失败',
|
||||
content: '无法检查更新,请稍后重试',
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 下载更新
|
||||
async downloadUpdate() {
|
||||
try {
|
||||
await window.api.autoUpdater.downloadUpdate();
|
||||
} catch (error) {
|
||||
console.error('下载更新失败:', error);
|
||||
NotifyPlugin.error({
|
||||
title: '下载更新失败',
|
||||
content: '无法下载更新,请稍后重试',
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 安装更新
|
||||
async quitAndInstall() {
|
||||
try {
|
||||
await window.api.autoUpdater.quitAndInstall();
|
||||
} catch (error) {
|
||||
console.error('安装更新失败:', error);
|
||||
NotifyPlugin.error({
|
||||
title: '安装更新失败',
|
||||
content: '无法安装更新,请稍后重试',
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 显示检查更新通知
|
||||
private showCheckingNotification() {
|
||||
NotifyPlugin.info({
|
||||
title: '检查更新',
|
||||
content: '正在检查是否有新版本...',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
|
||||
// 显示有更新可用对话框
|
||||
private showUpdateAvailableDialog(updateInfo: UpdateInfo) {
|
||||
// 保存更新信息到状态中
|
||||
downloadState.updateInfo = updateInfo;
|
||||
console.log(updateInfo)
|
||||
const releaseDate = new Date(updateInfo.pub_date).toLocaleDateString('zh-CN');
|
||||
|
||||
const dialog =DialogPlugin.confirm({
|
||||
header: `发现新版本 ${updateInfo.name}`,
|
||||
body: `发布时间: ${releaseDate}\n\n更新说明:\n${updateInfo.notes || '暂无更新说明'}\n\n是否立即下载此更新?`,
|
||||
confirmBtn: '立即下载',
|
||||
cancelBtn: '稍后提醒',
|
||||
onConfirm: () => {
|
||||
this.downloadUpdate();
|
||||
dialog.hide()
|
||||
},
|
||||
onCancel: () => {
|
||||
console.log('用户选择稍后下载更新');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 显示无更新通知
|
||||
private showNoUpdateNotification() {
|
||||
NotifyPlugin.info({
|
||||
title: '已是最新版本',
|
||||
content: '当前已是最新版本,无需更新',
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
|
||||
// 处理下载开始事件
|
||||
private handleDownloadStarted(updateInfo: UpdateInfo) {
|
||||
downloadState.isDownloading = true;
|
||||
downloadState.updateInfo = updateInfo;
|
||||
downloadState.progress = {
|
||||
percent: 0,
|
||||
transferred: 0,
|
||||
total: 0
|
||||
};
|
||||
|
||||
console.log('开始下载更新:', updateInfo.name);
|
||||
}
|
||||
|
||||
// 更新下载进度状态
|
||||
private showDownloadProgressNotification(progress: DownloadProgress) {
|
||||
// 更新响应式状态
|
||||
downloadState.isDownloading = true;
|
||||
downloadState.progress = progress;
|
||||
|
||||
console.log(`下载进度: ${Math.round(progress.percent)}% (${this.formatBytes(progress.transferred)} / ${this.formatBytes(progress.total)})`);
|
||||
}
|
||||
|
||||
// 显示更新下载完成对话框
|
||||
private showUpdateDownloadedDialog() {
|
||||
// 更新下载状态
|
||||
downloadState.isDownloading = false;
|
||||
downloadState.progress.percent = 100;
|
||||
|
||||
DialogPlugin.confirm({
|
||||
header: '更新下载完成',
|
||||
body: '新版本已下载完成,是否立即重启应用以完成更新?',
|
||||
confirmBtn: '立即重启',
|
||||
cancelBtn: '稍后重启',
|
||||
onConfirm: () => {
|
||||
this.quitAndInstall();
|
||||
},
|
||||
onCancel: () => {
|
||||
console.log('用户选择稍后重启');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 显示更新错误通知
|
||||
private showUpdateErrorNotification(error: string) {
|
||||
NotifyPlugin.error({
|
||||
title: '更新失败',
|
||||
content: `更新过程中出现错误: ${error}`,
|
||||
duration: 5000
|
||||
});
|
||||
}
|
||||
|
||||
// 格式化字节大小
|
||||
private formatBytes(bytes: number): string {
|
||||
if (bytes === 0) return '0 B';
|
||||
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
export const autoUpdateService = AutoUpdateService.getInstance();
|
||||
@@ -102,7 +102,7 @@ export const ControlAudioStore = defineStore('controlAudio', () => {
|
||||
|
||||
// 初始化
|
||||
const init = (el: ControlAudioState['audio']) => {
|
||||
userInfo = LocalUserDetailStore().userInfo
|
||||
userInfo = LocalUserDetailStore()
|
||||
console.log(el, '全局音频挂载初始化success')
|
||||
Audio.audio = el
|
||||
}
|
||||
@@ -139,21 +139,20 @@ export const ControlAudioStore = defineStore('controlAudio', () => {
|
||||
transitionVolume(Audio.audio, volume / 100, Audio.volume <= volume)
|
||||
} else {
|
||||
Audio.audio.volume = Number((volume / 100).toFixed(2))
|
||||
console.log('vo', Audio.audio.volume)
|
||||
}
|
||||
Audio.volume = volume
|
||||
userInfo.volume = volume
|
||||
userInfo.userInfo.volume = volume
|
||||
}
|
||||
} else {
|
||||
if (typeof volume === 'number' && Audio.audio) {
|
||||
if (volume <= 0) {
|
||||
Audio.volume = 0
|
||||
Audio.audio.volume = 0
|
||||
userInfo.volume = 0
|
||||
userInfo.userInfo.volume = 0
|
||||
} else {
|
||||
Audio.volume = 100
|
||||
Audio.audio.volume = 100
|
||||
userInfo.volume = 100
|
||||
userInfo.userInfo.volume = 100
|
||||
}
|
||||
} else {
|
||||
throw new Error('音量必须是0-100之间的数字')
|
||||
|
||||
@@ -17,7 +17,7 @@ export const LocalUserDetailStore = defineStore('Local', () => {
|
||||
} else {
|
||||
userInfo.value = {
|
||||
lastPlaySongId: null,
|
||||
topBarStyle: true,
|
||||
topBarStyle: false,
|
||||
mainColor: '#00DAC0',
|
||||
volume: 80,
|
||||
currentTime: 0
|
||||
|
||||
@@ -1,22 +1,56 @@
|
||||
import { NotifyPlugin } from 'tdesign-vue-next'
|
||||
import musicService from '@renderer/services/music'
|
||||
import { LocalUserDetailStore } from '@renderer/store/LocalUserDetail'
|
||||
import { toRaw } from 'vue'
|
||||
import { MessagePlugin } from 'tdesign-vue-next';
|
||||
|
||||
async function downloadSingleSong(songId: string, name: string, artist: string): Promise<void> {
|
||||
interface MusicItem {
|
||||
singer: string
|
||||
name: string
|
||||
albumName: string
|
||||
albumId: number
|
||||
source: string
|
||||
interval: string
|
||||
songmid: number
|
||||
img: string
|
||||
lrc: null | string
|
||||
types: string[]
|
||||
_types: Record<string, any>
|
||||
typeUrl: Record<string, any>
|
||||
}
|
||||
|
||||
const qualityMap: Record<string, string> = {
|
||||
'128k': '标准音质',
|
||||
'192k': '高品音质',
|
||||
'320k': '超高品质',
|
||||
flac: '无损音质',
|
||||
flac24bit: '超高解析',
|
||||
hires: '高清臻音',
|
||||
atmos: '全景环绕',
|
||||
master: '超清母带'
|
||||
}
|
||||
const qualityKey = Object.keys(qualityMap)
|
||||
|
||||
async function downloadSingleSong(songInfo: MusicItem): Promise<void> {
|
||||
|
||||
try {
|
||||
const LocalUserDetail = LocalUserDetailStore()
|
||||
const result = await musicService.request(
|
||||
'downloadSingleSong',
|
||||
{ id: songId, name, artist, ...LocalUserDetail.userSource },
|
||||
false,
|
||||
false
|
||||
)
|
||||
|
||||
let quality = LocalUserDetail.userSource.quality as string
|
||||
if (
|
||||
qualityKey.indexOf(quality) >
|
||||
qualityKey.indexOf((songInfo.types[songInfo.types.length - 1] as unknown as { type: any }).type)
|
||||
) {
|
||||
quality = (songInfo.types[songInfo.types.length - 1] as unknown as { type: any }).type
|
||||
}
|
||||
const tip = MessagePlugin.success('开始下载歌曲:'+ songInfo.name)
|
||||
const result = await window.api.music.requestSdk('downloadSingleSong',{
|
||||
pluginId:LocalUserDetail.userSource.pluginId?.toString() || '',
|
||||
source:songInfo.source,
|
||||
quality,
|
||||
songInfo:toRaw(songInfo)
|
||||
})
|
||||
;(await tip).close()
|
||||
if (!Object.hasOwn(result, 'path')) {
|
||||
await NotifyPlugin.info({
|
||||
title: '提示',
|
||||
content: result.message
|
||||
})
|
||||
MessagePlugin.info(result.message)
|
||||
} else {
|
||||
await NotifyPlugin.success({
|
||||
title: '下载成功',
|
||||
@@ -27,7 +61,7 @@ async function downloadSingleSong(songId: string, name: string, artist: string):
|
||||
console.error('下载失败:', error)
|
||||
await NotifyPlugin.error({
|
||||
title: '下载失败',
|
||||
content: `${error.message ?? '未知错误'}`
|
||||
content: `${error.message.includes('歌曲正在')? '歌曲正在下载中':'未知错误'}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,13 +168,11 @@ export function validateImportedPlaylist(playlist: any[]): boolean {
|
||||
// 验证每个歌曲对象是否包含必要的字段
|
||||
return playlist.every(
|
||||
(song) =>
|
||||
typeof song === 'object' &&
|
||||
typeof song.songmid === 'number' &&
|
||||
typeof song.name === 'string' &&
|
||||
song.songmid &&
|
||||
song.name &&
|
||||
typeof song.img === 'string' &&
|
||||
typeof song.singer === 'string' &&
|
||||
typeof song.interval === 'string' &&
|
||||
typeof song.albumName === 'string' &&
|
||||
typeof song.source === 'string'
|
||||
)
|
||||
}
|
||||
|
||||
51
src/renderer/src/views/Settings.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="settings-page">
|
||||
<div class="settings-container">
|
||||
<h2>应用设置</h2>
|
||||
|
||||
<!-- 其他设置项 -->
|
||||
<div class="settings-section">
|
||||
<h3>常规设置</h3>
|
||||
<!-- 这里可以添加其他设置项 -->
|
||||
</div>
|
||||
|
||||
<!-- 自动更新设置 -->
|
||||
<UpdateSettings />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import UpdateSettings from '../components/Settings/UpdateSettings.vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.settings-page {
|
||||
padding: 20px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.settings-container h2 {
|
||||
margin-bottom: 24px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--td-text-color-primary);
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
margin-bottom: 32px;
|
||||
padding: 20px;
|
||||
background: var(--td-bg-color-container);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--td-border-level-1-color);
|
||||
}
|
||||
|
||||
.settings-section h3 {
|
||||
margin-bottom: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--td-text-color-primary);
|
||||
}
|
||||
</style>
|
||||
@@ -106,7 +106,6 @@ const handleKeyDown = () => {
|
||||
<t-button
|
||||
v-for="(item, index) in menuList"
|
||||
:key="index"
|
||||
:theme="menuActive == index ? 'warning' : ''"
|
||||
:variant="menuActive == index ? 'base' : 'text'"
|
||||
:class="menuActive == index ? 'nav-button active' : 'nav-button'"
|
||||
block
|
||||
@@ -203,7 +202,7 @@ const handleKeyDown = () => {
|
||||
.logo-icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background-color: #4cd47c;
|
||||
background-color: var(--td-brand-color-4);
|
||||
border-radius: 0.625rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -245,11 +244,11 @@ const handleKeyDown = () => {
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #4cd47c;
|
||||
background-color: var(--td-brand-color-4);
|
||||
color: rgb(255, 255, 255);
|
||||
|
||||
&:hover {
|
||||
background-color: #44ff85;
|
||||
background-color: var(--td-brand-color-5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ const handlePause = () => {
|
||||
}
|
||||
|
||||
const handleDownload = (song: MusicItem) => {
|
||||
downloadSingleSong(song.songmid as unknown as string, song.name, song.albumName)
|
||||
downloadSingleSong(song)
|
||||
}
|
||||
|
||||
const handleAddToPlaylist = (song: MusicItem) => {
|
||||
|
||||
@@ -134,7 +134,7 @@ const handlePause = () => {
|
||||
}
|
||||
|
||||
const handleDownload = (song: MusicItem) => {
|
||||
downloadSingleSong(song.songmid as unknown as string, song.name, song.albumName)
|
||||
downloadSingleSong(song)
|
||||
}
|
||||
|
||||
const handleAddToPlaylist = (song: MusicItem) => {
|
||||
|
||||
@@ -52,6 +52,7 @@ const clearAPIKey = (): void => {
|
||||
}
|
||||
import { useRouter } from 'vue-router'
|
||||
import { computed, watch } from 'vue'
|
||||
import MusicCache from '@renderer/components/Settings/MusicCache.vue'
|
||||
const router = useRouter()
|
||||
const goPlugin = () => {
|
||||
router.push('/plugins')
|
||||
@@ -202,16 +203,6 @@ const getCurrentSourceName = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>当前风格预览</h3>
|
||||
<div class="preview-container">
|
||||
<div class="mock-titlebar">
|
||||
<div class="mock-title">Ceru Music - 设置</div>
|
||||
<TitleBarControls :control-style="currentStyle" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>两种风格对比</h3>
|
||||
<div class="comparison-container">
|
||||
@@ -232,7 +223,10 @@ const getCurrentSourceName = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>应用主题色</h3>
|
||||
<ThemeSelector />
|
||||
</div>
|
||||
<div class="demo-section">
|
||||
<h3>DeepSeek API 配置</h3>
|
||||
<div class="api-config-container">
|
||||
@@ -303,7 +297,6 @@ const getCurrentSourceName = () => {
|
||||
<PlaylistSettings />
|
||||
<!-- <PlaylistActions></PlaylistActions> -->
|
||||
</div>
|
||||
|
||||
<!-- 插件管理部分 -->
|
||||
<div class="demo-section">
|
||||
<h3>插件管理</h3>
|
||||
@@ -432,6 +425,12 @@ const getCurrentSourceName = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="demo-section">
|
||||
<div>
|
||||
<MusicCache></MusicCache>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>功能说明</h3>
|
||||
@@ -768,8 +767,8 @@ const getCurrentSourceName = () => {
|
||||
}
|
||||
|
||||
.plugin-status {
|
||||
background: #10b981;
|
||||
color: rgb(0, 0, 0);
|
||||
background: var(--td-brand-color-5);
|
||||
color: var(--td-gray-color-1);
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 1rem;
|
||||
font-size: 0.875rem;
|
||||
@@ -822,8 +821,8 @@ const getCurrentSourceName = () => {
|
||||
|
||||
&.active {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-color: #10b981;
|
||||
box-shadow: 0 0 20px rgba(16, 185, 129, 0.3);
|
||||
border-color: var(--td-brand-color-5);
|
||||
box-shadow: 0 0 20px var(--td-gray-color-6);
|
||||
}
|
||||
|
||||
.source-icon {
|
||||
@@ -899,12 +898,12 @@ const getCurrentSourceName = () => {
|
||||
}
|
||||
|
||||
:deep(.t-slider__track-active) {
|
||||
background: linear-gradient(90deg, #10b981, #059669);
|
||||
background: linear-gradient(90deg, var(--td-brand-color-5), var(--td-brand-color-6));
|
||||
}
|
||||
|
||||
:deep(.t-slider__button) {
|
||||
background: white;
|
||||
border: 3px solid #10b981;
|
||||
border: 3px solid var(--td-brand-color-5);
|
||||
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
}
|
||||
@@ -951,7 +950,7 @@ const getCurrentSourceName = () => {
|
||||
|
||||
.status-value {
|
||||
font-weight: 600;
|
||||
color: #10b981;
|
||||
color: var(--td-brand-color-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,7 +628,7 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.current-tag {
|
||||
background-color: #28a745;
|
||||
background-color: var(--td-brand-color-5);
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
@@ -810,7 +810,7 @@ onMounted(async () => {
|
||||
font-size: 12px;
|
||||
|
||||
.console-prompt {
|
||||
color: #00d4aa;
|
||||
color: var(--td-brand-color-5);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
24
src/types/global.d.ts
vendored
@@ -1,4 +1,28 @@
|
||||
import { CacheInfo, CacheOperationResult } from './musicCache'
|
||||
|
||||
// 全局类型定义
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI: {
|
||||
// 音乐缓存相关
|
||||
musicCache: {
|
||||
getInfo: () => Promise<CacheInfo>
|
||||
clear: () => Promise<CacheOperationResult>
|
||||
getSize: () => Promise<number>
|
||||
}
|
||||
}
|
||||
api: {
|
||||
// 自动更新相关
|
||||
autoUpdater: {
|
||||
checkForUpdates: () => Promise<void>
|
||||
quitAndInstall: () => Promise<void>
|
||||
onMessage: (callback: (data: { type: string; data?: any }) => void) => void
|
||||
removeMessageListener: () => void
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace LX {
|
||||
namespace Music {
|
||||
// 音质类型
|
||||
|
||||
10
src/types/musicCache.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export interface CacheInfo {
|
||||
count: number
|
||||
size: number
|
||||
sizeFormatted: string
|
||||
}
|
||||
|
||||
export interface CacheOperationResult {
|
||||
success: boolean
|
||||
message: string
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
log [CeruMusic] Plugin "ikun音源" loaded successfully.
|
||||
log [聚合API接口 (by lerd) by Ceru插件] 注册事件监听器: request
|
||||
log [聚合API接口 (by lerd) by Ceru插件] 发送事件: inited [object Object]
|
||||
log [聚合API接口 (by lerd) by Ceru插件] 音源注册完成: tx,wy,kg,kw,mg
|
||||
log [聚合API接口 (by lerd) by Ceru插件] 动态音源信息已更新: [object Object]
|
||||
log [CeruMusic] 事件驱动插件初始化成功
|
||||
log [CeruMusic] Plugin "聚合API接口 (by lerd)" loaded successfully.
|
||||
@@ -16,6 +16,9 @@
|
||||
],
|
||||
"@common/*":[
|
||||
"src/common/*"
|
||||
],
|
||||
"@types/*": [
|
||||
"src/renderer/src/types/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1467
website/CeruUse.html
Normal file
BIN
website/assets/3f50d3b838287b4bf1523d0f955fdf37.png
Normal file
|
After Width: | Height: | Size: 173 KiB |
BIN
website/assets/image-20250813180317221.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
website/assets/image-20250813180856660.png
Normal file
|
After Width: | Height: | Size: 155 KiB |
BIN
website/assets/image-20250813180944752.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
website/assets/image-20250826214921963.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
website/assets/image-20250826215101522.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
website/assets/image-20250826215206862.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
website/assets/image-20250826215251525.png
Normal file
|
After Width: | Height: | Size: 68 KiB |