diff --git a/.gitignore b/.gitignore
index 79f9b0e..e181355 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,4 @@ node_modules
.idea/
# package-lock.json
-.vscode
+# .vscode
diff --git a/.prettierrc b/.prettierrc
index 567d262..1b3eba7 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,5 +1,5 @@
{
- "printWidth": 120,
+ "printWidth": 100,
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..69ca106
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,15 @@
+{
+ // 使用 IntelliSense 了解相关属性。
+ // 悬停以查看现有属性的描述。
+ // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "pwa-chrome",
+ "request": "launch",
+ "name": "Launch Chrome against localhost",
+ "url": "http://localhost:3200",
+ "webRoot": "${workspaceFolder}"
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..f42b420
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,13 @@
+{
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": true
+ },
+ "prettier.requireConfig": true,
+ "editor.formatOnSave": true,
+ "[javascript]": {
+ "editor.formatOnSave": true,
+ "editor.formatOnPaste": false
+ },
+ "editor.detectIndentation": false,
+ "editor.tabSize": 2
+}
diff --git a/README.md b/README.md
index d1340c9..6852480 100644
--- a/README.md
+++ b/README.md
@@ -12,11 +12,11 @@
-> QQ音乐API koa2 版本, 通过Web网页版请求QQ音乐接口数据, 有问题请提 [issue](https://github.com/Rain120/qq-music-api/issues)
+> QQ 音乐 API koa2 版本, 通过 Web 网页版请求 QQ 音乐接口数据, 有问题请提 [issue](https://github.com/Rain120/qq-music-api/issues)
> 当前代码仅共学习,不可做商业用途
-### API结构图
+### API 结构图
> 目前暂时没有时间做登录模块的接口,欢迎各位大佬给我`PR`, 阿里嘎多
@@ -24,11 +24,12 @@
### 环境要求
-> 因为本项目采用的是`koa2`, 所以请确保你的`node`版本是7.6.0+
+> 因为本项目采用的是`koa2`, 所以请确保你的`node`版本是 7.6.0+
```
node -v
```
+
### 📦 安装
```
@@ -36,7 +37,8 @@ git@github.com:Rain120/qq-music-api.git
npm install
```
-### 🔨项目启动
+### 🔨 项目启动
+
```
// npm i -g nodemon
npm run start
@@ -44,6 +46,7 @@ npm run start
// or don't install nodemon
node app.js
```
+
项目监听端口是`3200`
### 🐳 Docker
@@ -65,7 +68,11 @@ npm run run:images
docker pull qq-music-api
```
-### 功能特性
+### 功能特性 & 日志更新
+
+- [ ] 大改版, 迭代 `v2.0.0`
+
+ - [ ] `API` 设计优化
- [x] 获取歌曲播放链接 **2021-01-24**
@@ -75,7 +82,7 @@ docker pull qq-music-api
- [x] 获取歌手热门歌曲 **2020-07-04**
-- [x] 获取QQ音乐产品的下载地址
+- [x] 获取 QQ 音乐产品的下载地址
- [x] 获取歌单分类
@@ -83,11 +90,11 @@ docker pull qq-music-api
- [x] 获取歌单详情
-- [x] 获取MV标签
+- [x] 获取 MV 标签
-- [x] 获取MV播放信息
+- [x] 获取 MV 播放信息
-- [x] 获取歌手MV
+- [x] 获取歌手 MV
- [x] 获取相似歌手
@@ -103,13 +110,13 @@ docker pull qq-music-api
- [x] 获取歌曲歌词
-- [x] 获取MV
+- [x] 获取 MV
- [x] 获取新碟信息
- [x] 获取歌手专辑
-- [x] ~~获取歌曲VKey~~ **2021-01-24**
+- [x] ~~获取歌曲 VKey~~ **2021-01-24**
- [x] 获取搜索热词
@@ -123,7 +130,7 @@ docker pull qq-music-api
- [x] 获取排行榜单详情
-- [x] 获取评论信息(cmd代表的意思没太弄明白)
+- [x] 获取评论信息(cmd 代表的意思没太弄明白)
- [x] 获取票务信息
@@ -141,7 +148,7 @@ docker pull qq-music-api
[Binaryify/NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)
-[Vue2.0开发企业级移动端音乐Web App](https://coding.imooc.com/class/107.html)
+[Vue2.0 开发企业级移动端音乐 Web App](https://coding.imooc.com/class/107.html)
**参考内容**
@@ -159,7 +166,7 @@ docker pull qq-music-api
#### 🤝 贡献 ![PR](https://img.shields.io/badge/PRs-Welcome-orange?style=flat-square&logo=appveyor)
-We welcome all contributions. You can submit any ideas as [pull requests](https://github.com/Rain120/qq-music-api/pulls) or as a GitHub [issue](https://github.com/Rain120/qq-music-api/issues).
+We welcome all contributions. You can submit any ideas as [pull requests](https://github.com/Rain120/qq-music-api/pulls) or as a GitHub [issue](https://github.com/Rain120/qq-music-api/issues).
#### 👨🏭 作者
diff --git a/app.js b/app.js
index 65f629c..fc59920 100644
--- a/app.js
+++ b/app.js
@@ -1,4 +1,4 @@
-require("@babel/register");
+require('@babel/register');
const Koa = require('koa');
const app = new Koa();
const bodyParser = require('koa-bodyparser');
@@ -9,35 +9,75 @@ const chalk = require('chalk');
const cors = require('./middlewares/koa-cors');
const router = require('./routers/router');
+const newRouter = require('./routers/new-router');
const cookie = require('./util/cookie');
require('./util/colors');
-const userInfo = require('./config/user-info')
-global = Object.assign({}, userInfo);
+const debug = require('./config/debugger');
+const userInfo = require('./config/user-info');
+global = Object.assign({}, debug, userInfo);
-console.log(chalk.green('\n🥳🎉 We had supported config the user cookies. \n'));
+console.log(
+ chalk.yellow(
+ '\n💪🙏 CN: 我们在新版本为了语义化修改了路由,当然了,我们也是保留了旧版本的路由,如果你是新项目,推荐使用新路由。 \n',
+ ),
+);
+console.log(chalk.yellow('\n💪🙏 CN: 新路由位置在new-router。\n'));
+console.log(
+ chalk.yellow(
+ '\n🥳🎉 EN: We have modified the routing in the new version for semantic purposes. Of course, we also keep the old version of the routing. If you are a new project, it is recommended to use the new routing.\n',
+ ),
+);
+console.log(chalk.yellow('\n💪🙏 EN: The new version at new-router. \n'));
+console.log(chalk.green('\n💪🙏 CN: 我们支持 Cookies 配置了 \n'));
+console.log(chalk.green('\n🥳🎉 EN: We had supported config the user cookies. \n'));
if (!(global.loginUin || global.uin)) {
- console.log(chalk.yellow(`😔 The configuration ${chalk.red('loginUin')} or your ${chalk.red('cookie')} in file ${chalk.green('config/user-info')} has not configured. \n`));
+ console.log(
+ chalk.yellow(
+ `😔 CN: ${chalk.green('config/user-info')} 文件中 ${chalk.red('loginUin')} 或者 ${chalk.red(
+ 'cookie',
+ )}尚未配置。\n`,
+ ),
+ );
+ console.log(
+ chalk.yellow(
+ `😔 EN: The configuration ${chalk.red('loginUin')} or your ${chalk.red(
+ 'cookie',
+ )} in file ${chalk.green('config/user-info')} has not configured. \n`,
+ ),
+ );
}
if (!global.cookie) {
- console.log(chalk.yellow(`😔 The configuration ${chalk.red('cookie')} in file ${chalk.green('config/user-info')} has not configured. \n`));
+ console.log(
+ chalk.yellow(
+ `😔 CN: ${chalk.green('config/user-info')} 文件中 ${chalk.red('cookie')} 尚未配置。\n`,
+ ),
+ );
+ console.log(
+ chalk.yellow(
+ `😔 EN: The configuration ${chalk.red('cookie')} in file ${chalk.green(
+ 'config/user-info',
+ )} has not configured. \n`,
+ ),
+ );
}
exec('npm info QQ-Music-API version', (err, stdout, stderr) => {
- if(!err){
- let version = stdout.trim()
- if(package.version < version){
- console.log(`Current Version: ${version}, Current Version: ${package.version}, Please update it.`.prompt);
+ if (!err) {
+ let version = stdout.trim();
+ if (package.version < version) {
+ console.log(
+ `Current Version: ${version}, Current Version: ${package.version}, Please update it.`
+ .prompt,
+ );
}
}
});
app.use(bodyParser());
app.use(cookie());
-app.use(static(
- path.join(__dirname, 'public')
-));
+app.use(static(path.join(__dirname, 'public')));
// logger
app.use(async (ctx, next) => {
@@ -47,14 +87,16 @@ app.use(async (ctx, next) => {
});
// cors
-app.use(cors({
- origin: (ctx) => ctx.request.header.origin,
- exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
- maxAge: 5,
- credentials: true,
- allowMethods: ['GET', 'POST', 'DELETE'],
- allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
-}));
+app.use(
+ cors({
+ origin: ctx => ctx.request.header.origin,
+ exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
+ maxAge: 5,
+ credentials: true,
+ allowMethods: ['GET', 'POST', 'DELETE'],
+ allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
+ }),
+);
// x-response-time
app.use(async (ctx, next) => {
@@ -64,11 +106,13 @@ app.use(async (ctx, next) => {
ctx.set('X-Response-Time', `${ms}ms`);
});
-app.use(router.routes())
- .use(router.allowedMethods());
+// TODO: v2.0.0 move it
+app.use(router.routes()).use(router.allowedMethods());
+
+app.use(newRouter.routes()).use(newRouter.allowedMethods());
const PORT = process.env.PORT || 3200;
app.listen(PORT, () => {
- console.log(`server running @ http://localhost:${PORT}`.prompt)
+ console.log(`server running @ http://localhost:${PORT}`.prompt);
});
diff --git a/config/debugger.js b/config/debugger.js
new file mode 100644
index 0000000..e8a9c41
--- /dev/null
+++ b/config/debugger.js
@@ -0,0 +1,10 @@
+/*
+ * @Author: Rainy [https://github.com/rain120]
+ * @Date: 2021-01-30 14:55:24
+ * @LastEditors: Rainy
+ * @LastEditTime: 2021-01-30 15:03:05
+ */
+
+module.exports = {
+ log: true,
+}
diff --git a/docs/README.md b/docs/README.md
index ac07013..4b5edc3 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,32 +1,44 @@
QQ Music API
-!> QQ音乐API koa2 版本, 通过Web网页版请求QQ音乐接口数据, 有问题请提 [issue](https://github.com/Rain120/qq-music-api/issues), 或者你有其他想法欢迎`PR`.
+!> QQ 音乐 API koa2 版本, 通过 Web 网页版请求 QQ 音乐接口数据, 有问题请提 [issue](https://github.com/Rain120/qq-music-api/issues), 或者你有其他想法欢迎`PR`.
-## API结构图
+## API 结构图
![qq-music](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/qq-music.png)
-## API接口
+## API 接口
!> koa2 接口说明(参数, 地址, 效果图)
## 新特性
-### 支持自定义设置cookie
+!> `v2.0.0`大版本修改是为了修复之前 **接口设计** 的 **可读性差** , **API 不合理** 等众多问题。
+
+!> 我们在旧版本的基础上, 针对性修改。所以对 **接口参数**, **接口返回**, **路由** 等方面做了一定的 **优化**, 我们推荐新用户使用当前时间最新版 `v2.0.0`。
!> 2020-01-23 新增, 只需要配置 `config/user-info.js` 中的 `cookies` 字段就会在发送请求时带上你的 `cookies`。
#### 格式化自定义的 cookie
-![normalize-cookie.png](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/normalize-cookie.png)
+!> 对于先前接入的用户, 我们也做了一些兼容性的接口处理, 主要是 **路由的变化 mapping**, 具体可以在 `routers/map-router.js` 中体现, 感谢你的关注和使用。
### 特性提示支持
![new-feature-error-tips.png](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/new-feature-error-tips.png)
+## 用户个人信息
+
+### 支持自定义设置 cookie
+
+!> 2020-01-23 新增, 只需要配置 `config/user-info.js` 中的 `cookies` 字段就会在发送请求是带上你的 `cookies`。
+
+#### 格式化自定义的 cookie
+
+![normalize-cookie.png](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/normalize-cookie.png)
+
### 获取 Cookie
-!> 目前只支持用户自己从 *QQ音乐* 获取获取 `Cookie`, 如何获取 `Cookie`, 登录 `QQ音乐`, 然后 `F12`, 找到 `Network`, 随便找一个获取数据的接口, 复制接口中 `request headers` 中的 `Cookie` 即可。
+!> 目前只支持用户自己从 _QQ 音乐_ 获取获取 `Cookie`, 如何获取 `Cookie`, 登录 `QQ音乐`, 然后 `F12`, 找到 `Network`, 随便找一个获取数据的接口, 复制接口中 `request headers` 中的 `Cookie` 即可。
接口说明: 调用此接口, 可获取自己在 `config/user-info.js` 配置的 `Cookie` 被格式化的结果
@@ -47,17 +59,99 @@ const userInfo = {
![normalize-cookie.png](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/normalize-cookie.png)
-### 获取QQ音乐产品的下载地址
+## 推荐
-接口说明: 调用此接口, 可获取QQ音乐标准产品下载链接
+### 获取首页推荐
-接口地址: `/downloadQQMusic`
+接口说明: 调用此接口, 可获取首页推荐
-调用例子: `/downloadQQMusic`
+接口地址: `/getRecommend`
+
+调用例子: `/getRecommend`
示例截图:
-![获取QQ音乐产品的下载地址](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/downloadQQMusic.png)
+![获取首页推荐](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getRecommend.png)
+
+## 搜索
+
+### 获取搜索热词
+
+接口说明: 调用此接口, 可获取搜索热词
+
+接口地址: `/getHotkey`
+
+调用例子: `/getHotkey`
+
+示例截图:
+
+![获取搜索热词](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/gethotkey.png)
+
+### 获取关键字搜索提示
+
+接口说明: 调用此接口, 可获取获取关键字搜索提示
+
+参数列表:
+
+- 必选参数
+
+ - `key`: 搜索关键字
+
+接口地址: `/getSmartbox`
+
+调用例子: `/getSmartbox?key=周杰伦`
+
+示例截图:
+
+![获取获取关键字搜索提示](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSmartbox.png)
+
+### 获取搜索结果
+
+接口说明: 调用此接口, 可获取获取搜索结果
+
+参数列表(部分参数待注释):
+
+- 必选参数
+
+ - `key`: 搜索关键字
+
+ - ~~`catZhida`: 默认值为 1~~
+
+ ~~1. `0` 表示歌曲~~
+
+ ~~2. `2` 表示歌手~~
+
+ ~~3. `3` 表示专辑~~
+
+ - `remoteplace`: 默认值为 `song`
+
+ 1. 单曲: `song`
+
+ 2. 专辑: `album`
+
+ 3. MV: `mv`
+
+ 4. 歌单: `playlist`
+
+ 5. 用户: `user`
+
+ 6. 歌词: `lyric`
+
+- 可选参数
+
+ - `page`: 当前页数, 默认为 `1`
+
+ - `limit`: 取出歌单数量, 默认为 `10`
+
+接口地址: `/getSearchByKey`
+
+调用例子: `/getSearchByKey?key=周杰伦`
+
+示例截图:
+
+![获取获取搜索结果](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSearchByKey.png)
+
+## 歌单
### 获取歌单分类
@@ -67,7 +161,6 @@ const userInfo = {
调用例子: `/getSongListCategories`
-
SortID
@@ -76,6 +169,7 @@ const userInfo = {
sortId: 3, sortName: 最热
sortId: 4, sortName: 评分
sortId: 5, sortName: none
+
**歌单分类(categoryId & categoryName)**
@@ -86,6 +180,7 @@ const userInfo = {
1.1
"categoryId": 10000000,
"categoryName": 全部,
+
@@ -168,6 +263,7 @@ const userInfo = {
3.16
"categoryId": 220,
"categoryName": "世界音乐",
+
@@ -221,6 +317,7 @@ const userInfo = {
4.16
"categoryId": 14,
"categoryName": "DJ",
+
@@ -253,6 +350,7 @@ const userInfo = {
5.9
"categoryId": 68,
"categoryName": "思念",
+
@@ -297,6 +395,7 @@ const userInfo = {
6.13
"categoryId": 16,
"categoryName": "校园",
+
@@ -312,15 +411,15 @@ const userInfo = {
- 必选参数
- - `categoryId`: 类别`id`, 详见 `/getSongListCategories`
+ - `categoryId`: 类别`id`, 详见 `/getSongListCategories`
- 可选参数
- - `page`: 当前页数, 默认为1
+ - `page`: 当前页数, 默认为 1
- - `limit`: 取出歌单数量, 默认为 20
+ - `limit`: 取出歌单数量, 默认为 20
- - `sortId`: 最新, 最热,评分, 默认为5
+ - `sortId`: 最新, 最热,评分, 默认为 5
接口地址: `/getSongLists`
@@ -340,15 +439,15 @@ const userInfo = {
- 必选参数
- - `categoryIds`: 类别`id`列表, 详见 `/getSongListCategories`
+ - `categoryIds`: 类别`id`列表, 详见 `/getSongListCategories`
- 可选参数
- - `page`: 当前页数, 默认为1
+ - `page`: 当前页数, 默认为 1
- - `limit`: 取出歌单数量, 默认为 20
+ - `limit`: 取出歌单数量, 默认为 20
- - `sortId`: 最新, 最热,评分, 默认为5
+ - `sortId`: 最新, 最热,评分, 默认为 5
接口地址: `/batchGetSongLists`
@@ -381,7 +480,7 @@ const userInfo = {
- 必选参数
- - `disstid`: 歌单`id`
+ - `disstid`: 歌单`id`
接口地址: `/getSongListDetail`
@@ -391,67 +490,7 @@ const userInfo = {
![获取歌单详情](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSongListDetail.png)
-### 获取MV标签
-
-接口说明: 调用此接口, 可获取MV标签
-
-接口地址: `/getMvByTag`
-
-调用例子: `/getMvByTag`
-
-示例截图:
-
-![获取MV标签](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getMvByTag.png)
-
-### 获取MV播放信息
-
-接口说明: 调用此接口, 可获取MV播放信息
-
-参数列表:
-
-- 必选参数
-
- - `vid`: `video id`
-
-接口地址: `/getMvPlay`
-
-调用例子: `/getMvPlay?vid=u00222le4ox`
-
-示例截图:
-
-![获取MV播放信息](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getMvPlay.png)
-
-### 获取歌手MV
-
-接口说明: 调用此接口, 可获取歌手MV
-
-参数列表:
-
-- 必选参数
-
- - `singermid`: 歌手`id`
-
-- 可选参数
-
- - `order`: 当前MV类型, 默认为`time`
-
- - `listen`: 歌手专辑音乐MV
-
- - `time`: 粉丝上传MV视频
-
- - `limit`: 取出歌单数量, 默认为5
-
-接口地址: `/getSingerMV`
-
-调用例子: `/getSingerMV?singermid=0025NhlN2yWrP4&order=all&limit=5`
-
-示例截图:
-
-![获取歌手MV - default](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSingerMv-default.png)
-
-![获取歌手MV - belong](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSingerMv-listen.png)
-
-![获取歌手MV - fans](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSingerMv-time.png)
+## 歌手
### 获取歌手热门歌曲
@@ -461,13 +500,13 @@ const userInfo = {
- 必选参数
- - `singermid`: 歌手`id`
+ - `singermid`: 歌手`id`
- 可选参数
- - `page`: 页数, 默认为0
+ - `page`: 页数, 默认为 0
- - `limit`: 取出歌单数量, 默认为5
+ - `limit`: 取出歌单数量, 默认为 5
接口地址: `/getSingerHotsong`
@@ -485,7 +524,11 @@ const userInfo = {
- 必选参数
- - `singermid`: 歌手`id`
+ - `singermid`: 歌手`id`
+
+!> 接口地址: `/singer/similar`
+
+!> 调用例子: `/singer/similar?id=4558&singermid=0025NhlN2yWrP4`
接口地址: `/getSimilarSinger`
@@ -503,7 +546,11 @@ const userInfo = {
- 必选参数
- - `singermid`: 歌手`id`
+ - `singermid`: 歌手`id`
+
+!> New 调用例子: `/singer/desc`
+
+!> New 调用例子: `/singer/desc?singermid=0025NhlN2yWrP4`
接口地址: `/getSingerDesc`
@@ -546,46 +593,47 @@ const userInfo = {
- genre 默认是 -100
+ genre 默认是 -100
"id": -100,
"name": "全部"
-
+
"id": 1,
"name": "流行"
-
+
"id": 6,
"name": "嘻哈"
-
+
"id": 2,
"name": "摇滚"
-
+
"id": 4,
"name": "电子"
-
+
"id": 3,
"name": "民谣"
-
+
"id": 8,
"name": "R&B"
-
+
"id": 10,
"name": "民歌"
-
+
"id": 9,
"name": "轻音乐"
-
+
"id": 5,
"name": "爵士"
-
+
"id": 14,
"name": "古典"
-
+
"id": 25,
"name": "乡村"
-
+
"id": 20,
"name": "蓝调"
+
@@ -593,87 +641,88 @@ const userInfo = {
"id": -100,
"name": "热门"
-
+
"id": 1,
"name": "A"
-
+
"id": 2,
"name": "B"
-
+
"id": 3,
"name": "C"
-
+
"id": 4,
"name": "D"
-
+
"id": 5,
"name": "E"
-
+
"id": 6,
"name": "F"
-
+
"id": 7,
"name": "G"
-
+
"id": 8,
"name": "H"
-
+
"id": 9,
"name": "I"
-
+
"id": 10,
"name": "J"
-
+
"id": 11,
"name": "K"
-
+
"id": 12,
"name": "L"
-
+
"id": 13,
"name": "M"
-
+
"id": 14,
"name": "N"
-
+
"id": 15,
"name": "O"
-
+
"id": 16,
"name": "P"
-
+
"id": 17,
"name": "Q"
-
+
"id": 18,
"name": "R"
-
+
"id": 19,
"name": "S"
-
+
"id": 20,
"name": "T"
-
+
"id": 21,
"name": "U"
-
+
"id": 22,
"name": "V"
-
+
"id": 23,
"name": "W"
-
+
"id": 24,
"name": "X"
-
+
"id": 25,
"name": "Y"
-
+
"id": 26,
"name": "Z"
-
+
"id": 27,
"name": "#"
+
@@ -681,15 +730,16 @@ const userInfo = {
"id": -100,
"name": "全部"
-
+
"id": 0,
"name": "男"
-
+
"id": 1,
"name": "女"
-
+
"id": 2,
"name": "组合"
+
接口地址: `/getSingerList`
@@ -701,99 +751,234 @@ const userInfo = {
![获取歌手列表](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSingerList.png)
-### 获取歌手被关注数量信息
+## 歌曲
-接口说明: 调用此接口, 可获取歌手被关注数量信息
+### 获取歌曲相关信息
+
+接口说明: 调用此接口, 可获取歌曲相关信息
参数列表:
- 必选参数
- - `singermid`: 歌手`id`
-
-接口地址: `/getSingerStarNum`
-
-调用例子: `/getSingerStarNum?singermid=0025NhlN2yWrP4`
-
-示例截图:
-
-![获取歌手被关注数量信息](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSingerStarNum.png)
-
-### 获取电台列表
-
-接口说明: 调用此接口, 可获取电台列表, 分类
+ - `songmid`: 歌曲`id`
-接口地址: `/getRadioLists`
+接口地址: `/getSongInfo`
-调用例子: `/getRadioLists`
+调用例子: `/getSongInfo?songmid=0025NhlN2yWrP4`
示例截图:
-![获取电台列表](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getRadioLists.png)
+![获取歌曲相关信息](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSongInfo.png)
-### 获取专辑
+### 批量获取歌曲相关信息
-接口说明: 调用此接口, 可获取专辑信息(专辑列表、详情)
+接口说明: 调用此接口, 可批量获取歌曲相关信息
参数列表:
- 必选参数
- - `albummid`: 专辑`id`
-
-接口地址: `/getAlbumInfo`
-
-调用例子: `/getAlbumInfo?albummid=0016l2F430zMux`
-
-示例截图:
-
-![获取专辑](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getAlbumInfo.png)
+```
+songs: [
+ [songmid, songid]
+]
+```
-### 获取数字专辑
+其中 `songid`可以不传
-接口说明: 调用此接口, 可获取数字专辑, 轮播图`banner`, 专辑列表等信息, 详见[API结构图](#API结构图)
+接口地址: `/batchGetSongInfo`
-接口地址: `/getDigitalAlbumLists`
+调用例子: `/batchGetSongInfo`
-调用例子: `/getDigitalAlbumLists`
+```body
+{
+ "songs": [
+ ["001CLC7W2Gpz4J"],
+ ["0025NhlN2yWrP4"]
+ ]
+}
+```
示例截图:
-![获取数字专辑](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getDigitalAlbumLists.png)
+![获取歌曲相关信息](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/batchGetSongInfo.png)
-### 获取歌曲歌词
+### 获取歌曲播放链接
-接口说明: 调用此接口, 可获取歌曲歌词
+接口说明: 调用此接口, 可获取歌曲播放链接
参数列表:
- 必选参数
- - `songmid`: 专辑`id`
+- `songmid`: 歌曲`id`, 多个播放链接使用 `,`分隔
-- 可选参数
+- `justPlayUrl`: 仅返回播放链接, 默认是 `play`。`[all | play]`
- - `isFormat`: 是否格式化歌词, 默认值为 `false`
+- `quality`: 播放品质, 默认是 128。`[m4a | 128 | 320 | ape | flac]`
-接口地址: `/getLyric`
+接口地址: `/getMusicPlay`
-调用例子: `/getLyric?songmid=003rJSwm3TechU`
+调用例子:
示例截图:
-![获取歌曲歌词 - 未格式化](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getLyric.png)
+#### 获取单个播放链接
+
+例子: `/songmid=0025NhlN2yWrP4`
+
+![获取单个播放链接](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getMusicPlay.png)
+
+#### 获取多个播放链接
+
+例子: `/songmid=001yNIo41SJjuC,001wPuVc4ZiMhj&resType=play`
+
+![获取多个歌曲播放链接](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/just-get-play-url.png)
+
+#### 获取多个播放链接
+
+例子: `/songmid=001yNIo41SJjuC,001wPuVc4ZiMhj&resType=all`
+
+![获取接口所有数据](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/get-play-all-data.png)
+
+#### 歌曲品质
+
+例子: `songmid=001yNIo41SJjuC&resType=play&quality=m4a`
+
+![song-quality-128.png](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/song-quality-128.png)
+
+![song-quality-m4a.png](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/song-quality-m4a.png)
+
+### 获取歌曲 + 专辑 图片
+
+接口说明: 调用此接口, 可获取票务信息
+
+- 必选参数
+
+ - `id`: 专辑或者歌单请求结果的`id`
+
+- 可选参数
+
+ - `size`: 图片大小, 默认 `300x300`
+
+ - `maxAge`: 图片过期时间, 默认 `12 mins = 2592000ms`
+
+接口地址: `/getImageUrl`
+
+调用例子: `/getImageUrl?id=000MkMni19ClKG` or `/getImageUrl?id=000MkMni19ClKG&size=500x500`
+
+示例截图:
+
+![获取歌曲id](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/get-song-id.png)
+
+![获取歌曲图片地址](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/get-song-image.png)
+
+![歌曲图片地址](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/song-image.png)
+
+![获取专辑id](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/get-song-album-id.png)
+
+![获取歌曲专辑地址](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/get-album-image.png)
+
+![歌曲专辑地址](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/album-image.png)
+
+### 获取歌曲歌词
+
+接口说明: 调用此接口, 可获取歌曲歌词
+
+参数列表:
+
+- 必选参数
+
+ - `songmid`: 专辑`id`
+
+- 可选参数
+
+ - `isFormat`: 是否格式化歌词, 默认值为 `false`
+
+接口地址: `/getLyric`
+
+调用例子: `/getLyric?songmid=003rJSwm3TechU`
+
+示例截图:
+
+![获取歌曲歌词 - 未格式化](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getLyric.png)
![获取歌曲歌词 - 格式化](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getLyric-parse.png)
-### 获取MV
+## MV
+
+### 获取 MV 标签
+
+接口说明: 调用此接口, 可获取 MV 标签
+
+接口地址: `/getMvByTag`
+
+调用例子: `/getMvByTag`
+
+示例截图:
+
+![获取MV标签](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getMvByTag.png)
+
+### 获取 MV 播放信息
+
+接口说明: 调用此接口, 可获取 MV 播放信息
+
+参数列表:
+
+- 必选参数
+
+ - `vid`: `video id`
+
+接口地址: `/getMvPlay`
+
+调用例子: `/getMvPlay?vid=u00222le4ox`
+
+示例截图:
+
+![获取MV播放信息](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getMvPlay.png)
+
+### 获取歌手 MV
-接口说明: 调用此接口, 可获取MV以及其Tag信息
+接口说明: 调用此接口, 可获取歌手 MV
参数列表:
- 必选参数
- - `area_id`: 区域`id`, 默认值为全部(15)
+ - `singermid`: 歌手`id`
+
+- 可选参数
+
+ - `order`: 当前 MV 类型, 默认为`time`
+
+ - `listen`: 歌手专辑音乐 MV
+
+ - `time`: 粉丝上传 MV 视频
+
+ - `limit`: 取出歌单数量, 默认为 5
+
+接口地址: `/getSingerMV`
+
+调用例子: `/getSingerMV?singermid=0025NhlN2yWrP4&order=all&limit=5`
+
+示例截图:
+
+![获取歌手MV - default](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSingerMv-default.png)
+
+![获取歌手MV - belong](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSingerMv-listen.png)
+
+![获取歌手MV - fans](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSingerMv-time.png)
+
+### 获取 MV
+
+接口说明: 调用此接口, 可获取 MV 以及其 Tag 信息
+
+参数列表:
+
+- 必选参数
+
+ - `area_id`: 区域`id`, 默认值为全部(15)
Area
@@ -824,6 +1009,7 @@ const userInfo = {
"name": "日本"
}
]
+
`version_id`: 版本`id`, 默认值为全部(7)
@@ -865,13 +1051,14 @@ const userInfo = {
"name": "儿歌"
}
]
+
- 可选参数
- - `page`: 当前页数, 默认为1
+ - `page`: 当前页数, 默认为 1
- - `limit`: 取出歌单数量, 默认为 20
+ - `limit`: 取出歌单数量, 默认为 20
接口地址: `/getMv`
@@ -881,349 +1068,216 @@ const userInfo = {
![获取MV](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getMv.png)
-### 获取新碟信息
+## 排行榜
-接口说明: 调用此接口, 可获取新碟信息
+### 获取排行榜单列表
-参数列表:
+接口说明: 调用此接口, 可获取排行榜单列表
- 可选参数
- - `page`: 当前页数, 默认为 1
+ - `page`: 当前页数, 默认为 `1`
- - `limit`: 取出歌单数量, 默认为 20
+ - `limit`: 取出歌单数量, 默认为 `10`
-接口地址: `/getNewDisks`
+接口地址: `/getTopLists`
-调用例子: `/getNewDisks`
+调用例子: `/getTopLists`
示例截图:
-![获取新碟信息](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getNewDisks.png)
-
-### 获取歌手专辑
-
-接口说明: 调用此接口, 可获取歌手专辑
-
-参数列表:
+![获取排行榜单列表](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getTopLists.png)
-- 必选参数
+### 获取排行榜单详情
- - `singermid`: 歌手`id`
+接口说明: 调用此接口, 可获取排行榜单详情
- 可选参数
- - `page`: 当前页数, 默认为1
-
- - `limit`: 取出歌单数量, 默认为 20
-
-接口地址: `/getSingerAlbum`
-
-调用例子: `/getSingerAlbum?singermid=0025NhlN2yWrP4`
-
-示例截图:
-
-![获取歌手专辑](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSingerAlbum.png)
-
-### 获取歌曲相关信息
-
-接口说明: 调用此接口, 可获取歌曲相关信息
+ - `topId`: 榜单`id`
-参数列表:
-
-- 必选参数
+ - `page`: 当前页数, 默认为 1
- - `songmid`: 歌曲`id`
+ - `limit`: 取出歌单数量, 默认为 10
-接口地址: `/getSongInfo`
+接口地址: `/getRanks`
-调用例子: `/getSongInfo?songmid=0025NhlN2yWrP4`
+调用例子: `/getRanks`
示例截图:
-![获取歌曲相关信息](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSongInfo.png)
-
-### 批量获取歌曲相关信息
+![获取排行榜单详情](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getRanks.png)
-接口说明: 调用此接口, 可批量获取歌曲相关信息
+### 获取评论信息(cmd 代表的意思没太弄明白)
-参数列表:
+接口说明: 调用此接口, 可获取评论信息
- 必选参数
-```
-songs: [
- [songmid, songid]
-]
-```
-
-其中 `songid`可以不传
-
-接口地址: `/batchGetSongInfo`
-
-调用例子: `/batchGetSongInfo`
-
-```body
-{
- "songs": [
- ["001CLC7W2Gpz4J"],
- ["0025NhlN2yWrP4"]
- ]
-}
-```
-示例截图:
+ - `id`: 专辑或者歌单请求结果的`id`
-![获取歌曲相关信息](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/batchGetSongInfo.png)
+- 可选参数
-### 获取歌曲播放链接
+ - `rootcommentid`: 榜单`id`
-接口说明: 调用此接口, 可获取歌曲播放链接
+ - `cid`:
-参数列表:
+ - `pagenum`: 当前页数, 默认为 `0`
-- 必选参数
+ - `pagesize`: 取出评论数量, 默认为 `25`
-- `songmid`: 歌曲`id`, 多个播放链接使用 `,`分隔
+ - `cmd`:
-- `justPlayUrl`: 仅返回播放链接, 默认是 `play`。`[all | play]`
+ - `reqtype`:
-- `quality`: 播放品质, 默认是 128。`[m4a | 128 | 320 | ape | flac]`
+ - `biztype`:
-接口地址: `/getMusicPlay`
+接口地址: `/getComments`
-调用例子:
+调用例子: `/getComments?id=8220&rootcommentid=album_8220_1003310416_1558068713`
示例截图:
-#### 获取单个播放链接
-
-例子: `/songmid=0025NhlN2yWrP4`
-
-![获取单个播放链接](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getMusicPlay.png)
-
-#### 获取多个播放链接
-
-例子: `/songmid=001yNIo41SJjuC,001wPuVc4ZiMhj&resType=play`
-
-![获取多个歌曲播放链接](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/just-get-play-url.png)
-
-#### 获取多个播放链接
-
-例子: `/songmid=001yNIo41SJjuC,001wPuVc4ZiMhj&resType=all`
-
-![获取接口所有数据](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/get-play-all-data.png)
-
-#### 歌曲品质
-
-例子: `songmid=001yNIo41SJjuC&resType=play&quality=m4a`
-
-![song-quality-128.png](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/song-quality-128.png)
-
-![song-quality-m4a.png](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/song-quality-m4a.png)
-
-### 获取搜索热词
-
-接口说明: 调用此接口, 可获取搜索热词
-
-接口地址: `/getHotkey`
+![获取评论信息 - id获取](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getComments-id.png)
-调用例子: `/getHotkey`
+![获取评论信息](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getComments.png)
-示例截图:
+![获取评论信息 - 带params](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getComments-param.png)
-![获取搜索热词](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/gethotkey.png)
+## 关注
-### 获取关键字搜索提示
+### 获取歌手被关注数量信息
-接口说明: 调用此接口, 可获取获取关键字搜索提示
+接口说明: 调用此接口, 可获取歌手被关注数量信息
参数列表:
- 必选参数
- - `key`: 搜索关键字
+ - `singermid`: 歌手`id`
-接口地址: `/getSmartbox`
+接口地址: `/getSingerStarNum`
-调用例子: `/getSmartbox?key=周杰伦`
+调用例子: `/getSingerStarNum?singermid=0025NhlN2yWrP4`
示例截图:
-![获取获取关键字搜索提示](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSmartbox.png)
-
-### 获取搜索结果
-
-接口说明: 调用此接口, 可获取获取搜索结果
-
-参数列表(部分参数待注释):
-
-- 必选参数
-
- - `key`: 搜索关键字
-
- - ~~`catZhida`: 默认值为1~~
+![获取歌手被关注数量信息](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSingerStarNum.png)
- ~~1. `0` 表示歌曲~~
+## 电台
- ~~2. `2` 表示歌手~~
+### 获取电台列表
- ~~3. `3` 表示专辑~~
+接口说明: 调用此接口, 可获取电台列表, 分类
- - `remoteplace`: 默认值为 `song`
+接口地址: `/getRadioLists`
- 1. 单曲: `song`
+调用例子: `/getRadioLists`
- 2. 专辑: `album`
+示例截图:
- 3. MV: `mv`
+![获取电台列表](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getRadioLists.png)
- 4. 歌单: `playlist`
+## 专辑
- 5. 用户: `user`
+### 获取专辑
- 6. 歌词: `lyric`
+接口说明: 调用此接口, 可获取专辑信息(专辑列表、详情)
-- 可选参数
+参数列表:
- - `page`: 当前页数, 默认为 `1`
+- 必选参数
- - `limit`: 取出歌单数量, 默认为 `10`
+ - `albummid`: 专辑`id`
-接口地址: `/getSearchByKey`
+接口地址: `/getAlbumInfo`
-调用例子: `/getSearchByKey?key=周杰伦`
+调用例子: `/getAlbumInfo?albummid=0016l2F430zMux`
示例截图:
-![获取获取搜索结果](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSearchByKey.png)
+![获取专辑](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getAlbumInfo.png)
-### 获取首页推荐
+### 获取数字专辑
-接口说明: 调用此接口, 可获取首页推荐
+接口说明: 调用此接口, 可获取数字专辑, 轮播图`banner`, 专辑列表等信息, 详见[API 结构图](#API结构图)
-接口地址: `/getRecommend`
+接口地址: `/getDigitalAlbumLists`
-调用例子: `/getRecommend`
+调用例子: `/getDigitalAlbumLists`
示例截图:
-![获取首页推荐](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getRecommend.png)
-
-### 获取排行榜单列表
-
-接口说明: 调用此接口, 可获取排行榜单列表
-
-- 可选参数
-
- - `page`: 当前页数, 默认为 `1`
-
- - `limit`: 取出歌单数量, 默认为 `10`
-
-接口地址: `/getTopLists`
+![获取数字专辑](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getDigitalAlbumLists.png)
-调用例子: `/getTopLists`
+### 获取歌手专辑
-示例截图:
+接口说明: 调用此接口, 可获取歌手专辑
-![获取排行榜单列表](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getTopLists.png)
+参数列表:
-### 获取排行榜单详情
+- 必选参数
-接口说明: 调用此接口, 可获取排行榜单详情
+ - `singermid`: 歌手`id`
- 可选参数
- - `topId`: 榜单`id`
-
- - `page`: 当前页数, 默认为1
+ - `page`: 当前页数, 默认为 1
- - `limit`: 取出歌单数量, 默认为 10
+ - `limit`: 取出歌单数量, 默认为 20
-接口地址: `/getRanks`
+接口地址: `/getSingerAlbum`
-调用例子: `/getRanks`
+调用例子: `/getSingerAlbum?singermid=0025NhlN2yWrP4`
示例截图:
-![获取排行榜单详情](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getRanks.png)
+![获取歌手专辑](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getSingerAlbum.png)
-### 获取评论信息(cmd代表的意思没太弄明白)
+## 新碟
-接口说明: 调用此接口, 可获取评论信息
+### 获取新碟信息
-- 必选参数
+接口说明: 调用此接口, 可获取新碟信息
- - `id`: 专辑或者歌单请求结果的`id`
+参数列表:
- 可选参数
- - `rootcommentid`: 榜单`id`
-
- - `cid`:
-
- - `pagenum`: 当前页数, 默认为 `0`
-
- - `pagesize`: 取出评论数量, 默认为 `25`
-
- - `cmd`:
+ - `page`: 当前页数, 默认为 1
- - `reqtype`:
+ - `limit`: 取出歌单数量, 默认为 20
- - `biztype`:
-
-
-接口地址: `/getComments`
+接口地址: `/getNewDisks`
-调用例子: `/getComments?id=8220&rootcommentid=album_8220_1003310416_1558068713`
+调用例子: `/getNewDisks`
示例截图:
-![获取评论信息 - id获取](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getComments-id.png)
-
-![获取评论信息](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getComments.png)
+![获取新碟信息](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getNewDisks.png)
-![获取评论信息 - 带params](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getComments-param.png)
+## 产品
-### 获取歌曲 + 专辑 图片
+### 获取票务信息
接口说明: 调用此接口, 可获取票务信息
-- 必选参数
-
- - `id`: 专辑或者歌单请求结果的`id`
-
-- 可选参数
-
- - `size`: 图片大小, 默认 `300x300`
-
- - `maxAge`: 图片过期时间, 默认 `12 mins = 2592000ms`
-
-接口地址: `/getImageUrl`
+接口地址: `/getTicketInfo`
-调用例子: `/getImageUrl?id=000MkMni19ClKG` or `/getImageUrl?id=000MkMni19ClKG&size=500x500`
+调用例子: `/getTicketInfo`
示例截图:
-![获取歌曲id](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/get-song-id.png)
-
-![获取歌曲图片地址](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/get-song-image.png)
-
-![歌曲图片地址](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/song-image.png)
-
-![获取专辑id](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/get-song-album-id.png)
-
-![获取歌曲专辑地址](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/get-album-image.png)
-
-![歌曲专辑地址](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/album-image.png)
+![获取票务信息](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getTicketInfo.png)
-### 获取票务信息
+### 获取 QQ 音乐产品的下载地址
-接口说明: 调用此接口, 可获取票务信息
+接口说明: 调用此接口, 可获取 QQ 音乐标准产品下载链接
-接口地址: `/getTicketInfo`
+接口地址: `/downloadQQMusic`
-调用例子: `/getTicketInfo`
+调用例子: `/downloadQQMusic`
示例截图:
-![获取票务信息](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/getTicketInfo.png)
+![获取QQ音乐产品的下载地址](https://raw.githubusercontent.com/Rain120/qq-music-api/master/screenshot/downloadQQMusic.png)
diff --git a/module/URequest.js b/module/URequest.js
new file mode 100644
index 0000000..c7b64f9
--- /dev/null
+++ b/module/URequest.js
@@ -0,0 +1,13 @@
+const U = require('./u-axios');
+
+module.exports = ({
+ method = 'get',
+ params = {},
+ option = {},
+ thenable,
+ catcher = err => null,
+}) => {
+ const options = Object.assign(option, { params });
+
+ return U({ method, options }).then(thenable).catch(catcher);
+};
diff --git a/module/u-axios.js b/module/u-axios.js
new file mode 100644
index 0000000..4783a94
--- /dev/null
+++ b/module/u-axios.js
@@ -0,0 +1,15 @@
+const request = require('../util/request');
+const config = require('./config');
+
+module.exports = ({ options = {}, method = 'get' }) => {
+ const opts = Object.assign(options, config.commonParams, {
+ headers: {
+ referer: 'https://y.qq.com/portal/player.html',
+ host: 'u.y.qq.com',
+ 'content-type': 'application/x-www-form-urlencoded',
+ },
+ });
+
+ global.log && console.log('https://u.y.qq.com/cgi-bin/musicu.fcg', { opts });
+ return request('https://u.y.qq.com/cgi-bin/musicu.fcg', method, opts, 'u');
+};
diff --git a/module/y-axios.js b/module/y-axios.js
new file mode 100644
index 0000000..deb6a92
--- /dev/null
+++ b/module/y-axios.js
@@ -0,0 +1,22 @@
+const request = require('../util/request');
+const config = require('./config');
+
+module.exports = ({
+ url,
+ method = 'get',
+ options = {},
+ hasCommonParams = true,
+ thenable,
+ catcher = err => null,
+}) => {
+ const commonParams = hasCommonParams ? config.commonParams : {};
+ const opts = Object.assign(options, commonParams, {
+ headers: {
+ referer: 'https://c.y.qq.com/',
+ host: 'c.y.qq.com',
+ },
+ });
+
+ global.log && console.log(url, { opts });
+ return request(url, method, opts).then(thenable).catch(catcher);
+};
diff --git a/package-lock.json b/package-lock.json
index fa528a2..506bee0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "qq-music-api",
- "version": "1.0.3",
+ "version": "1.0.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -5898,6 +5898,12 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
+ "sax": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+ "dev": true
+ },
"select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
@@ -6813,6 +6819,22 @@
"integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==",
"dev": true
},
+ "xml2js": {
+ "version": "0.4.23",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
+ "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
+ "dev": true,
+ "requires": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
+ }
+ },
+ "xmlbuilder": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "dev": true
+ },
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
diff --git a/package.json b/package.json
index bb65ed7..602c717 100644
--- a/package.json
+++ b/package.json
@@ -74,7 +74,8 @@
"husky": "^4.2.5",
"lint-staged": "^10.2.11",
"nodemon": "^2.0.4",
- "prettier": "^2.0.5"
+ "prettier": "^2.0.5",
+ "xml2js": "^0.4.23"
},
"engines": {
"node": ">=7.6.0"
diff --git a/routers/context/cookies.js b/routers/context/cookies.js
deleted file mode 100644
index 53f1ea5..0000000
--- a/routers/context/cookies.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * @Author: Rainy [https://github.com/rain120]
- * @Date: 2021-01-23 15:41:41
- * @LastEditors: Rainy
- * @LastEditTime: 2021-01-23 18:13:59
- */
-module.exports = {
- get: async (ctx, next) => {
- ctx.status = 200;
- ctx.body = {
- data: {
- code: 200,
- cookie: global.cookie,
- cookieList: global.cookieList,
- cookieObject: global.cookieObject,
- },
- };
- },
- set: async (ctx, next) => {
- ctx.request.cookies = global.cookie;
- ctx.request.header['Access-Control-Allow-Origin'] = 'https://y.qq.com';
- ctx.request.header['Access-Control-Allow-Methods'] = 'GET,PUT,POST,DELETE';
- ctx.request.header['Access-Control-Allow-Headers'] = 'Content-Type';
- ctx.request.header['Access-Control-Allow-Credentials'] = true;
- ctx.body = {
- data: {
- code: 200,
- message: '操作成功',
- }
- }
- },
-}
diff --git a/routers/context/index.js b/routers/context/index.js
deleted file mode 100644
index f208ae2..0000000
--- a/routers/context/index.js
+++ /dev/null
@@ -1,70 +0,0 @@
-const getDownloadQQMusic = require('./getDownloadQQMusic');
-const getHotKey = require('./getHotkey');
-const getSearchByKey = require('./getSearchByKey');
-const getSmartbox = require('./getSmartbox');
-const getSongListCategories = require('./getSongListCategories');
-const getSongLists = require('./getSongLists');
-const batchGetSongLists = require('./batchGetSongLists');
-const getSongInfo = require('./getSongInfo');
-const batchGetSongInfo = require('./batchGetSongInfo');
-const getSongListDetail = require('./getSongListDetail');
-const getNewDisks = require('./getNewDisks');
-const getMvByTag = require('./getMvByTag');
-const getMv = require('./getMv');
-const getSingerList = require('./getSingerList');
-const getSimilarSinger = require('./getSimilarSinger');
-const getSingerAlbum = require('./getSingerAlbum');
-const getSingerHotsong = require('./getSingerHotsong');
-const getSingerMv = require('./getSingerMv');
-const getSingerDesc = require('./getSingerDesc');
-const getSingerStarNum = require('./getSingerStarNum');
-const getRadioLists = require('./getRadioLists');
-const getDigitalAlbumLists = require('./getDigitalAlbumLists');
-const getLyric = require('./getLyric');
-const getMusicPlay = require('./getMusicPlay');
-const getAlbumInfo = require('./getAlbumInfo');
-const getComments = require('./getComments');
-const getRecommend = require('./getRecommend');
-const getMvPlay = require('./getMvPlay');
-const getTopLists = require('./getTopLists');
-const getRanks = require('./getRanks');
-const getTicketInfo = require('./getTicketInfo');
-const getImageUrl = require('./getImageUrl');
-const {get: getCookie, set: setCookie} = require('./cookies');
-
-module.exports = {
- getCookie,
- setCookie,
- getDownloadQQMusic,
- getHotKey,
- getSearchByKey,
- getSmartbox,
- getSongListCategories,
- getSongLists,
- batchGetSongLists,
- getSongInfo,
- batchGetSongInfo,
- getSongListDetail,
- getNewDisks,
- getMvByTag,
- getMv,
- getSingerList,
- getSimilarSinger,
- getSingerAlbum,
- getSingerHotsong,
- getSingerMv,
- getSingerDesc,
- getSingerStarNum,
- getRadioLists,
- getDigitalAlbumLists,
- getLyric,
- getMusicPlay,
- getAlbumInfo,
- getComments,
- getRecommend,
- getMvPlay,
- getTopLists,
- getRanks,
- getTicketInfo,
- getImageUrl,
-};
diff --git a/routers/index.js b/routers/index.js
new file mode 100644
index 0000000..bb0923d
--- /dev/null
+++ b/routers/index.js
@@ -0,0 +1,74 @@
+const getDownloadQQMusic = require('./context/getDownloadQQMusic');
+const getHotKey = require('./context/getHotkey');
+const getSearchByKey = require('./context/getSearchByKey');
+const getSmartbox = require('./context/getSmartbox');
+const getSongListCategories = require('./context/getSongListCategories');
+const getSongLists = require('./context/getSongLists');
+const batchGetSongLists = require('./context/batchGetSongLists');
+const getSongInfo = require('./context/getSongInfo');
+const batchGetSongInfo = require('./context/batchGetSongInfo');
+const getSongListDetail = require('./context/getSongListDetail');
+const getNewDisks = require('./context/getNewDisks');
+const getMvByTag = require('./context/getMvByTag');
+const getMv = require('./context/getMv');
+const getSingerList = require('./context/getSingerList');
+// const getSimilarSinger = require('./getSimilarSinger');
+const getSingerAlbum = require('./context/getSingerAlbum');
+const getSingerHotsong = require('./context/getSingerHotsong');
+const getSingerMv = require('./context/getSingerMv');
+// const getSingerDesc = require('./getSingerDesc');
+const getSingerStarNum = require('./context/getSingerStarNum');
+const getRadioLists = require('./context/getRadioLists');
+const getDigitalAlbumLists = require('./context/getDigitalAlbumLists');
+const getLyric = require('./context/getLyric');
+const getMusicPlay = require('./context/getMusicPlay');
+const getAlbumInfo = require('./context/getAlbumInfo');
+const getComments = require('./context/getComments');
+const getRecommend = require('./context/getRecommend');
+const getMvPlay = require('./context/getMvPlay');
+const getTopLists = require('./context/getTopLists');
+const getRanks = require('./context/getRanks');
+const getTicketInfo = require('./context/getTicketInfo');
+const getImageUrl = require('./context/getImageUrl');
+// Cookies
+const cookies = require('./user');
+
+// Singer
+const singer = require('./singer');
+
+module.exports = {
+ ...cookies,
+ ...singer,
+ getDownloadQQMusic,
+ getHotKey,
+ getSearchByKey,
+ getSmartbox,
+ getSongListCategories,
+ getSongLists,
+ batchGetSongLists,
+ getSongInfo,
+ batchGetSongInfo,
+ getSongListDetail,
+ getNewDisks,
+ getMvByTag,
+ getMv,
+ getSingerList,
+ // getSimilarSinger,
+ getSingerAlbum,
+ getSingerHotsong,
+ getSingerMv,
+ // getSingerDesc,
+ getSingerStarNum,
+ getRadioLists,
+ getDigitalAlbumLists,
+ getLyric,
+ getMusicPlay,
+ getAlbumInfo,
+ getComments,
+ getRecommend,
+ getMvPlay,
+ getTopLists,
+ getRanks,
+ getTicketInfo,
+ getImageUrl,
+};
diff --git a/routers/map-router.js b/routers/map-router.js
new file mode 100644
index 0000000..7697a66
--- /dev/null
+++ b/routers/map-router.js
@@ -0,0 +1,136 @@
+/*
+ * @Author: Rainy [https://github.com/rain120]
+ * @Date: 2021-01-30 15:30:13
+ * @LastEditors: Rainy
+ * @LastEditTime: 2021-01-31 11:46:13
+ */
+const context = require('.');
+
+module.exports = {
+ // INFO: singer Module
+ // /getSimilarSinger?singermid=0025NhlN2yWrP4
+ '/getSimilarSinger/:singermid?': {
+ cb: context.getSimilarSinger,
+ },
+ // /singe/similar?id=4558&singermid=0025NhlN2yWrP4
+ '/singer/similar/:id/:singermid/:pageNum?': {
+ cb: context.getSimilarSinger,
+ },
+ // TODO:
+ '/getSingerList/:area?/:sex?/:genre?/:index?/:page?': {
+ cb: context.getSingerList,
+ },
+ '/getSingerAlbum/:singermid?/:limit?/:page?': {
+ cb: context.getSingerAlbum,
+ },
+ '/getSingerHotsong/:singermid?/:limit?/:page?': {
+ cb: context.getSingerHotsong,
+ },
+ '/getSingerMv/:singermid?/:limit?/:order?': {
+ cb: context.getSingerMv,
+ },
+ '/getSingerDesc/:singermid?': {
+ cb: context.getSingerDesc,
+ },
+ '/singer/desc/:singermid?': {
+ cb: context.getSingerDesc,
+ },
+ '/getSingerStarNum/:singermid?': {
+ cb: context.getSingerStarNum,
+ },
+
+ // INFO: user Module
+ '/user/getCookie': {
+ cb: context.getCookie,
+ },
+ '/user/setCookie': {
+ cb: context.setCookie,
+ },
+
+ // INFO: app Module
+ '/downloadQQMusic': {
+ cb: context.getDownloadQQMusic,
+ },
+
+ // INFO: search-hot Module
+ '/getHotkey': {
+ cb: context.getHotKey,
+ },
+ '/getSearchByKey/:key?/:limit?/:page?/:catZhida?': {
+ cb: context.getSearchByKey,
+ },
+ '/getSmartbox/:key?': {
+ cb: context.getSmartbox,
+ },
+
+ // INFO: song Module
+ '/getSongListCategories': {
+ cb: context.getSongListCategories,
+ },
+ '/getMusicPlay/:songmid?': {
+ cb: context.getMusicPlay,
+ },
+ '/getSongLists/:page?/:limit?/:categoryId?/:sortId?': {
+ cb: context.getSongLists,
+ },
+ '/batchGetSongLists': {
+ method: 'post',
+ cb: context.batchGetSongLists,
+ },
+ '/getSongInfo/:songmid?/:songid?': {
+ cb: context.getSongInfo,
+ },
+ '/batchGetSongInfo': {
+ method: 'post',
+ cb: context.batchGetSongInfo,
+ },
+ '/getSongListDetail/:disstid?': {
+ cb: context.getSongListDetail,
+ },
+ '/getLyric/:songmid?/:isFormat?': {
+ cb: context.getLyric,
+ },
+
+ '/getNewDisks/:page?/:limit?': {
+ cb: context.getNewDisks,
+ },
+
+ // INFO: MV Module
+ '/getMvByTag': {
+ cb: context.getMvByTag,
+ },
+ '/getMv/:area_id?/:version_id?/:limit?/:page?': {
+ cb: context.getMv,
+ },
+ '/getMvPlay/:vid?': {
+ cb: context.getMvPlay,
+ },
+
+ '/getRadioLists': {
+ cb: context.getRadioLists,
+ },
+ '/getDigitalAlbumLists': {
+ cb: context.getDigitalAlbumLists,
+ },
+ '/getAlbumInfo/:albummid?': {
+ cb: context.getAlbumInfo,
+ },
+ '/getComments/:id?/:rootcommentid?/:cid?/:pagesize?/:pagenum?/:cmd?/:reqtype?/:biztype?': {
+ cb: context.getComments,
+ },
+ '/getRecommend': {
+ cb: context.getRecommend,
+ },
+ '/getTopLists': {
+ cb: context.getTopLists,
+ },
+ '/getRanks/:topId?/:limit?/:page?': {
+ cb: context.getRanks,
+ },
+ '/getTicketInfo': {
+ cb: context.getTicketInfo,
+ },
+ '/getImageUrl': {
+ cb: context.getImageUrl,
+ },
+};
diff --git a/routers/new-router.js b/routers/new-router.js
new file mode 100644
index 0000000..4889759
--- /dev/null
+++ b/routers/new-router.js
@@ -0,0 +1,12 @@
+const Router = require('koa-router');
+const router = new Router();
+const mapRouter = require('./map-router');
+
+Object.keys(mapRouter).forEach(key => {
+ if (key && mapRouter[key]) {
+ const { method = 'get', cb } = mapRouter[key];
+ router[method](key, cb);
+ }
+});
+
+module.exports = router;
diff --git a/routers/router.js b/routers/router.js
index eba623c..6c86df6 100644
--- a/routers/router.js
+++ b/routers/router.js
@@ -1,6 +1,6 @@
const Router = require('koa-router');
const router = new Router();
-const context = require('./context');
+const context = require('.');
// cookies
router.get('/user/getCookie', context.getCookie);
@@ -45,9 +45,12 @@ router.get('/getMv/:area_id?/:version_id?/:limit?/:page?', context.getMv);
router.get('/getSingerList/:area?/:sex?/:genre?/:index?/:page?', context.getSingerList);
// getSimilarSinger
-// singermid=0025NhlN2yWrP4
+// /getSimilarSinger?singermid=0025NhlN2yWrP4
router.get('/getSimilarSinger/:singermid?', context.getSimilarSinger);
+// /singer/similar?singermid=0025NhlN2yWrP4
+router.get('/singer/similar/:singermid?', context.getSimilarSinger);
+
// getSingerAlbum
// singermid=0025NhlN2yWrP4
router.get('/getSingerAlbum/:singermid?/:limit?/:page?', context.getSingerAlbum);
@@ -83,8 +86,8 @@ router.get('/getMusicPlay/:songmid?', context.getMusicPlay);
router.get('/getAlbumInfo/:albummid?', context.getAlbumInfo);
router.get(
- '/getComments/:id?/:rootcommentid?/:cid?/:pagesize?/:pagenum?/:cmd?/:reqtype?/:biztype?',
- context.getComments,
+ '/getComments/:id?/:rootcommentid?/:cid?/:pagesize?/:pagenum?/:cmd?/:reqtype?/:biztype?',
+ context.getComments,
);
// recommend
diff --git a/routers/singer.js b/routers/singer.js
new file mode 100644
index 0000000..a4d1461
--- /dev/null
+++ b/routers/singer.js
@@ -0,0 +1,160 @@
+/*
+ * @Author: Rainy [https://github.com/rain120]
+ * @Date: 2021-01-30 13:12:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2021-01-31 14:12:41
+ */
+const YRequest = require('../module/y-axios');
+const URequest = require('../module/URequest');
+const { commonParams } = require('../module/config');
+const moment = require('moment');
+const { handleXml } = require('../util/xml');
+
+const singerParams = Object.assign(commonParams, {
+ g_tk: 1290642389,
+ sign: 'zzafsjavizx8rzd4m36c2a7e17c9f884980ef9092cb8809e415',
+ format: 'json',
+});
+
+// /singe/similar?singermid=0025NhlN2yWrP4
+const similarOld = async (ctx, next) => {
+ const { singermid: singer_mid } = ctx.query;
+ const options = {
+ params: {
+ format: 'json',
+ outCharset: 'utf-8',
+ utf8: 1,
+ start: 0,
+ num: 5,
+ singer_mid,
+ },
+ };
+
+ return await YRequest({
+ url: '/v8/fcg-bin/fcg_v8_simsinger.fcg',
+ method: 'get',
+ options,
+ thenable: res => {
+ if (!singer_mid) {
+ ctx.status = 400;
+ ctx.body = {
+ message: 'no singermid',
+ };
+ return;
+ }
+ Object.assign(ctx, {
+ status: 200,
+ body: {
+ data: res.data,
+ },
+ });
+ },
+ catcher: error => {
+ console.log('error', error);
+ ctx.status = 400;
+ ctx.body = { error };
+ },
+ });
+};
+
+// 周杰伦 /singe/similar?id=4558&singermid=0025NhlN2yWrP4
+const similar = async (ctx, next) => {
+ const { id: singerId, singermid: singerMid, pageSize: num = 5 } = ctx.query;
+ console.log('similar error: not support pageSize');
+ const data = {
+ comm: {
+ ct: 24,
+ cv: 10000,
+ },
+ similarSingerList: {
+ method: 'GetSimilarSingerList',
+ param: {
+ singerId: parseInt(singerId, 10),
+ singerMid,
+ // TODO: QQ 貌似不支持
+ num: 5,
+ },
+ module: 'music.SimilarSingerSvr',
+ },
+ };
+
+ const params = Object.assign(singerParams, {
+ '-': 'getSimilarSingerList1112654390871275',
+ data: JSON.stringify(data),
+ });
+
+ return await URequest({
+ method: 'get',
+ params,
+ thenable: res => {
+ if (!singerMid) {
+ ctx.status = 400;
+ ctx.body = {
+ message: 'no singermid',
+ };
+ return;
+ }
+ Object.assign(ctx, {
+ status: 200,
+ body: {
+ data: res.data,
+ },
+ });
+ },
+ catcher: error => {
+ console.log('error', error);
+ ctx.status = 400;
+ ctx.body = { error };
+ },
+ });
+};
+
+// /singe/desc?singermid=0025NhlN2yWrP4
+const desc = async (ctx, next) => {
+ const { singermid } = ctx.query;
+ const options = {
+ params: {
+ format: 'xml',
+ outCharset: 'utf-8',
+ utf8: 1,
+ r: moment().valueOf(),
+ singermid,
+ },
+ };
+
+ return await YRequest({
+ url: '/splcloud/fcgi-bin/fcg_get_singer_desc.fcg',
+ method: 'get',
+ options,
+ thenable: async res => {
+ if (!singermid) {
+ ctx.status = 400;
+ ctx.body = {
+ message: 'no singermid',
+ };
+ return;
+ }
+ const result = await handleXml(res.data.replace(/()/g, ''));
+ Object.assign(ctx, {
+ status: 200,
+ body: {
+ data: {
+ result,
+ xml: res.data,
+ },
+ },
+ });
+ },
+ catcher: error => {
+ console.log('error', error);
+ ctx.status = 400;
+ ctx.body = { error };
+ },
+ });
+};
+
+module.exports = {
+ getSimilarSingerOld: similarOld,
+ getSimilarSinger: similar,
+ getSingerDesc: desc,
+};
diff --git a/routers/user.js b/routers/user.js
new file mode 100644
index 0000000..35fb78c
--- /dev/null
+++ b/routers/user.js
@@ -0,0 +1,37 @@
+/*
+ * @Author: Rainy [https://github.com/rain120]
+ * @Date: 2021-01-23 15:41:41
+ * @LastEditors: Rainy
+ * @LastEditTime: 2021-01-31 11:56:19
+ */
+
+const get = async (ctx, next) => {
+ ctx.status = 200;
+ ctx.body = {
+ data: {
+ code: 200,
+ cookie: global.cookie,
+ cookieList: global.cookieList,
+ cookieObject: global.cookieObject,
+ },
+ };
+};
+
+const set = async (ctx, next) => {
+ ctx.request.cookies = global.cookie;
+ ctx.request.header['Access-Control-Allow-Origin'] = 'https://y.qq.com';
+ ctx.request.header['Access-Control-Allow-Methods'] = 'GET,PUT,POST,DELETE';
+ ctx.request.header['Access-Control-Allow-Headers'] = 'Content-Type';
+ ctx.request.header['Access-Control-Allow-Credentials'] = true;
+ ctx.body = {
+ data: {
+ code: 200,
+ message: '操作成功',
+ },
+ };
+};
+
+module.exports = {
+ getCookie: get,
+ setCookie: get,
+};
diff --git a/util/utils.js b/util/utils.js
new file mode 100644
index 0000000..ee032a3
--- /dev/null
+++ b/util/utils.js
@@ -0,0 +1,17 @@
+/*
+ * @Author: Rainy [https://github.com/rain120]
+ * @Date: 2021-01-31 11:21:45
+ * @LastEditors: Rainy
+ * @LastEditTime: 2021-01-31 11:32:06
+ */
+const isObject = value => Object.prototype.toString.call(value) === '[object Object]';
+
+const isArray = value =>
+ Array.isArray
+ ? Array.isArray(value)
+ : Object.prototype.toString.call(value) === '[object isArray]';
+
+module.exports = {
+ isObject,
+ isArray,
+};
diff --git a/util/xml.js b/util/xml.js
new file mode 100644
index 0000000..866b080
--- /dev/null
+++ b/util/xml.js
@@ -0,0 +1,37 @@
+/*
+ * @Author: Rainy [https://github.com/rain120]
+ * @Date: 2021-01-31 11:18:17
+ * @LastEditors: Rainy
+ * @LastEditTime: 2021-01-31 11:31:13
+ */
+const parseString = require('xml2js').parseString;
+const { isObject, isArray } = require('./utils');
+
+function handleXml(data) {
+ return new Promise((resolve, reject) => {
+ const handleObj = obj => {
+ Object.keys(obj).forEach(key => {
+ const value = obj[key];
+ if (isObject(value) && isArray(value) && value.length === 1) {
+ obj[key] = value[0];
+ }
+
+ if (isObject(value)) {
+ handleObj(obj[key]);
+ }
+ });
+ };
+
+ parseString(data, (err, result) => {
+ if (err) {
+ reject(err);
+ }
+ handleObj(result);
+ resolve(result);
+ });
+ });
+}
+
+module.exports = {
+ handleXml,
+};