diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index 341b31d..0000000
--- a/.editorconfig
+++ /dev/null
@@ -1,24 +0,0 @@
-root = true
-
-[*]
-charset = utf-8
-end_of_line = lf
-insert_final_newline = true
-indent_style = space
-indent_size = 2
-trim_trailing_whitespace = true
-
-[*.md]
-trim_trailing_whitespace = false
-
-[*.yml]
-indent_style = space
-indent_size = 2
-
-[*.php]
-indent_style = space
-indent_size = 4
-
-[*.sh]
-indent_style = space
-indent_size = 4
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
deleted file mode 100644
index ea545ae..0000000
--- a/.github/workflows/test.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-name: Test
-on: [push, pull_request]
-
-jobs:
- phpunit:
- name: PHP-${{ matrix.php_version }}-${{ matrix.perfer }}
- runs-on: ubuntu-latest
- strategy:
- fail-fast: false
- matrix:
- php_version:
- - 8.0
- - 8.1
- - 8.2
- perfer:
- - stable
- steps:
- - uses: actions/checkout@master
- - name: Install Dependencies
- run: composer update --prefer-dist --no-interaction --no-suggest --prefer-${{ matrix.perfer }}
- - name: Run syntax check
- run: find -L . -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index ff7f293..c18ed01 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,2 @@
-vendor/
-composer.lock
-.idea/
+node_modules/
+lib/
\ No newline at end of file
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
new file mode 100644
index 0000000..ae3733c
--- /dev/null
+++ b/ARCHITECTURE.md
@@ -0,0 +1,157 @@
+# Meting 架构重构说明
+
+## 重构概述
+
+本次重构将原本单一文件中的不同音乐厂商逻辑抽离到独立的 Provider 文件中,采用了标准的 Provider 模式,提高了代码的可维护性和扩展性。
+
+## 新架构结构
+
+```
+src/
+├── meting.js # 主入口文件(重构后)
+├── meting-original.js # 原始文件备份
+└── providers/ # 音乐平台提供者目录
+ ├── index.js # Provider 工厂类
+ ├── base.js # 基础 Provider 接口
+ ├── netease.js # 网易云音乐 Provider
+ ├── tencent.js # 腾讯音乐 Provider
+ ├── xiami.js # 虾米音乐 Provider
+ ├── kugou.js # 酷狗音乐 Provider
+ ├── baidu.js # 百度音乐 Provider
+ └── kuwo.js # 酷我音乐 Provider
+```
+
+## 架构优势
+
+### 1. 单一职责原则
+- 每个 Provider 只负责一个音乐平台的逻辑
+- 主 Meting 类只负责协调和通用功能
+
+### 2. 开放封闭原则
+- 添加新平台只需创建新的 Provider,无需修改现有代码
+- 修改某个平台的逻辑不会影响其他平台
+
+### 3. 内部闭环设计
+- 每个 Provider 内部处理自己的编码/解码逻辑
+- 避免了主类中的方法映射和统一处理
+- 真正实现了平台逻辑的完全隔离
+
+### 4. 代码组织清晰
+- 每个文件职责明确,便于维护
+- 相关功能聚合在一起
+- 版本号在构建时从 package.json 注入,避免运行时文件读取
+
+## 核心组件
+
+### BaseProvider 基础类
+所有平台 Provider 的基础接口,定义了标准的方法:
+- `getHeaders()`: 获取请求头配置
+- `search()`: 搜索功能
+- `song()`: 获取歌曲详情
+- `album()`: 获取专辑信息
+- `artist()`: 获取艺术家作品
+- `playlist()`: 获取播放列表
+- `url()`: 获取播放链接
+- `lyric()`: 获取歌词
+- `pic()`: 获取封面图片
+- `format()`: 数据格式化
+- `encode()`: 请求编码(如需要)
+- `urlDecode()`: URL解码(如需要)
+- `lyricDecode()`: 歌词解码(如需要)
+
+### ProviderFactory 工厂类
+负责创建和管理 Provider 实例:
+- `create(platform, meting)`: 创建指定平台的 Provider
+- `getSupportedPlatforms()`: 获取支持的平台列表
+- `isSupported(platform)`: 检查平台是否支持
+
+### 主 Meting 类
+协调各个 Provider,提供统一的 API 接口:
+- 保持原有的公共 API 不变
+- 简化为纯粹的协调者角色
+- 将具体执行逻辑完全委托给 Provider
+- 版本号在构建时注入,无运行时开销
+
+## 使用方式
+
+重构后的使用方式与原版完全兼容:
+
+```javascript
+import Meting from './src/meting.js';
+
+// 创建实例
+const meting = new Meting('netease');
+
+// 或者动态切换平台
+meting.site('tencent');
+
+// 使用 API(与原版完全相同)
+const result = await meting.search('稻香');
+```
+
+## 扩展新平台
+
+添加新平台只需要:
+
+1. 在 `src/providers/` 下创建新的 Provider 文件
+2. 继承 `BaseProvider` 并实现所需方法
+3. 在 `src/providers/index.js` 中注册新 Provider
+
+示例:
+```javascript
+// src/providers/newplatform.js
+import BaseProvider from './base.js';
+
+export default class NewPlatformProvider extends BaseProvider {
+ constructor(meting) {
+ super(meting);
+ this.name = 'newplatform';
+ }
+
+ getHeaders() {
+ // 实现平台特定的请求头
+ }
+
+ search(keyword, option = {}) {
+ // 实现搜索逻辑
+ }
+
+ // ... 实现其他必需方法
+}
+```
+
+## 构建系统
+
+### Rollup 构建配置
+- 使用自定义插件在构建时注入版本号
+- 源码中使用 `__VERSION__` 占位符
+- 构建时自动替换为 package.json 中的实际版本
+- 避免运行时文件系统读取,提升性能
+
+### 构建流程
+```bash
+npm run build # 构建 ESM 和 CJS 两种格式
+```
+
+构建后:
+- `lib/meting.esm.js` - ES Module 格式
+- `lib/meting.js` - CommonJS 格式
+
+## 兼容性
+
+- ✅ 保持原有 API 接口不变
+- ✅ 保持原有使用方式不变
+- ✅ 保持原有功能特性不变
+- ✅ 支持原有的链式调用
+- ✅ 支持原有的配置方法
+- ✅ 构建时版本号注入,无运行时开销
+
+## 测试验证
+
+已通过以下测试:
+- 基础功能测试(`test/test.js`)
+- 架构验证测试(`test/simple-test.js`)
+- 版本号注入测试(`test/build-version-test.js`)
+- 各平台 Provider 测试
+
+重构成功保持了所有原有功能,同时大大提升了代码的可维护性和扩展性,并优化了运行时性能。
\ No newline at end of file
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..1d785df
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,250 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## 项目概述
+
+Meting Node.js 版本是一个强大的音乐 API 框架,用于加速音乐相关应用的开发。这是原版 PHP Meting 项目的 Node.js 移植版本,支持多个主流音乐平台的 API 调用,包括网易云音乐(netease)、腾讯音乐(tencent)、虾米音乐(xiami)、酷狗音乐(kugou)、百度音乐(baidu)和酷我音乐(kuwo)。
+
+## 开发相关命令
+
+### 构建和测试
+```bash
+# 构建库文件(生成 ESM 和 CJS 格式)
+npm run build
+
+# 开发模式(文件监听自动构建)
+npm run dev
+
+# 运行完整测试(会先构建)
+npm test
+
+# 运行示例代码
+npm start
+# 或
+npm run example
+
+# 直接测试特定文件(需要先构建)
+node test/test.js # 完整平台测试
+node test/example.js # 运行示例代码
+```
+
+### 单独测试命令
+```bash
+# 快速测试单个平台(需要先构建)
+node -e "
+import Meting from './lib/meting.js';
+const m = new Meting('netease');
+m.format(true);
+m.search('test', { limit: 5 }).then(console.log);
+"
+
+# 验证版本号注入
+node -e "import Meting from './lib/meting.js'; console.log('Version:', (new Meting()).VERSION)"
+```
+
+### 环境要求
+- Node.js >= 12.0.0
+- 无外部依赖,仅使用 Node.js 内置模块
+
+## 核心架构
+
+### Provider 模式设计
+项目采用 Provider 模式重构,实现了真正的内部闭环设计:
+
+- **主 Meting 类** (`src/meting.js`): 纯粹的协调者,负责平台切换和 API 委托
+- **Provider 工厂** (`src/providers/index.js`): 管理所有平台 Provider 的创建和注册
+- **基础 Provider** (`src/providers/base.js`): 定义统一接口和默认实现
+- **平台 Provider** (`src/providers/{platform}.js`): 每个音乐平台的独立实现
+- **统一 EAPI 流程**: 所有请求都通过统一的 EAPI 请求栈执行,无需用户选择不同的协议
+
+### 关键设计原则
+
+1. **内部闭环**: 每个 Provider 完全独立处理自己的编码/解码逻辑,通过 `handleEncode()` 和 `handleDecode()` 方法
+2. **单一职责**: 每个文件只负责一个平台或一个功能模块
+3. **无方法映射**: 避免了主类中的方法名映射,Provider 内部直接处理特定逻辑
+
+### 执行流程
+```
+用户 API 调用 → 主 Meting 类 → Provider.executeRequest() → 平台特定处理 → 返回标准化结果
+```
+
+**详细请求处理流程:**
+1. **API 调用**: `meting.search('关键词', { page: 1, limit: 30 })` 等公共方法
+2. **Provider 委托**: 主类调用对应 Provider 的方法获取 API 配置
+3. **编码处理**: 如果需要,Provider 调用 `handleEncode()` 进行请求加密
+4. **HTTP 请求**: 使用内置 fetch API 发送统一的 EAPI 请求,包含重试机制和超时控制
+5. **解码处理**: 如果需要,Provider 调用 `handleDecode()` 进行响应解密
+6. **数据格式化**: 根据 `format()` 设置决定是否标准化数据结构
+7. **结果返回**: 返回 JSON 字符串格式的处理结果
+
+## 公共 API
+- `search(keyword, option = {})`: 根据关键词搜索音乐,返回 Promise;`option` 支持 `type`(分类,默认 1 即歌曲)、`page`(页码,默认 1)、`limit`(每页数量,默认 30)
+- `song(id)`: 获取歌曲详情
+- `album(id)`: 获取专辑信息
+- `artist(id, limit = 50)`: 获取艺术家作品列表
+- `playlist(id)`: 获取播放列表
+- `url(id, br = 320)`: 获取音频播放地址,可指定码率(kbps)
+- `lyric(id)`: 获取歌词
+- `pic(id, size = 300)`: 获取封面图片信息,可指定尺寸
+
+### 错误处理机制
+- **网络错误**: 自动重试 3 次,每次间隔 1 秒
+- **超时控制**: 默认 20 秒请求超时
+- **平台切换**: 支持动态切换音乐平台作为降级方案
+- **错误状态**: 错误信息存储在 `meting.error` 和 `meting.status` 属性中
+
+### 版本号管理
+- 源码中使用 `__VERSION__` 占位符
+- 构建时通过 Rollup 自定义插件注入 package.json 中的实际版本号
+- 避免运行时文件系统读取,提升性能
+
+**构建注入机制详情:**
+```javascript
+// rollup.config.js 中的版本注入插件
+{
+ name: 'inject-version',
+ transform(code, id) {
+ if (id.endsWith('src/meting.js')) {
+ return code.replace('__VERSION__', packageInfo.version);
+ }
+ return null;
+ }
+}
+```
+
+**使用方式:**
+```javascript
+const meting = new Meting();
+console.log(meting.VERSION); // 输出实际版本号,如 "1.5.13"
+```
+
+## 重要设计模式
+
+### 适配器模式
+每个音乐平台都有对应的格式化方法,统一数据结构:
+- 各平台返回统一的 JSON 格式
+- `format(true)` 开启时进行数据标准化
+
+### 策略模式
+不同平台的请求处理策略通过 Provider 实现:
+- 每个平台有独特的 API 端点配置
+- 平台特定的加密和签名方式
+- 动态的请求头和参数处理
+
+### 内置加密支持
+- 网易云音乐:AES-CBC 加密 + RSA 公钥加密
+- 虾米音乐:签名验证机制
+- 百度音乐:AES 加密
+- 使用 Node.js 内置 crypto 模块,无外部依赖
+
+## 添加新平台
+
+添加新平台的完整流程:
+
+1. 在 `src/providers/` 下创建新的 Provider 文件
+2. 继承 `BaseProvider` 并实现所有必需方法
+3. 实现平台特定的 `handleEncode()` 和 `handleDecode()` 方法
+4. 在 `src/providers/index.js` 中注册新 Provider
+5. 添加对应的测试用例
+
+```javascript
+// src/providers/newplatform.js
+import BaseProvider from './base.js';
+
+export default class NewPlatformProvider extends BaseProvider {
+ constructor(meting) {
+ super(meting);
+ this.name = 'newplatform';
+ }
+
+ getHeaders() {
+ // 实现平台特定的请求头
+ }
+
+ search(keyword, option = {}) {
+ // 实现搜索逻辑并返回统一的 EAPI 配置
+ }
+
+ async handleEncode(api) {
+ // 处理平台特定的编码逻辑
+ return api;
+ }
+
+ async handleDecode(decodeType, data) {
+ // 处理平台特定的解码逻辑
+ return data;
+ }
+
+ // ... 实现其他必需方法
+}
+```
+
+## 数据格式
+
+### 标准化歌曲格式(format: true)
+```javascript
+{
+ "id": "歌曲ID",
+ "name": "歌曲名称",
+ "artist": ["艺术家1", "艺术家2"],
+ "album": "专辑名称",
+ "pic_id": "封面图片ID",
+ "url_id": "播放链接ID",
+ "lyric_id": "歌词ID",
+ "source": "平台标识"
+}
+```
+
+## 注意事项
+
+### API 调用限制
+- 建议在连续请求间添加延迟(测试中使用 2 秒间隔)
+- 避免过于频繁的 API 调用导致被限制
+- 各平台可能有不同的频率限制策略
+
+### 平台兼容性
+- 音乐平台 API 可能随时变更,需要定期更新
+- 部分功能可能因版权限制无法获取(如播放链接)
+- 某些平台可能需要 Cookie 验证
+
+### 错误处理模式
+```javascript
+const meting = new Meting('netease');
+meting.format(true);
+
+try {
+ const result = await meting.search('关键词', { page: 1, limit: 30 });
+ // 处理统一格式的结果
+} catch (error) {
+ // 记录错误并尝试切换平台重试
+ console.error(meting.status);
+ meting.site('tencent');
+ const fallback = await meting.search('关键词', { page: 1, limit: 30 });
+}
+```
+
+## 构建系统
+
+### Rollup 配置特点
+- **双格式输出**: 同时生成 ESM (`lib/meting.esm.js`) 和 CJS (`lib/meting.js`) 格式
+- **版本号注入**: 构建时自动替换源码中的 `__VERSION__` 占位符
+- **依赖管理**: 仅使用 Node.js 内置模块,external 配置包含 `crypto`, `url`, `fs`, `path`
+- **代码压缩**: 使用 terser 插件进行代码压缩优化
+- **开发模式**: 支持文件监听自动构建 (`npm run dev`)
+
+### 构建流程
+```bash
+# 开发流程
+npm run dev # 监听文件变化,自动重新构建
+
+# 生产构建
+npm run build # 构建两种格式到 lib/ 目录
+npm test # 构建后运行测试验证
+npm publish # 发布前会自动执行 prepublishOnly 构建命令
+```
+
+### 输出文件说明
+- `lib/meting.esm.js`: ES Module 格式,用于现代打包工具
+- `lib/meting.js`: CommonJS 格式,用于 Node.js require 语法
+- 两种格式都经过压缩优化,文件大小约 30-40KB
diff --git a/README.md b/README.md
index 0c4fbb1..e80be8b 100644
--- a/README.md
+++ b/README.md
@@ -2,95 +2,285 @@
+Made with ❤️ for the music community +
diff --git a/composer.json b/composer.json deleted file mode 100644 index 0b54c4e..0000000 --- a/composer.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "metowolf/meting", - "type": "library", - "description": "A powerful music API framework to accelerate development.", - "keywords": [ - "php music", - "music api", - "lighty", - "lightweight" - ], - "homepage": "https://github.com/metowolf/Meting", - "license": "MIT", - "authors": [ - { - "name": "metowolf", - "email": "i@i-meto.com", - "homepage": "https://i-meto.com" - } - ], - "support": { - "issues": "https://github.com/metowolf/Meting/issues", - "source": "https://github.com/metowolf/Meting" - }, - "require": { - "php": ">=5.4.0", - "ext-curl": "*", - "ext-openssl": "*" - }, - "suggest": { - "ext-bcmath": "Required to use BC Math calculate RSA.", - "ext-openssl": "Required to use OpenSSL encrypt params." - }, - "autoload": { - "psr-4": { - "Metowolf\\" : "src/" - } - } -} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6a5f8c2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1173 @@ +{ + "name": "@meting/core", + "version": "1.5.12", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@meting/core", + "version": "1.5.12", + "license": "MIT", + "devDependencies": { + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-node-resolve": "^16.0.1", + "@rollup/plugin-terser": "^0.4.4", + "rollup": "^4.49.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://mirrors.tencent.com/npm/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://mirrors.tencent.com/npm/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.0", + "resolved": "https://mirrors.tencent.com/npm/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.3", + "resolved": "https://mirrors.tencent.com/npm/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", + "dev": true, + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://mirrors.tencent.com/npm/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://mirrors.tencent.com/npm/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://mirrors.tencent.com/npm/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://mirrors.tencent.com/npm/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://mirrors.tencent.com/npm/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://mirrors.tencent.com/npm/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://mirrors.tencent.com/npm/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://mirrors.tencent.com/npm/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.3", + "resolved": "https://mirrors.tencent.com/npm/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.3", + "resolved": "https://mirrors.tencent.com/npm/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.28.2" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://mirrors.tencent.com/npm/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.3", + "resolved": "https://mirrors.tencent.com/npm/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://mirrors.tencent.com/npm/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://mirrors.tencent.com/npm/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://mirrors.tencent.com/npm/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://mirrors.tencent.com/npm/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://mirrors.tencent.com/npm/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://mirrors.tencent.com/npm/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/plugin-babel": { + "version": "6.0.4", + "resolved": "https://mirrors.tencent.com/npm/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz", + "integrity": "sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@rollup/pluginutils": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.1", + "resolved": "https://mirrors.tencent.com/npm/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz", + "integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://mirrors.tencent.com/npm/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.2.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", + "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.49.0.tgz", + "integrity": "sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.49.0.tgz", + "integrity": "sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.49.0.tgz", + "integrity": "sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.49.0.tgz", + "integrity": "sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.49.0.tgz", + "integrity": "sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.49.0.tgz", + "integrity": "sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.49.0.tgz", + "integrity": "sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.49.0.tgz", + "integrity": "sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.49.0.tgz", + "integrity": "sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.49.0.tgz", + "integrity": "sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.49.0.tgz", + "integrity": "sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.49.0.tgz", + "integrity": "sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.49.0.tgz", + "integrity": "sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.49.0.tgz", + "integrity": "sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.49.0.tgz", + "integrity": "sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.49.0.tgz", + "integrity": "sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.49.0.tgz", + "integrity": "sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.49.0.tgz", + "integrity": "sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.49.0.tgz", + "integrity": "sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.49.0.tgz", + "integrity": "sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://mirrors.tencent.com/npm/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://mirrors.tencent.com/npm/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://mirrors.tencent.com/npm/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/browserslist": { + "version": "4.25.3", + "resolved": "https://mirrors.tencent.com/npm/browserslist/-/browserslist-4.25.3.tgz", + "integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001735", + "electron-to-chromium": "^1.5.204", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://mirrors.tencent.com/npm/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001737", + "resolved": "https://mirrors.tencent.com/npm/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", + "integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://mirrors.tencent.com/npm/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://mirrors.tencent.com/npm/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "peer": true + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://mirrors.tencent.com/npm/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://mirrors.tencent.com/npm/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.211", + "resolved": "https://mirrors.tencent.com/npm/electron-to-chromium/-/electron-to-chromium-1.5.211.tgz", + "integrity": "sha512-IGBvimJkotaLzFnwIVgW9/UD/AOJ2tByUmeOrtqBfACSbAw5b1G0XpvdaieKyc7ULmbwXVx+4e4Be8pOPBrYkw==", + "dev": true, + "peer": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://mirrors.tencent.com/npm/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://mirrors.tencent.com/npm/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://mirrors.tencent.com/npm/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://mirrors.tencent.com/npm/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://mirrors.tencent.com/npm/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://mirrors.tencent.com/npm/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://mirrors.tencent.com/npm/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://mirrors.tencent.com/npm/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://mirrors.tencent.com/npm/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://mirrors.tencent.com/npm/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://mirrors.tencent.com/npm/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "peer": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://mirrors.tencent.com/npm/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "peer": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://mirrors.tencent.com/npm/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://mirrors.tencent.com/npm/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "peer": true + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://mirrors.tencent.com/npm/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://mirrors.tencent.com/npm/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://mirrors.tencent.com/npm/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://mirrors.tencent.com/npm/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://mirrors.tencent.com/npm/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "4.49.0", + "resolved": "https://mirrors.tencent.com/npm/rollup/-/rollup-4.49.0.tgz", + "integrity": "sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.49.0", + "@rollup/rollup-android-arm64": "4.49.0", + "@rollup/rollup-darwin-arm64": "4.49.0", + "@rollup/rollup-darwin-x64": "4.49.0", + "@rollup/rollup-freebsd-arm64": "4.49.0", + "@rollup/rollup-freebsd-x64": "4.49.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.49.0", + "@rollup/rollup-linux-arm-musleabihf": "4.49.0", + "@rollup/rollup-linux-arm64-gnu": "4.49.0", + "@rollup/rollup-linux-arm64-musl": "4.49.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.49.0", + "@rollup/rollup-linux-ppc64-gnu": "4.49.0", + "@rollup/rollup-linux-riscv64-gnu": "4.49.0", + "@rollup/rollup-linux-riscv64-musl": "4.49.0", + "@rollup/rollup-linux-s390x-gnu": "4.49.0", + "@rollup/rollup-linux-x64-gnu": "4.49.0", + "@rollup/rollup-linux-x64-musl": "4.49.0", + "@rollup/rollup-win32-arm64-msvc": "4.49.0", + "@rollup/rollup-win32-ia32-msvc": "4.49.0", + "@rollup/rollup-win32-x64-msvc": "4.49.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://mirrors.tencent.com/npm/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://mirrors.tencent.com/npm/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://mirrors.tencent.com/npm/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/smob": { + "version": "1.5.0", + "resolved": "https://mirrors.tencent.com/npm/smob/-/smob-1.5.0.tgz", + "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://mirrors.tencent.com/npm/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://mirrors.tencent.com/npm/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://mirrors.tencent.com/npm/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/terser": { + "version": "5.43.1", + "resolved": "https://mirrors.tencent.com/npm/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://mirrors.tencent.com/npm/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://mirrors.tencent.com/npm/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "peer": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f0d17c0 --- /dev/null +++ b/package.json @@ -0,0 +1,68 @@ +{ + "name": "@meting/core", + "version": "1.5.13", + "description": "A powerful music API framework to accelerate development - Node.js port. Support Netease, Tencent, Xiami, Kugou, Baidu, Kuwo music platforms.", + "main": "./lib/meting.js", + "module": "./lib/meting.esm.js", + "type": "module", + "exports": { + "import": "./lib/meting.esm.js", + "require": "./lib/meting.js" + }, + "files": [ + "lib/", + "LICENSE" + ], + "scripts": { + "build": "rollup -c", + "dev": "rollup -c --watch", + "prepublishOnly": "npm run build", + "test": "npm run build && node test/test.js", + "example": "npm run build && node test/example.js", + "start": "npm run build && node test/example.js" + }, + "keywords": [ + "music", + "api", + "netease", + "tencent", + "xiami", + "kugou", + "baidu", + "kuwo", + "nodejs", + "lightweight", + "music-api", + "streaming", + "search", + "lyrics", + "no-dependencies" + ], + "author": { + "name": "metowolf", + "email": "i@i-meto.com", + "url": "https://i-meto.com" + }, + "contributors": [ + { + "name": "Claude Code", + "email": "noreply@anthropic.com", + "url": "https://claude.ai/code" + } + ], + "license": "MIT", + "homepage": "https://github.com/metowolf/Meting", + "repository": { + "type": "git", + "url": "https://github.com/metowolf/Meting.git" + }, + "bugs": { + "url": "https://github.com/metowolf/Meting/issues" + }, + "devDependencies": { + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-node-resolve": "^16.0.1", + "@rollup/plugin-terser": "^0.4.4", + "rollup": "^4.49.0" + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..6bfabe4 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,47 @@ +import { nodeResolve } from '@rollup/plugin-node-resolve'; +import { babel } from '@rollup/plugin-babel'; +import { readFileSync } from 'fs'; +import terser from '@rollup/plugin-terser' + +// 读取 package.json 中的版本号 +const packageInfo = JSON.parse(readFileSync('./package.json', 'utf8')); + +export default { + input: 'src/meting.js', + output: [ + { + file: 'lib/meting.esm.js', + format: 'es' + }, + { + file: 'lib/meting.js', + format: 'cjs', + exports: 'auto' + } + ], + plugins: [ + nodeResolve({ + preferBuiltins: true + }), + babel({ babelHelpers: 'bundled' }), + // 版本号注入插件 + { + name: 'inject-version', + transform(code, id) { + if (id.endsWith('src/meting.js')) { + return code.replace('__VERSION__', packageInfo.version); + } + return null; + } + }, + terser({ + format: { + comments: false + }, + compress: { + drop_console: false + } + }) + ], + external: ['crypto', 'url', 'fs', 'path'] +}; \ No newline at end of file diff --git a/src/Meting.php b/src/Meting.php deleted file mode 100644 index d307c17..0000000 --- a/src/Meting.php +++ /dev/null @@ -1,1537 +0,0 @@ - - * Released under the MIT license - */ - -namespace Metowolf; - -class Meting -{ - const VERSION = '1.5.11'; - - public $raw; - public $data; - public $info; - public $error; - public $status; - - public $server; - public $proxy = null; - public $format = false; - public $header; - - public function __construct($value = 'netease') - { - $this->site($value); - } - - public function site($value) - { - $suppose = array('netease', 'tencent', 'xiami', 'kugou', 'baidu', 'kuwo'); - $this->server = in_array($value, $suppose) ? $value : 'netease'; - $this->header = $this->curlset(); - - return $this; - } - - public function cookie($value) - { - $this->header['Cookie'] = $value; - - return $this; - } - - public function format($value = true) - { - $this->format = $value; - - return $this; - } - - public function proxy($value) - { - $this->proxy = $value; - - return $this; - } - - private function exec($api) - { - if (isset($api['encode'])) { - $api = call_user_func_array(array($this, $api['encode']), array($api)); - } - if ($api['method'] == 'GET') { - if (isset($api['body'])) { - $api['url'] .= '?'.http_build_query($api['body']); - $api['body'] = null; - } - } - - $this->curl($api['url'], $api['body']); - - if (!$this->format) { - return $this->raw; - } - - $this->data = $this->raw; - - if (isset($api['decode'])) { - $this->data = call_user_func_array(array($this, $api['decode']), array($this->data)); - } - if (isset($api['format'])) { - $this->data = $this->clean($this->data, $api['format']); - } - - return $this->data; - } - - private function curl($url, $payload = null, $headerOnly = 0) - { - $header = array_map(function ($k, $v) { - return $k.': '.$v; - }, array_keys($this->header), $this->header); - $curl = curl_init(); - if (!is_null($payload)) { - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, is_array($payload) ? http_build_query($payload) : $payload); - } - curl_setopt($curl, CURLOPT_HEADER, $headerOnly); - curl_setopt($curl, CURLOPT_TIMEOUT, 20); - curl_setopt($curl, CURLOPT_ENCODING, 'gzip'); - curl_setopt($curl, CURLOPT_IPRESOLVE, 1); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10); - curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_HTTPHEADER, $header); - if ($this->proxy) { - curl_setopt($curl, CURLOPT_PROXY, $this->proxy); - } - for ($i = 0; $i < 3; $i++) { - $this->raw = curl_exec($curl); - $this->info = curl_getinfo($curl); - $this->error = curl_errno($curl); - $this->status = $this->error ? curl_error($curl) : ''; - if (!$this->error) { - break; - } - } - curl_close($curl); - - return $this; - } - - private function pickup($array, $rule) - { - $t = explode('.', $rule); - foreach ($t as $vo) { - if (!isset($array[$vo])) { - return array(); - } - $array = $array[$vo]; - } - - return $array; - } - - private function clean($raw, $rule) - { - $raw = json_decode($raw, true); - if (!empty($rule)) { - $raw = $this->pickup($raw, $rule); - } - if (!isset($raw[0]) && count($raw)) { - $raw = array($raw); - } - $result = array_map(array($this, 'format_'.$this->server), $raw); - - return json_encode($result); - } - - public function search($keyword, $option = null) - { - switch ($this->server) { - case 'netease': - $api = array( - 'method' => 'POST', - 'url' => 'http://music.163.com/api/cloudsearch/pc', - 'body' => array( - 's' => $keyword, - 'type' => isset($option['type']) ? $option['type'] : 1, - 'limit' => isset($option['limit']) ? $option['limit'] : 30, - 'total' => 'true', - 'offset' => isset($option['page']) && isset($option['limit']) ? ($option['page'] - 1) * $option['limit'] : 0, - ), - 'encode' => 'netease_AESCBC', - 'format' => 'result.songs', - ); - break; - case 'tencent': - $api = array( - 'method' => 'GET', - 'url' => 'https://c.y.qq.com/soso/fcgi-bin/client_search_cp', - 'body' => array( - 'format' => 'json', - 'p' => isset($option['page']) ? $option['page'] : 1, - 'n' => isset($option['limit']) ? $option['limit'] : 30, - 'w' => $keyword, - 'aggr' => 1, - 'lossless' => 1, - 'cr' => 1, - 'new_json' => 1, - ), - 'format' => 'data.song.list', - ); - break; - case 'xiami': - $api = array( - 'method' => 'GET', - 'url' => 'https://acs.m.xiami.com/h5/mtop.alimusic.search.searchservice.searchsongs/1.0/', - 'body' => array( - 'data' => array( - 'key' => $keyword, - 'pagingVO' => array( - 'page' => isset($option['page']) ? $option['page'] : 1, - 'pageSize' => isset($option['limit']) ? $option['limit'] : 30, - ), - ), - 'r' => 'mtop.alimusic.search.searchservice.searchsongs', - ), - 'encode' => 'xiami_sign', - 'format' => 'data.data.songs', - ); - break; - case 'kugou': - $api = array( - 'method' => 'GET', - 'url' => 'http://mobilecdn.kugou.com/api/v3/search/song', - 'body' => array( - 'api_ver' => 1, - 'area_code' => 1, - 'correct' => 1, - 'pagesize' => isset($option['limit']) ? $option['limit'] : 30, - 'plat' => 2, - 'tag' => 1, - 'sver' => 5, - 'showtype' => 10, - 'page' => isset($option['page']) ? $option['page'] : 1, - 'keyword' => $keyword, - 'version' => 8990, - ), - 'format' => 'data.info', - ); - break; - case 'baidu': - $api = array( - 'method' => 'GET', - 'url' => 'http://musicapi.taihe.com/v1/restserver/ting', - 'body' => array( - 'from' => 'qianqianmini', - 'method' => 'baidu.ting.search.merge', - 'isNew' => 1, - 'platform' => 'darwin', - 'page_no' => isset($option['page']) ? $option['page'] : 1, - 'query' => $keyword, - 'version' => '11.2.1', - 'page_size' => isset($option['limit']) ? $option['limit'] : 30, - ), - 'format' => 'result.song_info.song_list', - ); - break; - case 'kuwo': - $api = array( - 'method' => 'GET', - 'url' => 'http://www.kuwo.cn/api/www/search/searchMusicBykeyWord', - 'body' => array( - 'key' => $keyword, - 'pn' => isset($option['page']) ? $option['page'] : 1, - 'rn' => isset($option['limit']) ? $option['limit'] : 30, - 'httpsStatus' => 1, - ), - 'format' => 'data.list', - ); - break; - } - - return $this->exec($api); - } - - public function song($id) - { - switch ($this->server) { - case 'netease': - $api = array( - 'method' => 'POST', - 'url' => 'http://music.163.com/api/v3/song/detail/', - 'body' => array( - 'c' => '[{"id":'.$id.',"v":0}]', - ), - 'encode' => 'netease_AESCBC', - 'format' => 'songs', - ); - break; - case 'tencent': - $api = array( - 'method' => 'GET', - 'url' => 'https://c.y.qq.com/v8/fcg-bin/fcg_play_single_song.fcg', - 'body' => array( - 'songmid' => $id, - 'platform' => 'yqq', - 'format' => 'json', - ), - 'format' => 'data', - ); - break; - case 'xiami': - $api = array( - 'method' => 'GET', - 'url' => 'https://acs.m.xiami.com/h5/mtop.alimusic.music.songservice.getsongdetail/1.0/', - 'body' => array( - 'data' => array( - 'songId' => $id, - ), - 'r' => 'mtop.alimusic.music.songservice.getsongdetail', - ), - 'encode' => 'xiami_sign', - 'format' => 'data.data.songDetail', - ); - break; - case 'kugou': - $api = array( - 'method' => 'POST', - 'url' => 'http://m.kugou.com/app/i/getSongInfo.php', - 'body' => array( - 'cmd' => 'playInfo', - 'hash' => $id, - 'from' => 'mkugou', - ), - 'format' => '', - ); - break; - case 'baidu': - $api = array( - 'method' => 'GET', - 'url' => 'http://musicapi.taihe.com/v1/restserver/ting', - 'body' => array( - 'from' => 'qianqianmini', - 'method' => 'baidu.ting.song.getInfos', - 'songid' => $id, - 'res' => 1, - 'platform' => 'darwin', - 'version' => '1.0.0', - ), - 'encode' => 'baidu_AESCBC', - 'format' => 'songinfo', - ); - break; - case 'kuwo': - $api = array( - 'method' => 'GET', - 'url' => 'http://www.kuwo.cn/api/www/music/musicInfo', - 'body' => array( - 'mid' => $id, - 'httpsStatus' => 1, - ), - 'format' => 'data', - ); - break; - } - - return $this->exec($api); - } - - public function album($id) - { - switch ($this->server) { - case 'netease': - $api = array( - 'method' => 'POST', - 'url' => 'http://music.163.com/api/v1/album/'.$id, - 'body' => array( - 'total' => 'true', - 'offset' => '0', - 'id' => $id, - 'limit' => '1000', - 'ext' => 'true', - 'private_cloud' => 'true', - ), - 'encode' => 'netease_AESCBC', - 'format' => 'songs', - ); - break; - case 'tencent': - $api = array( - 'method' => 'GET', - 'url' => 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_album_detail_cp.fcg', - 'body' => array( - 'albummid' => $id, - 'platform' => 'mac', - 'format' => 'json', - 'newsong' => 1, - ), - 'format' => 'data.getSongInfo', - ); - break; - case 'xiami': - $api = array( - 'method' => 'GET', - 'url' => 'https://acs.m.xiami.com/h5/mtop.alimusic.music.albumservice.getalbumdetail/1.0/', - 'body' => array( - 'data' => array( - 'albumId' => $id, - ), - 'r' => 'mtop.alimusic.music.albumservice.getalbumdetail', - ), - 'encode' => 'xiami_sign', - 'format' => 'data.data.albumDetail.songs', - ); - break; - case 'kugou': - $api = array( - 'method' => 'GET', - 'url' => 'http://mobilecdn.kugou.com/api/v3/album/song', - 'body' => array( - 'albumid' => $id, - 'area_code' => 1, - 'plat' => 2, - 'page' => 1, - 'pagesize' => -1, - 'version' => 8990, - ), - 'format' => 'data.info', - ); - break; - case 'baidu': - $api = array( - 'method' => 'GET', - 'url' => 'http://musicapi.taihe.com/v1/restserver/ting', - 'body' => array( - 'from' => 'qianqianmini', - 'method' => 'baidu.ting.album.getAlbumInfo', - 'album_id' => $id, - 'platform' => 'darwin', - 'version' => '11.2.1', - ), - 'format' => 'songlist', - ); - break; - case 'kuwo': - $api = array( - 'method' => 'GET', - 'url' => 'http://www.kuwo.cn/api/www/album/albumInfo', - 'body' => array( - 'albumId' => $id, - 'pn' => 1, - 'rn' => 1000, - 'httpsStatus' => 1, - ), - 'format' => 'data.musicList', - ); - break; - } - - return $this->exec($api); - } - - public function artist($id, $limit = 50) - { - switch ($this->server) { - case 'netease': - $api = array( - 'method' => 'POST', - 'url' => 'http://music.163.com/api/v1/artist/'.$id, - 'body' => array( - 'ext' => 'true', - 'private_cloud' => 'true', - 'ext' => 'true', - 'top' => $limit, - 'id' => $id, - ), - 'encode' => 'netease_AESCBC', - 'format' => 'hotSongs', - ); - break; - case 'tencent': - $api = array( - 'method' => 'GET', - 'url' => 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg', - 'body' => array( - 'singermid' => $id, - 'begin' => 0, - 'num' => $limit, - 'order' => 'listen', - 'platform' => 'mac', - 'newsong' => 1, - ), - 'format' => 'data.list', - ); - break; - case 'xiami': - $api = array( - 'method' => 'GET', - 'url' => 'https://acs.m.xiami.com/h5/mtop.alimusic.music.songservice.getartistsongs/1.0/', - 'body' => array( - 'data' => array( - 'artistId' => $id, - 'pagingVO' => array( - 'page' => 1, - 'pageSize' => $limit, - ), - ), - 'r' => 'mtop.alimusic.music.songservice.getartistsongs', - ), - 'encode' => 'xiami_sign', - 'format' => 'data.data.songs', - ); - break; - case 'kugou': - $api = array( - 'method' => 'GET', - 'url' => 'http://mobilecdn.kugou.com/api/v3/singer/song', - 'body' => array( - 'singerid' => $id, - 'area_code' => 1, - 'page' => 1, - 'plat' => 0, - 'pagesize' => $limit, - 'version' => 8990, - ), - 'format' => 'data.info', - ); - break; - case 'baidu': - $api = array( - 'method' => 'GET', - 'url' => 'http://musicapi.taihe.com/v1/restserver/ting', - 'body' => array( - 'from' => 'qianqianmini', - 'method' => 'baidu.ting.artist.getSongList', - 'artistid' => $id, - 'limits' => $limit, - 'platform' => 'darwin', - 'offset' => 0, - 'tinguid' => 0, - 'version' => '11.2.1', - ), - 'format' => 'songlist', - ); - break; - case 'kuwo': - $api = array( - 'method' => 'GET', - 'url' => 'http://www.kuwo.cn/api/www/artist/artistMusic', - 'body' => array( - 'artistid' => $id, - 'pn' => 1, - 'rn' => $limit, - 'httpsStatus' => 1, - ), - 'format' => 'data.list', - ); - break; - } - - return $this->exec($api); - } - - public function playlist($id) - { - switch ($this->server) { - case 'netease': - $api = array( - 'method' => 'POST', - 'url' => 'http://music.163.com/api/v6/playlist/detail', - 'body' => array( - 's' => '0', - 'id' => $id, - 'n' => '1000', - 't' => '0', - ), - 'encode' => 'netease_AESCBC', - 'format' => 'playlist.tracks', - ); - break; - case 'tencent': - $api = array( - 'method' => 'GET', - 'url' => 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_playlist_cp.fcg', - 'body' => array( - 'id' => $id, - 'format' => 'json', - 'newsong' => 1, - 'platform' => 'jqspaframe.json', - ), - 'format' => 'data.cdlist.0.songlist', - ); - break; - case 'xiami': - $api = array( - 'method' => 'GET', - 'url' => 'https://acs.m.xiami.com/h5/mtop.alimusic.music.list.collectservice.getcollectdetail/1.0/', - 'body' => array( - 'data' => array( - 'listId' => $id, - 'isFullTags' => false, - 'pagingVO' => array( - 'page' => 1, - 'pageSize' => 1000, - ), - ), - 'r' => 'mtop.alimusic.music.list.collectservice.getcollectdetail', - ), - 'encode' => 'xiami_sign', - 'format' => 'data.data.collectDetail.songs', - ); - break; - case 'kugou': - $api = array( - 'method' => 'GET', - 'url' => 'http://mobilecdn.kugou.com/api/v3/special/song', - 'body' => array( - 'specialid' => $id, - 'area_code' => 1, - 'page' => 1, - 'plat' => 2, - 'pagesize' => -1, - 'version' => 8990, - ), - 'format' => 'data.info', - ); - break; - case 'baidu': - $api = array( - 'method' => 'GET', - 'url' => 'http://musicapi.taihe.com/v1/restserver/ting', - 'body' => array( - 'from' => 'qianqianmini', - 'method' => 'baidu.ting.diy.gedanInfo', - 'listid' => $id, - 'platform' => 'darwin', - 'version' => '11.2.1', - ), - 'format' => 'content', - ); - break; - case 'kuwo': - $api = array( - 'method' => 'GET', - 'url' => 'http://www.kuwo.cn/api/www/playlist/playListInfo', - 'body' => array( - 'pid' => $id, - 'pn' => 1, - 'rn' => 1000, - 'httpsStatus' => 1, - ), - 'format' => 'data.musicList', - ); - break; - } - - return $this->exec($api); - } - - public function url($id, $br = 320) - { - switch ($this->server) { - case 'netease': - $api = array( - 'method' => 'POST', - 'url' => 'http://music.163.com/api/song/enhance/player/url', - 'body' => array( - 'ids' => array($id), - 'br' => $br * 1000, - ), - 'encode' => 'netease_AESCBC', - 'decode' => 'netease_url', - ); - break; - case 'tencent': - $api = array( - 'method' => 'GET', - 'url' => 'https://c.y.qq.com/v8/fcg-bin/fcg_play_single_song.fcg', - 'body' => array( - 'songmid' => $id, - 'platform' => 'yqq', - 'format' => 'json', - ), - 'decode' => 'tencent_url', - ); - break; - case 'xiami': - $api = array( - 'method' => 'GET', - 'url' => 'https://acs.m.xiami.com/h5/mtop.alimusic.music.songservice.getsongs/1.0/', - 'body' => array( - 'data' => array( - 'songIds' => array( - $id, - ), - ), - 'r' => 'mtop.alimusic.music.songservice.getsongs', - ), - 'encode' => 'xiami_sign', - 'decode' => 'xiami_url', - ); - break; - case 'kugou': - $api = array( - 'method' => 'POST', - 'url' => 'http://media.store.kugou.com/v1/get_res_privilege', - 'body' => json_encode( - array( - 'relate' => 1, - 'userid' => '0', - 'vip' => 0, - 'appid' => 1000, - 'token' => '', - 'behavior' => 'download', - 'area_code' => '1', - 'clientver' => '8990', - 'resource' => array(array( - 'id' => 0, - 'type' => 'audio', - 'hash' => $id, - )), ) - ), - 'decode' => 'kugou_url', - ); - break; - case 'baidu': - $api = array( - 'method' => 'GET', - 'url' => 'http://musicapi.taihe.com/v1/restserver/ting', - 'body' => array( - 'from' => 'qianqianmini', - 'method' => 'baidu.ting.song.getInfos', - 'songid' => $id, - 'res' => 1, - 'platform' => 'darwin', - 'version' => '1.0.0', - ), - 'encode' => 'baidu_AESCBC', - 'decode' => 'baidu_url', - ); - break; - case 'kuwo': - $api = array( - 'method' => 'GET', - 'url' => 'http://www.kuwo.cn/api/v1/www/music/playUrl', - 'body' => array( - 'mid' => $id, - 'type' => 'music', - 'httpsStatus' => 1, - ), - 'decode' => 'kuwo_url', - ); - break; - } - $this->temp['br'] = $br; - - return $this->exec($api); - } - - public function lyric($id) - { - switch ($this->server) { - case 'netease': - $api = array( - 'method' => 'POST', - 'url' => 'http://music.163.com/api/song/lyric', - 'body' => array( - 'id' => $id, - 'os' => 'linux', - 'lv' => -1, - 'kv' => -1, - 'tv' => -1, - ), - 'encode' => 'netease_AESCBC', - 'decode' => 'netease_lyric', - ); - break; - case 'tencent': - $api = array( - 'method' => 'GET', - 'url' => 'https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg', - 'body' => array( - 'songmid' => $id, - 'g_tk' => '5381', - ), - 'decode' => 'tencent_lyric', - ); - break; - case 'xiami': - $api = array( - 'method' => 'GET', - 'url' => 'https://acs.m.xiami.com/h5/mtop.alimusic.music.lyricservice.getsonglyrics/1.0/', - 'body' => array( - 'data' => array( - 'songId' => $id, - ), - 'r' => 'mtop.alimusic.music.lyricservice.getsonglyrics', - ), - 'encode' => 'xiami_sign', - 'decode' => 'xiami_lyric', - ); - break; - case 'kugou': - $api = array( - 'method' => 'GET', - 'url' => 'http://krcs.kugou.com/search', - 'body' => array( - 'keyword' => '%20-%20', - 'ver' => 1, - 'hash' => $id, - 'client' => 'mobi', - 'man' => 'yes', - ), - 'decode' => 'kugou_lyric', - ); - break; - case 'baidu': - $api = array( - 'method' => 'GET', - 'url' => 'http://musicapi.taihe.com/v1/restserver/ting', - 'body' => array( - 'from' => 'qianqianmini', - 'method' => 'baidu.ting.song.lry', - 'songid' => $id, - 'platform' => 'darwin', - 'version' => '1.0.0', - ), - 'decode' => 'baidu_lyric', - ); - break; - case 'kuwo': - $api = array( - 'method' => 'GET', - 'url' => 'http://m.kuwo.cn/newh5/singles/songinfoandlrc', - 'body' => array( - 'musicId' => $id, - 'httpsStatus' => 1, - ), - 'decode' => 'kuwo_lyric', - ); - break; - } - - return $this->exec($api); - } - - public function pic($id, $size = 300) - { - switch ($this->server) { - case 'netease': - $url = 'https://p3.music.126.net/'.$this->netease_encryptId($id).'/'.$id.'.jpg?param='.$size.'y'.$size; - break; - case 'tencent': - $url = 'https://y.gtimg.cn/music/photo_new/T002R'.$size.'x'.$size.'M000'.$id.'.jpg?max_age=2592000'; - break; - case 'xiami': - $format = $this->format; - $data = $this->format(false)->song($id); - $this->format = $format; - $data = json_decode($data, true); - $url = $data['data']['data']['songDetail']['albumLogo']; - $url = str_replace('http:', 'https:', $url).'@1e_1c_100Q_'.$size.'h_'.$size.'w'; - break; - case 'kugou': - $format = $this->format; - $data = $this->format(false)->song($id); - $this->format = $format; - $data = json_decode($data, true); - $url = $data['imgUrl']; - $url = str_replace('{size}', '400', $url); - break; - case 'baidu': - $format = $this->format; - $data = $this->format(false)->song($id); - $this->format = $format; - $data = json_decode($data, true); - $url = isset($data['songinfo']['pic_radio']) ? $data['songinfo']['pic_radio'] : $data['songinfo']['pic_small']; - break; - case 'kuwo': - $format = $this->format; - $data = $this->format(false)->song($id); - $this->format = $format; - $data = json_decode($data, true); - $url = isset($data['data']['pic']) ? $data['data']['pic'] : $data['data']['albumpic']; - break; - } - - return json_encode(array('url' => $url)); - } - - private function curlset() - { - switch ($this->server) { - case 'netease': - return array( - 'Referer' => 'https://music.163.com/', - 'Cookie' => 'appver=8.2.30; os=iPhone OS; osver=15.0; EVNSM=1.0.0; buildver=2206; channel=distribution; machineid=iPhone13.3', - 'User-Agent' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 CloudMusic/0.1.1 NeteaseMusic/8.2.30', - 'X-Real-IP' => long2ip(mt_rand(1884815360, 1884890111)), - 'Accept' => '*/*', - 'Accept-Language' => 'zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4', - 'Connection' => 'keep-alive', - 'Content-Type' => 'application/x-www-form-urlencoded', - ); - case 'tencent': - return array( - 'Referer' => 'http://y.qq.com', - 'Cookie' => 'pgv_pvi=22038528; pgv_si=s3156287488; pgv_pvid=5535248600; yplayer_open=1; ts_last=y.qq.com/portal/player.html; ts_uid=4847550686; yq_index=0; qqmusic_fromtag=66; player_exist=1', - 'User-Agent' => 'QQ%E9%9F%B3%E4%B9%90/54409 CFNetwork/901.1 Darwin/17.6.0 (x86_64)', - 'Accept' => '*/*', - 'Accept-Language' => 'zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4', - 'Connection' => 'keep-alive', - 'Content-Type' => 'application/x-www-form-urlencoded', - ); - case 'xiami': - return array( - 'Cookie' => '_m_h5_tk=15d3402511a022796d88b249f83fb968_1511163656929; _m_h5_tk_enc=b6b3e64d81dae577fc314b5c5692df3c', - 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) XIAMI-MUSIC/3.1.1 Chrome/56.0.2924.87 Electron/1.6.11 Safari/537.36', - 'Accept' => 'application/json', - 'Content-type' => 'application/x-www-form-urlencoded', - 'Accept-Language' => 'zh-CN', - ); - case 'kugou': - return array( - 'User-Agent' => 'IPhone-8990-searchSong', - 'UNI-UserAgent' => 'iOS11.4-Phone8990-1009-0-WiFi', - ); - case 'baidu': - return array( - 'Cookie' => 'BAIDUID='.$this->getRandomHex(32).':FG=1', - 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) baidu-music/1.2.1 Chrome/66.0.3359.181 Electron/3.0.5 Safari/537.36', - 'Accept' => '*/*', - 'Content-type' => 'application/json;charset=UTF-8', - 'Accept-Language' => 'zh-CN', - ); - case 'kuwo': - return array( - 'Cookie' => 'Hm_lvt_cdb524f42f0ce19b169a8071123a4797=1623339177,1623339183; _ga=GA1.2.1195980605.1579367081; Hm_lpvt_cdb524f42f0ce19b169a8071123a4797=1623339982; kw_token=3E7JFQ7MRPL; _gid=GA1.2.747985028.1623339179; _gat=1', - 'csrf' => '3E7JFQ7MRPL', - 'Host' => 'www.kuwo.cn', - 'Referer' => 'http://www.kuwo.cn/', - 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36', - ); - } - } - - private function getRandomHex($length) - { - if (function_exists('random_bytes')) { - return bin2hex(random_bytes($length / 2)); - } - if (function_exists('mcrypt_create_iv')) { - return bin2hex(mcrypt_create_iv($length / 2, MCRYPT_DEV_URANDOM)); - } - if (function_exists('openssl_random_pseudo_bytes')) { - return bin2hex(openssl_random_pseudo_bytes($length / 2)); - } - } - - private function bchexdec($hex) - { - $dec = 0; - $len = strlen($hex); - for ($i = 1; $i <= $len; $i++) { - $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i)))); - } - - return $dec; - } - - private function bcdechex($dec) - { - $hex = ''; - do { - $last = bcmod($dec, 16); - $hex = dechex($last).$hex; - $dec = bcdiv(bcsub($dec, $last), 16); - } while ($dec > 0); - - return $hex; - } - - private function str2hex($string) - { - $hex = ''; - for ($i = 0; $i < strlen($string); $i++) { - $ord = ord($string[$i]); - $hexCode = dechex($ord); - $hex .= substr('0'.$hexCode, -2); - } - - return $hex; - } - - private function netease_AESCBC($api) - { - $modulus = '157794750267131502212476817800345498121872783333389747424011531025366277535262539913701806290766479189477533597854989606803194253978660329941980786072432806427833685472618792592200595694346872951301770580765135349259590167490536138082469680638514416594216629258349130257685001248172188325316586707301643237607'; - $pubkey = '65537'; - $nonce = '0CoJUm6Qyw8W8jud'; - $vi = '0102030405060708'; - - if (extension_loaded('bcmath')) { - $skey = $this->getRandomHex(16); - } else { - $skey = 'B3v3kH4vRPWRJFfH'; - } - - $body = json_encode($api['body']); - - if (function_exists('openssl_encrypt')) { - $body = openssl_encrypt($body, 'aes-128-cbc', $nonce, false, $vi); - $body = openssl_encrypt($body, 'aes-128-cbc', $skey, false, $vi); - } else { - $pad = 16 - (strlen($body) % 16); - $body = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $nonce, $body.str_repeat(chr($pad), $pad), MCRYPT_MODE_CBC, $vi)); - $pad = 16 - (strlen($body) % 16); - $body = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $skey, $body.str_repeat(chr($pad), $pad), MCRYPT_MODE_CBC, $vi)); - } - - if (extension_loaded('bcmath')) { - $skey = strrev(utf8_encode($skey)); - $skey = $this->bchexdec($this->str2hex($skey)); - $skey = bcpowmod($skey, $pubkey, $modulus); - $skey = $this->bcdechex($skey); - $skey = str_pad($skey, 256, '0', STR_PAD_LEFT); - } else { - $skey = '85302b818aea19b68db899c25dac229412d9bba9b3fcfe4f714dc016bc1686fc446a08844b1f8327fd9cb623cc189be00c5a365ac835e93d4858ee66f43fdc59e32aaed3ef24f0675d70172ef688d376a4807228c55583fe5bac647d10ecef15220feef61477c28cae8406f6f9896ed329d6db9f88757e31848a6c2ce2f94308'; - } - - $api['url'] = str_replace('/api/', '/weapi/', $api['url']); - $api['body'] = array( - 'params' => $body, - 'encSecKey' => $skey, - ); - - return $api; - } - - private function baidu_AESCBC($api) - { - $key = 'DBEECF8C50FD160E'; - $vi = '1231021386755796'; - - $data = 'songid='.$api['body']['songid'].'&ts='.intval(microtime(true) * 1000); - - if (function_exists('openssl_encrypt')) { - $data = openssl_encrypt($data, 'aes-128-cbc', $key, false, $vi); - } else { - $pad = 16 - (strlen($data) % 16); - $data = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data.str_repeat(chr($pad), $pad), MCRYPT_MODE_CBC, $vi)); - } - - $api['body']['e'] = $data; - - return $api; - } - - private function xiami_sign($api) - { - $data = $this->curl('https://acs.m.xiami.com/h5/mtop.alimusic.recommend.songservice.getdailysongs/1.0/?appKey=12574478&t=1560663823000&dataType=json&data=%7B%22requestStr%22%3A%22%7B%5C%22header%5C%22%3A%7B%5C%22platformId%5C%22%3A%5C%22mac%5C%22%7D%2C%5C%22model%5C%22%3A%5B%5D%7D%22%7D&api=mtop.alimusic.recommend.songservice.getdailysongs&v=1.0&type=originaljson&sign=22ad1377ee193f3e2772c17c6192b17c', null, 1); - preg_match_all('/_m_h5[^;]+/', $data->raw, $match); - $this->header['Cookie'] = $match[0][0].'; '.$match[0][1]; - $data = json_encode(array( - 'requestStr' => json_encode(array( - 'header' => array( - 'platformId' => 'mac', - ), - 'model' => $api['body']['data'], - )), - )); - $appkey = '12574478'; - $cookie = $this->header['Cookie']; - preg_match('/_m_h5_tk=([^_]+)/', $cookie, $match); - $token = $match[1]; - $t = time() * 1000; - $sign = md5(sprintf('%s&%s&%s&%s', $token, $t, $appkey, $data)); - $api['body'] = array( - 'appKey' => $appkey, - 't' => $t, - 'dataType' => 'json', - 'data' => $data, - 'api' => $api['body']['r'], - 'v' => '1.0', - 'type' => 'originaljson', - 'sign' => $sign, - ); - - return $api; - } - - private function netease_encryptId($id) - { - $magic = str_split('3go8&$8*3*3h0k(2)2'); - $song_id = str_split($id); - for ($i = 0; $i < count($song_id); $i++) { - $song_id[$i] = chr(ord($song_id[$i]) ^ ord($magic[$i % count($magic)])); - } - $result = base64_encode(md5(implode('', $song_id), 1)); - $result = str_replace(array('/', '+'), array('_', '-'), $result); - - return $result; - } - - private function netease_url($result) - { - $data = json_decode($result, true); - if (isset($data['data'][0]['uf']['url'])) { - $data['data'][0]['url'] = $data['data'][0]['uf']['url']; - } - if (isset($data['data'][0]['url'])) { - $url = array( - 'url' => $data['data'][0]['url'], - 'size' => $data['data'][0]['size'], - 'br' => $data['data'][0]['br'] / 1000, - ); - } else { - $url = array( - 'url' => '', - 'size' => 0, - 'br' => -1, - ); - } - - return json_encode($url); - } - - private function tencent_url($result) - { - $data = json_decode($result, true); - $guid = mt_rand() % 10000000000; - - $type = array( - array('size_flac', 999, 'F000', 'flac'), - array('size_320mp3', 320, 'M800', 'mp3'), - array('size_192aac', 192, 'C600', 'm4a'), - array('size_128mp3', 128, 'M500', 'mp3'), - array('size_96aac', 96, 'C400', 'm4a'), - array('size_48aac', 48, 'C200', 'm4a'), - array('size_24aac', 24, 'C100', 'm4a'), - ); - - $uin = '0'; - preg_match('/uin=(\d+)/', $this->header['Cookie'], $uin_match); - if (count($uin_match)) { - $uin = $uin_match[1]; - } - - $payload = array( - 'req_0' => array( - 'module' => 'vkey.GetVkeyServer', - 'method' => 'CgiGetVkey', - 'param' => array( - 'guid' => (string) $guid, - 'songmid' => array(), - 'filename' => array(), - 'songtype' => array(), - 'uin' => $uin, - 'loginflag' => 1, - 'platform' => '20', - ), - ), - ); - - foreach ($type as $vo) { - $payload['req_0']['param']['songmid'][] = $data['data'][0]['mid']; - $payload['req_0']['param']['filename'][] = $vo[2].$data['data'][0]['file']['media_mid'].'.'.$vo[3]; - $payload['req_0']['param']['songtype'][] = $data['data'][0]['type']; - } - - $api = array( - 'method' => 'GET', - 'url' => 'https://u.y.qq.com/cgi-bin/musicu.fcg', - 'body' => array( - 'format' => 'json', - 'platform' => 'yqq.json', - 'needNewCode' => 0, - 'data' => json_encode($payload), - ), - ); - $response = json_decode($this->exec($api), true); - $vkeys = $response['req_0']['data']['midurlinfo']; - - foreach ($type as $index => $vo) { - if ($data['data'][0]['file'][$vo[0]] && $vo[1] <= $this->temp['br']) { - if (!empty($vkeys[$index]['vkey'])) { - $url = array( - 'url' => $response['req_0']['data']['sip'][0].$vkeys[$index]['purl'], - 'size' => $data['data'][0]['file'][$vo[0]], - 'br' => $vo[1], - ); - break; - } - } - } - if (!isset($url['url'])) { - $url = array( - 'url' => '', - 'size' => 0, - 'br' => -1, - ); - } - - return json_encode($url); - } - - private function xiami_url($result) - { - $data = json_decode($result, true); - - $type = array( - 's' => 740, - 'h' => 320, - 'l' => 128, - 'f' => 64, - 'e' => 32, - ); - $max = 0; - $url = array(); - foreach ($data['data']['data']['songs'][0]['listenFiles'] as $vo) { - if ($type[$vo['quality']] <= $this->temp['br'] && $type[$vo['quality']] > $max) { - $max = $type[$vo['quality']]; - $url = array( - 'url' => $vo['listenFile'], - 'size' => $vo['fileSize'], - 'br' => $type[$vo['quality']], - ); - } - } - if (!isset($url['url'])) { - $url = array( - 'url' => '', - 'size' => 0, - 'br' => -1, - ); - } - - return json_encode($url); - } - - private function kugou_url($result) - { - $data = json_decode($result, true); - - $max = 0; - $url = array(); - foreach ($data['data'][0]['relate_goods'] as $vo) { - if ($vo['info']['bitrate'] <= $this->temp['br'] && $vo['info']['bitrate'] > $max) { - $api = array( - 'method' => 'GET', - 'url' => 'http://trackercdn.kugou.com/i/v2/', - 'body' => array( - 'hash' => $vo['hash'], - 'key' => md5($vo['hash'].'kgcloudv2'), - 'pid' => 3, - 'behavior' => 'play', - 'cmd' => '25', - 'version' => 8990, - ), - ); - $t = json_decode($this->exec($api), true); - if (isset($t['url'])) { - $max = $t['bitRate'] / 1000; - $url = array( - 'url' => reset($t['url']), - 'size' => $t['fileSize'], - 'br' => $t['bitRate'] / 1000, - ); - } - } - } - if (!isset($url['url'])) { - $url = array( - 'url' => '', - 'size' => 0, - 'br' => -1, - ); - } - - return json_encode($url); - } - - private function baidu_url($result) - { - $data = json_decode($result, true); - - $max = 0; - $url = array(); - foreach ($data['songurl']['url'] as $vo) { - if ($vo['file_bitrate'] <= $this->temp['br'] && $vo['file_bitrate'] > $max) { - $url = array( - 'url' => $vo['file_link'], - 'br' => $vo['file_bitrate'], - ); - } - } - if (!isset($url['url'])) { - $url = array( - 'url' => '', - 'br' => -1, - ); - } - - return json_encode($url); - } - - private function kuwo_url($result) - { - $result = json_decode($result, true); - - $url = array(); - if ($result['code'] == 200 && isset($result['data']['url'])) { - $url = array( - 'url' => $result['data']['url'], - 'br' => 128, - ); - } else { - $url = array( - 'url' => '', - 'br' => -1, - ); - } - - return json_encode($url); - } - - private function netease_lyric($result) - { - $result = json_decode($result, true); - $data = array( - 'lyric' => isset($result['lrc']['lyric']) ? $result['lrc']['lyric'] : '', - 'tlyric' => isset($result['tlyric']['lyric']) ? $result['tlyric']['lyric'] : '', - ); - - return json_encode($data, JSON_UNESCAPED_UNICODE); - } - - private function tencent_lyric($result) - { - $result = substr($result, 18, -1); - $result = json_decode($result, true); - $data = array( - 'lyric' => isset($result['lyric']) ? base64_decode($result['lyric']) : '', - 'tlyric' => isset($result['trans']) ? base64_decode($result['trans']) : '', - ); - - return json_encode($data, JSON_UNESCAPED_UNICODE); - } - - private function xiami_lyric($result) - { - $result = json_decode($result, true); - - if (count($result['data']['data']['lyrics'])) { - $data = $result['data']['data']['lyrics'][0]['content']; - $data = preg_replace('/<[^>]+>/', '', $data); - preg_match_all('/\[([\d:\.]+)\](.*)\s\[x-trans\](.*)/i', $data, $match); - if (count($match[0])) { - for ($i = 0; $i < count($match[0]); $i++) { - $A[] = '['.$match[1][$i].']'.$match[2][$i]; - $B[] = '['.$match[1][$i].']'.$match[3][$i]; - } - $arr = array( - 'lyric' => str_replace($match[0], $A, $data), - 'tlyric' => str_replace($match[0], $B, $data), - ); - } else { - $arr = array( - 'lyric' => $data, - 'tlyric' => '', - ); - } - } else { - $arr = array( - 'lyric' => '', - 'tlyric' => '', - ); - } - - return json_encode($arr, JSON_UNESCAPED_UNICODE); - } - - private function kugou_lyric($result) - { - $result = json_decode($result, true); - $api = array( - 'method' => 'GET', - 'url' => 'http://lyrics.kugou.com/download', - 'body' => array( - 'charset' => 'utf8', - 'accesskey' => $result['candidates'][0]['accesskey'], - 'id' => $result['candidates'][0]['id'], - 'client' => 'mobi', - 'fmt' => 'lrc', - 'ver' => 1, - ), - ); - $data = json_decode($this->exec($api), true); - $arr = array( - 'lyric' => base64_decode($data['content']), - 'tlyric' => '', - ); - - return json_encode($arr, JSON_UNESCAPED_UNICODE); - } - - private function baidu_lyric($result) - { - $result = json_decode($result, true); - $data = array( - 'lyric' => isset($result['lrcContent']) ? $result['lrcContent'] : '', - 'tlyric' => '', - ); - - return json_encode($data, JSON_UNESCAPED_UNICODE); - } - - private function kuwo_lyric($result) - { - $result = json_decode($result, true); - if (count($result['data']['lrclist'])) { - $kuwolrc = ''; - for ($i = 0; $i < count($result['data']['lrclist']); $i++) { - $otime = $result['data']['lrclist'][$i]['time']; - $osec = explode('.', $otime)[0]; - $min = str_pad(floor($osec / 60), 2, "0", STR_PAD_LEFT); - $sec = str_pad($osec - $min * 60, 2, "0", STR_PAD_LEFT); - $msec = explode('.', $otime)[1]; - $olyric = $result['data']['lrclist'][$i]['lineLyric']; - $kuwolrc = $kuwolrc . '[' . $min . ':' . $sec . '.' . $msec . ']' . $olyric . "\n"; - } - $arr = array( - 'lyric' => $kuwolrc, - 'tlyric' => '', - ); - } else { - $arr = array( - 'lyric' => '', - 'tlyric' => '', - ); - } - return json_encode($arr, JSON_UNESCAPED_UNICODE); - } - - protected function format_netease($data) - { - $result = array( - 'id' => $data['id'], - 'name' => $data['name'], - 'artist' => array(), - 'album' => $data['al']['name'], - 'pic_id' => isset($data['al']['pic_str']) ? $data['al']['pic_str'] : $data['al']['pic'], - 'url_id' => $data['id'], - 'lyric_id' => $data['id'], - 'source' => 'netease', - ); - if (isset($data['al']['picUrl'])) { - preg_match('/\/(\d+)\./', $data['al']['picUrl'], $match); - $result['pic_id'] = $match[1]; - } - foreach ($data['ar'] as $vo) { - $result['artist'][] = $vo['name']; - } - - return $result; - } - - protected function format_tencent($data) - { - if (isset($data['musicData'])) { - $data = $data['musicData']; - } - $result = array( - 'id' => $data['mid'], - 'name' => $data['name'], - 'artist' => array(), - 'album' => trim($data['album']['title']), - 'pic_id' => $data['album']['mid'], - 'url_id' => $data['mid'], - 'lyric_id' => $data['mid'], - 'source' => 'tencent', - ); - foreach ($data['singer'] as $vo) { - $result['artist'][] = $vo['name']; - } - - return $result; - } - - protected function format_xiami($data) - { - $result = array( - 'id' => $data['songId'], - 'name' => $data['songName'], - 'artist' => array(), - 'album' => $data['albumName'], - 'pic_id' => $data['songId'], - 'url_id' => $data['songId'], - 'lyric_id' => $data['songId'], - 'source' => 'xiami', - ); - foreach ($data['singerVOs'] as $vo) { - $result['artist'][] = $vo['artistName']; - } - - return $result; - } - - protected function format_kugou($data) - { - $result = array( - 'id' => $data['hash'], - 'name' => isset($data['filename']) ? $data['filename'] : $data['fileName'], - 'artist' => array(), - 'album' => isset($data['album_name']) ? $data['album_name'] : '', - 'url_id' => $data['hash'], - 'pic_id' => $data['hash'], - 'lyric_id' => $data['hash'], - 'source' => 'kugou', - ); - list($result['artist'], $result['name']) = explode(' - ', $result['name'], 2); - $result['artist'] = explode('、', $result['artist']); - - return $result; - } - - protected function format_baidu($data) - { - $result = array( - 'id' => $data['song_id'], - 'name' => $data['title'], - 'artist' => explode(',', $data['author']), - 'album' => $data['album_title'], - 'pic_id' => $data['song_id'], - 'url_id' => $data['song_id'], - 'lyric_id' => $data['song_id'], - 'source' => 'baidu', - ); - - return $result; - } - - protected function format_kuwo($data) - { - $result = array( - 'id' => $data['rid'], - 'name' => $data['name'], - 'artist' => explode('&', $data['artist']), - 'album' => $data['album'], - 'pic_id' => $data['rid'], - 'url_id' => $data['rid'], - 'lyric_id' => $data['rid'], - 'source' => 'kuwo', - ); - - return $result; - } - -} diff --git a/src/meting.js b/src/meting.js new file mode 100644 index 0000000..2161054 --- /dev/null +++ b/src/meting.js @@ -0,0 +1,192 @@ +/** + * Meting music framework - Node.js version (重构版本) + * https://i-meto.com + * https://github.com/metowolf/Meting + * + * Copyright 2019, METO Sheel + * Released under the MIT license + */ + +import { URLSearchParams } from 'url'; +import ProviderFactory from './providers/index.js'; + +class Meting { + constructor(server = 'netease') { + this.VERSION = '__VERSION__'; // 在构建时由 rollup 替换为实际版本号 + this.raw = null; + this.info = null; + this.error = null; + this.status = null; + this.temp = {}; + + this.server = null; + this.provider = null; + this.isFormat = false; + this.header = {}; + + this.site(server); + } + + // 设置音乐平台 + site(server) { + if (!ProviderFactory.isSupported(server)) { + server = 'netease'; // 默认使用网易云音乐 + } + + this.server = server; + this.provider = ProviderFactory.create(server, this); + this.header = this.provider.getHeaders(); + + return this; + } + + // 设置 Cookie + cookie(cookie) { + this.header['Cookie'] = cookie; + return this; + } + + // 设置数据格式化 + format(format = true) { + this.isFormat = format; + return this; + } + + // 执行 API 请求的主方法 + async _exec(api) { + // 让 Provider 自己处理完整的请求流程 + return await this.provider.executeRequest(api, this); + } + + // HTTP 请求方法 - 使用 fetch API + async _curl(url, payload = null, headerOnly = false) { + const requestOptions = { + method: payload ? 'POST' : 'GET', + headers: { ...this.header } + }; + + // 处理请求体 + if (payload) { + if (typeof payload === 'object' && !Buffer.isBuffer(payload) && typeof payload !== 'string') { + payload = new URLSearchParams(payload).toString(); + requestOptions.headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } + requestOptions.body = payload; + } + + // 添加超时控制 + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 20000); + requestOptions.signal = controller.signal; + + let retries = 3; + const makeRequest = async () => { + try { + const response = await fetch(url, requestOptions); + + clearTimeout(timeoutId); + + // 存储响应信息 + this.info = { + statusCode: response.status, + headers: Object.fromEntries(response.headers.entries()) + }; + + // 获取响应数据 + const data = await response.text(); + this.raw = data; + this.error = null; + this.status = ''; + + return this; + } catch (err) { + clearTimeout(timeoutId); + + // 处理错误 + if (err.name === 'AbortError') { + this.error = 'TIMEOUT'; + this.status = 'Request timeout'; + } else { + this.error = err.code || err.name; + this.status = err.message; + } + + // 重试机制 + if (retries > 0) { + retries--; + await new Promise(resolve => setTimeout(resolve, 1000)); + return makeRequest(); + } else { + return this; + } + } + }; + + return await makeRequest(); + } + + + // ========== 公共 API 方法 ========== + + // 搜索功能 + async search(keyword, option = {}) { + const api = this.provider.search(keyword, option); + return await this._exec(api); + } + + // 获取歌曲详情 + async song(id) { + const api = this.provider.song(id); + return await this._exec(api); + } + + // 获取专辑信息 + async album(id) { + const api = this.provider.album(id); + return await this._exec(api); + } + + // 获取艺术家作品 + async artist(id, limit = 50) { + const api = this.provider.artist(id, limit); + return await this._exec(api); + } + + // 获取播放列表 + async playlist(id) { + const api = this.provider.playlist(id); + return await this._exec(api); + } + + // 获取音频播放链接 + async url(id, br = 320) { + this.temp.br = br; + const api = this.provider.url(id, br); + return await this._exec(api); + } + + // 获取歌词 + async lyric(id) { + const api = this.provider.lyric(id); + return await this._exec(api); + } + + // 获取封面图片 + async pic(id, size = 300) { + return await this.provider.pic(id, size); + } + + // ========== 静态方法 ========== + + // 获取支持的平台列表 + static getSupportedPlatforms() { + return ProviderFactory.getSupportedPlatforms(); + } + + // 检查平台是否支持 + static isSupported(platform) { + return ProviderFactory.isSupported(platform); + } +} + +export default Meting; diff --git a/src/providers/baidu.js b/src/providers/baidu.js new file mode 100644 index 0000000..5447132 --- /dev/null +++ b/src/providers/baidu.js @@ -0,0 +1,280 @@ +import crypto from 'crypto'; +import BaseProvider from './base.js'; + +/** + * 百度音乐平台提供者 + */ +export default class BaiduProvider extends BaseProvider { + constructor(meting) { + super(meting); + this.name = 'baidu'; + } + + /** + * 获取百度音乐的请求头配置 + */ + getHeaders() { + return { + 'Cookie': `BAIDUID=${this._getRandomHex(32)}:FG=1`, + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) baidu-music/1.2.1 Chrome/66.0.3359.181 Electron/3.0.5 Safari/537.36', + 'Accept': '*/*', + 'Content-Type': 'application/json;charset=UTF-8', + 'Accept-Language': 'zh-CN' + }; + } + + /** + * 搜索歌曲 + */ + search(keyword, option = {}) { + return { + method: 'GET', + url: 'http://musicapi.taihe.com/v1/restserver/ting', + body: { + from: 'qianqianmini', + method: 'baidu.ting.search.merge', + isNew: 1, + platform: 'darwin', + page_no: option.page || 1, + query: keyword, + version: '11.2.1', + page_size: option.limit || 30 + }, + format: 'result.song_info.song_list' + }; + } + + /** + * 获取歌曲详情 + */ + song(id) { + return { + method: 'GET', + url: 'http://musicapi.taihe.com/v1/restserver/ting', + body: { + from: 'qianqianmini', + method: 'baidu.ting.song.getInfos', + songid: id, + res: 1, + platform: 'darwin', + version: '1.0.0' + }, + encode: 'baidu_AESCBC', + format: 'songinfo' + }; + } + + /** + * 获取专辑信息 + */ + album(id) { + return { + method: 'GET', + url: 'http://musicapi.taihe.com/v1/restserver/ting', + body: { + from: 'qianqianmini', + method: 'baidu.ting.album.getAlbumInfo', + album_id: id, + platform: 'darwin', + version: '11.2.1' + }, + format: 'songlist' + }; + } + + /** + * 获取艺术家作品 + */ + artist(id, limit = 50) { + return { + method: 'GET', + url: 'http://musicapi.taihe.com/v1/restserver/ting', + body: { + from: 'qianqianmini', + method: 'baidu.ting.artist.getSongList', + artistid: id, + limits: limit, + platform: 'darwin', + offset: 0, + tinguid: 0, + version: '11.2.1' + }, + format: 'songlist' + }; + } + + /** + * 获取播放列表 + */ + playlist(id) { + return { + method: 'GET', + url: 'http://musicapi.taihe.com/v1/restserver/ting', + body: { + from: 'qianqianmini', + method: 'baidu.ting.diy.gedanInfo', + listid: id, + platform: 'darwin', + version: '11.2.1' + }, + format: 'content' + }; + } + + /** + * 获取音频播放链接 + */ + url(id, br = 320) { + return { + method: 'GET', + url: 'http://musicapi.taihe.com/v1/restserver/ting', + body: { + from: 'qianqianmini', + method: 'baidu.ting.song.getInfos', + songid: id, + res: 1, + platform: 'darwin', + version: '1.0.0' + }, + encode: 'baidu_AESCBC', + decode: 'baidu_url' + }; + } + + /** + * 获取歌词 + */ + lyric(id) { + return { + method: 'GET', + url: 'http://musicapi.taihe.com/v1/restserver/ting', + body: { + from: 'qianqianmini', + method: 'baidu.ting.song.lry', + songid: id, + platform: 'darwin', + version: '1.0.0' + }, + decode: 'baidu_lyric' + }; + } + + /** + * 获取封面图片 + */ + async pic(id, size = 300) { + const format = this.meting.isFormat; + const data = await this.meting.format(false).song(id); + this.meting.isFormat = format; + const songData = JSON.parse(data); + const url = songData.songinfo.pic_radio || songData.songinfo.pic_small; + return JSON.stringify({ url: url }); + } + + /** + * 格式化百度音乐数据 + */ + format(data) { + return { + id: data.song_id, + name: data.title, + artist: data.author ? data.author.split(',') : [], + album: data.album_title || '', + pic_id: data.song_id, + url_id: data.song_id, + lyric_id: data.song_id, + source: 'baidu' + }; + } + + /** + * 处理百度音乐的编码/解码逻辑 + */ + async handleEncode(api) { + if (api.encode === 'baidu_AESCBC') { + return this.aesEncrypt(api); + } + return api; + } + + async handleDecode(decodeType, data) { + if (decodeType === 'baidu_url') { + return this.urlDecode(data); + } else if (decodeType === 'baidu_lyric') { + return this.lyricDecode(data); + } + return data; + } + + /** + * 百度音乐 AES 加密 + */ + async aesEncrypt(api) { + const key = 'DBEECF8C50FD160E'; + const vi = '1231021386755796'; + + const data = `songid=${api.body.songid}&ts=${Date.now()}`; + + const cipher = crypto.createCipheriv('aes-128-cbc', key, vi); + cipher.setAutoPadding(true); + let encrypted = cipher.update(data, 'utf8', 'base64'); + encrypted += cipher.final('base64'); + + api.body.e = encrypted; + + return api; + } + + /** + * 百度音乐 URL 解码 + */ + urlDecode(result) { + const data = JSON.parse(result); + + let maxBr = 0; + let url; + + data.songurl.url.forEach(item => { + if (item.file_bitrate <= this.meting.temp.br && item.file_bitrate > maxBr) { + maxBr = item.file_bitrate; + url = { + url: item.file_link, + br: item.file_bitrate + }; + } + }); + + if (!url) { + url = { + url: '', + br: -1 + }; + } + + return JSON.stringify(url); + } + + /** + * 百度音乐歌词解码 + */ + lyricDecode(result) { + const data = JSON.parse(result); + const lyricData = { + lyric: data.lrcContent || '', + tlyric: '' + }; + + return JSON.stringify(lyricData); + } + + // ========== 私有工具方法 ========== + + /** + * 生成随机十六进制字符串 + */ + _getRandomHex(length) { + return crypto.randomBytes(Math.ceil(length / 2)) + .toString('hex') + .slice(0, length); + } +} \ No newline at end of file diff --git a/src/providers/base.js b/src/providers/base.js new file mode 100644 index 0000000..91b2776 --- /dev/null +++ b/src/providers/base.js @@ -0,0 +1,247 @@ +/** + * 音乐平台提供者基础类 + * 定义所有音乐平台提供者需要实现的接口 + */ +export default class BaseProvider { + constructor(meting) { + this.meting = meting; + this.name = 'base'; + } + + /** + * 获取平台的请求头配置 + * @returns {Object} 请求头对象 + */ + getHeaders() { + return {}; + } + + /** + * 搜索歌曲 + * @param {string} keyword 搜索关键词 + * @param {Object} [option={}] 搜索选项 + * @returns {Object} API 配置对象 + */ + search(keyword, option = {}) { + throw new Error(`${this.name} provider must implement search method`); + } + + /** + * 获取歌曲详情 + * @param {string} id 歌曲ID + * @returns {Object} API 配置对象 + */ + song(id) { + throw new Error(`${this.name} provider must implement song method`); + } + + /** + * 获取专辑信息 + * @param {string} id 专辑ID + * @returns {Object} API 配置对象 + */ + album(id) { + throw new Error(`${this.name} provider must implement album method`); + } + + /** + * 获取艺术家作品 + * @param {string} id 艺术家ID + * @param {number} limit 限制数量 + * @returns {Object} API 配置对象 + */ + artist(id, limit = 50) { + throw new Error(`${this.name} provider must implement artist method`); + } + + /** + * 获取播放列表 + * @param {string} id 播放列表ID + * @returns {Object} API 配置对象 + */ + playlist(id) { + throw new Error(`${this.name} provider must implement playlist method`); + } + + /** + * 获取音频播放链接 + * @param {string} id 歌曲ID + * @param {number} br 比特率 + * @returns {Object} API 配置对象 + */ + url(id, br = 320) { + throw new Error(`${this.name} provider must implement url method`); + } + + /** + * 获取歌词 + * @param {string} id 歌曲ID + * @returns {Object} API 配置对象 + */ + lyric(id) { + throw new Error(`${this.name} provider must implement lyric method`); + } + + /** + * 获取封面图片 + * @param {string} id 图片ID + * @param {number} size 图片尺寸 + * @returns {Promise