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, +};