Compare commits

...

18 Commits

Author SHA1 Message Date
sqj
d2b1d69929 new 2025-08-27 14:53:03 +08:00
sqj
67430a25a3 应用更新检测 2025-08-27 14:36:50 +08:00
sqj
7f971f0256 应用更新检测 2025-08-27 14:22:27 +08:00
sqj
a7d4d877a9 website 2025-08-27 14:12:48 +08:00
sqj
0c349bd4d5 website 2025-08-27 14:08:49 +08:00
sqj
a8f7c620d0 website 2025-08-27 14:01:48 +08:00
sqj
8077138609 fix workflow 2025-08-27 13:56:14 +08:00
sqj
9deb6a937b feat:应用更新检测 2025-08-27 13:29:49 +08:00
sqj
1259a6d70d feat:使用文档 2025-08-26 22:20:56 +08:00
sqj
14496d5d0b add(website):更新发布页 2025-08-26 20:01:22 +08:00
sqj
5f042e15eb add(website):更新发布页 2025-08-26 19:52:47 +08:00
sqj
6945c733e9 add(website):更新发布页 2025-08-26 19:32:58 +08:00
sqj
eea157b8d6 fix:gitignore 2025-08-26 18:44:11 +08:00
sqj
0c22eaa212 fix:gitignore 2025-08-26 18:42:28 +08:00
sqj
44542c4d29 feat:updata v1.0.9 2025-08-26 18:38:47 +08:00
sqj
2e398b7874 fix(search,musicSdk):修复下载功能,支持缓存下载&add(setting):支持歌曲缓存支持清除缓存 2025-08-26 18:28:02 +08:00
sqj
d0cd96e1df fix(search,musicSdk):修复下载功能,支持缓存下载&add(setting):支持歌曲缓存支持清除缓存 2025-08-26 18:23:20 +08:00
sqj
89b085381e fix:修复播放列表导入问题 2025-08-25 19:37:16 +08:00
122 changed files with 12043 additions and 8210 deletions

View File

@@ -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"
}
}

View File

@@ -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
View File

@@ -10,3 +10,9 @@ build
/download/
/plugin/
/plugins/
temp
temp/log.txt
/.kiro/
/.vscode/
/.codebuddy/
/.idea/

View File

@@ -1,22 +0,0 @@
# 语言设置
## 对话语言
- **主要语言**: 中文(简体中文)
- 与用户对话时请使用中文回复
- 代码注释和文档也应该使用中文
- 变量名和函数名仍使用英文(遵循编程规范)
## 代码规范
- 代码本身使用英文命名
- 注释使用中文说明
- 错误信息和用户界面文本使用中文
- README和文档文件使用中文编写
## 示例
```typescript
// 播放音乐的函数
function playMusic(songId: string): void {
// 开始播放指定的歌曲
console.log('正在播放音乐...')
}
```

View File

@@ -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.

View File

@@ -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)

View File

@@ -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

View File

@@ -1,3 +0,0 @@
{
"recommendations": ["dbaeumer.vscode-eslint"]
}

39
.vscode/launch.json vendored
View File

@@ -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
View File

@@ -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
}

View File

@@ -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
```
## 文档与资源

View File

@@ -1,3 +1,3 @@
provider: generic
url: https://example.com/auto-updates
url: https://update.ceru.shiqianjiang.cn
updaterCacheDirName: ceru-music-updater

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

121
docs/auto-update.md Normal file
View 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

File diff suppressed because one or more lines are too long

51
docs/使用文档.md Normal file
View 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插件`**(目前生态欠缺)** 或现成的**落雪**插件导入使用![image-20250826221438856](assets/image-20250826221438856.png)
###### 导入完成点击使用![image-20250826221517247](assets/image-20250826221517247.png)

View File

@@ -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/

View File

@@ -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',

View File

@@ -1,6 +1,6 @@
{
"name": "ceru-music",
"version": "1.0.0",
"version": "1.1.4",
"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": {

File diff suppressed because it is too large Load Diff

View File

@@ -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.

View File

@@ -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]

View File

@@ -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 音乐插件] 请求失败: 歌曲不存在"

View File

@@ -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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -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

View File

@@ -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.

View File

@@ -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,
}

File diff suppressed because one or more lines are too long

View File

@@ -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
};

View File

@@ -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)
}

View File

@@ -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, // 歌曲IDlocal为文件路径

262
src/main/autoUpdate.ts Normal file
View 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)));
}
}

View 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);
}

View 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
}
})

View File

@@ -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

View 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()

View File

@@ -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)

View File

@@ -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
})

View File

@@ -89,3 +89,7 @@ export interface PlaylistDetailResult {
source: string
info: PlaylistInfo
}
export interface DownloadSingleSongArgs extends GetMusicUrlArg {
path?: string
}

View File

@@ -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> = {}

View File

@@ -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()

View File

@@ -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'
})
})
}
}

View File

@@ -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

View File

@@ -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') {
// 兼容旧的调用方式

View File

@@ -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'
}
})
}
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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>

View File

@@ -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

View File

@@ -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']
}
}

View File

@@ -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>

View File

@@ -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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View File

@@ -59,6 +59,7 @@ onDeactivated(() => {
const handleEnded = (): void => {
audioStore.Audio.isPlay = false
audioStore.publish('ended')
console.log('eddddddddd')
}
const handleSeeked = (): void => {

View File

@@ -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()
})
// 组件被激活时(从缓存中恢复)

View 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元素在各种主题下的表现。

View 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>

View File

@@ -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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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 // 导出下载状态供组件使用
};
}

View File

@@ -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'

View 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();

View File

@@ -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之间的数字')

View File

@@ -17,7 +17,7 @@ export const LocalUserDetailStore = defineStore('Local', () => {
} else {
userInfo.value = {
lastPlaySongId: null,
topBarStyle: true,
topBarStyle: false,
mainColor: '#00DAC0',
volume: 80,
currentTime: 0

View File

@@ -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('歌曲正在')? '歌曲正在下载中':'未知错误'}`
})
}
}

View File

@@ -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'
)
}

View 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>

View File

@@ -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);
}
}

View File

@@ -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) => {

View File

@@ -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) => {

View File

@@ -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);
}
}
}

View File

@@ -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
View File

@@ -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
View File

@@ -0,0 +1,10 @@
export interface CacheInfo {
count: number
size: number
sizeFormatted: string
}
export interface CacheOperationResult {
success: boolean
message: string
}

View File

@@ -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.

View File

@@ -16,6 +16,9 @@
],
"@common/*":[
"src/common/*"
],
"@types/*": [
"src/renderer/src/types/*"
]
}
}

1467
website/CeruUse.html Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

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