├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── app.js ├── docs ├── .nojekyll ├── 1660899340003.jpg ├── KG.md ├── KW.md ├── MG.md ├── README.md ├── TX.md ├── WY.md ├── _sidebar.md ├── common.md ├── index.html ├── question.md ├── scavengers.md ├── version.json └── 请关注一下公众号,每天有精彩资源推送.jpg ├── l.js ├── middlewares ├── auth.js └── rest.js ├── package-lock.json ├── package.json ├── public ├── controllers.js └── index.html ├── refreshQQCookie.js ├── routes └── v1 │ ├── controllers.js │ ├── index.js │ ├── kugou │ ├── comment.js │ ├── controllers.js │ ├── cookie.js │ ├── lyric.js │ ├── pic.js │ ├── playlist.js │ ├── search.js │ ├── song.js │ └── top.js │ ├── kuwo │ ├── comment.js │ ├── controllers.js │ ├── lyric.js │ ├── playlist.js │ ├── search.js │ ├── song.js │ └── top.js │ ├── migu │ ├── comment.js │ ├── controllers.js │ ├── cookie.js │ ├── lyric.js │ ├── playlist.js │ ├── search.js │ ├── singer.js │ ├── song.js │ └── top.js │ ├── qq │ ├── comment.js │ ├── controllers.js │ ├── cookie.js │ ├── login.js │ ├── lyric.js │ ├── playlist.js │ ├── search.js │ ├── song.js │ ├── song_back.js │ └── top.js │ ├── scavengers │ ├── controllers.js │ └── getMasterColor.js │ └── wy │ ├── comment.js │ ├── controllers.js │ ├── cookie.js │ ├── login.js │ ├── lyric.js │ ├── mv.js │ ├── playlist.js │ ├── recommendSongs.js │ ├── search.js │ ├── song.js │ └── top.js ├── setting.js ├── test ├── a.php ├── t copy.js ├── t.js └── testwysong.js └── util ├── MD5.js ├── cache.js ├── cookie_util.js ├── crypto.js ├── decodeLyric.js ├── kugou_mid.js ├── kugou_request.js ├── kuwo_request.js ├── login_qq.js ├── login_qq_scan.js ├── migu_request.js ├── p.js ├── qq_request.js ├── qqsign.js ├── sign.js └── sssss.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}\\app.js" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > wp_MusicApi 一个荒野拾荒者的 API 2 | 3 | 4 | 5 | ![](https://img.shields.io/badge/最新版本-v1.5.2-green) 6 | 7 | [![](https://img.shields.io/badge/QQ群1-922193759-green)](https://jq.qq.com/?_wv=1027&k=3USa76OC) 8 | 9 | 10 | 11 | [点击加入QQ群](https://jq.qq.com/?_wv=1027&k=3USa76OC) 12 | 13 | ## 公告 14 | 15 | 文档地址:[文档](https://github-zc.github.io/wp_MusicApi/) 16 | 17 | 网易云接口:[点击跳转](https://binaryify.github.io/NeteaseCloudMusicApi/) (服务器地址:http://iecoxe.top:3000 ) 18 | 19 | 个人博客CSDN:[CSDN地址](https://blog.csdn.net/weixin_44358443?type=blog) 20 | 21 | 基于本接口开发的软件: 22 | 23 | [wp music Android版](https://blog.iecoxe.top/2021/03/07/7/) 24 | 25 | [wp music 电脑版](https://github.com/GitHub-ZC/Wp_music/releases) 26 | 27 | 28 | 29 | 欢迎大家关注**微信公众号**,后续会慢慢更新优质资源 30 | 31 | ![请关注一下公众号,每天有精彩资源推送](C:\Users\15314\Desktop\tools\wp_MusicApi\docs\请关注一下公众号,每天有精彩资源推送.jpg) 32 | 33 | ## 新版特性 34 | 35 | 1. 相对更加稳定,服务器不再过滤参数内容,用户自行解析参数,减少参数解析出错几率 36 | 2. 支持平台:QQ 音乐,酷狗音乐,酷我音乐,咪咕音乐,网易,具体音乐音质支持往下看 37 | 3. 接口更加丰富,增加部分接口和平台 38 | 4. 降低门槛,参数简单统一,支持 GET 请求,支持跨域调用 39 | 40 | 41 | 42 | 43 | 44 | ## 项目安装与使用(新增 npm 安装方式) 45 | 46 | [wp_MusicApi 安装使用说明](https://github-zc.github.io/wp_MusicApi/#/common) 47 | 48 | 49 | 50 | 51 | 52 | ## Cookie 共享仓库使用说明 53 | 54 | 最近新写的接口,单独部署,主要实现 QQ音乐 Cookie 自动刷新 55 | 56 | 57 | 58 | > 接口:(GET) http://42.192.118.65:5100/qq/getCookie 59 | 60 | 示例:http://42.192.118.65:5100/qq/getCookie?uin=123456789 61 | 62 | 参数:uin => QQ号 63 | 64 | 注意:获取前提是 你上传过(通过/qq/setCookie接口) 登录成功的 QQ音乐 Cookie 字符串 65 | 66 | 67 | 68 | > 接口:(POST) http://42.192.118.65:5100/qq/setCookie 69 | 70 | 参数:data => QQ音乐 Cookie 字符串格式 71 | 72 | 功能:上传的Cookie会自动刷新登录,通过(/qq/getCookie)获取的是刷新过后的Cookie 73 | 74 | 注意:请上传登录成功的 QQ音乐 Cookie 字符串格式(浏览器 F12 获取),请使用 `x-www-form-urlencoded` 格式提交 75 | 76 | 77 | 78 | **项目介绍**:此项目只需要在 `setting.js` 项目设置文件中,修改 `QQ_uin` 字段的值(请填入QQ号,如果需要获取VIP歌曲,请传入带绿钻的QQ号,**前提是先将登陆过的Cookie传入共享仓库**),就能实现Cookie的自动刷新 79 | 80 | 81 | 82 | 83 | 84 | ## 工作原理 85 | 86 | 跨站请求伪造 (CSRF), 伪造请求头 , 调用官方 API 87 | 88 | 89 | 90 | ## 关于项目 91 | 92 | > wp_MusicApi 拾荒者的音乐接口 93 | 94 | v1.0:项目开始 95 | 96 | v1.1:新建`cache_redis`分支,主要用于服务端缓存,默认两分钟,需要的请自行clone 97 | 98 | v1.2:新增的咪咕、酷我、QQ的热搜参数,新增`pc`和`web`,更正了文档的已知错误,修改了咪咕和酷我的排行榜数据内容格式,以前使用本API的请**注意** 99 | 100 | v1.3: 维护所有失效接口,新增网易播放接口以及Cookie上传,新增咪咕高无损,新增酷狗无损以及Cookie上传,新增QQ登录,新增缓存,默认缓存5分钟,更新文档,修改了咪咕排行榜的数据格式,修改了QQ搜索的数据格式, 具体请自行研究。 101 | 102 | v1.3.1: 新增 首页导航,提示项目正在运行,新增版本检测提示 103 | 104 | v1.3.2: 更新酷狗搜索,改走客户端接口,更改版本检测timeout时间为3秒 105 | 106 | v1.3.3: 新增酷狗 mobileSearch 接口 107 | 108 | v1.3.4: 更新 酷狗排行榜详情 接口 109 | 110 | v1.3.5: 维护部分接口 111 | 112 | v1.4: 新增QQ扫码登录,本次采用的是基于Android端QQ音乐的身份令牌,登陆时间会保证更长时间 113 | 114 | v1.4.1: 去除QQ基于Android端QQ音乐的扫码登录,新增接口加密,默认关闭,需要打开可以在 setting.js 中打开,新增网易云歌单歌曲获取,新增网易云无损音乐 115 | 116 | v1.4.2: 新增网易云搜索接口 117 | 118 | v1.4.3: 新增网易云歌词接口,网易云mv播放地址获取接口 119 | 120 | v1.4.4: 新增网易云排行榜详情接口 121 | 122 | v1.4.5: 新增网易云每日推荐歌曲接口,新增QQ登录自动刷新功能 => 在 `setting.js` 设置文件中 修改 **QQ_uin** 字段 ,QQ Cookie会自动刷新(需要在Cookie共享仓库中上传自己的Cookie信息) 123 | 124 | v1.5.0: 新增五个平台的评论接口,从此版本开始,项目支持 **npm** 安装方式([npm 使用说明](https://github-zc.github.io/wp_MusicApi/#/common)) 125 | 126 | v1.5.1: 新增一个新的网易获取接口,并且可以自定义传入 cookie 127 | 128 | v1.5.2: 新增酷狗歌单导入(酷狗码),新增酷我歌单导入,新增qq获取播放链接(批量获取) 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | ## 用户须知 137 | 138 | !> 考虑到性能问题,可以使用专门服务器与本项目对接,例如`nginx`,具体的搭建方法还请用户自行百度,这里不做演示 139 | 140 | !> 该项目仅做接口转发,部分接口通过修改 `Referer` 实现,所有数据均不做存储处理,部分接口采用缓存,大家还是理性的保护好自己的个人信息,谨防诈骗 141 | 142 | !> 目前本项目刚刚开始,只提供QQ、咪咕、酷我、酷狗等音乐平台部分接口,后期再不断完善 143 | 144 | !> 本项目仅供学习使用,请尊重版权,请勿利用此项目从事商业行为 145 | 146 | 147 | 148 | ## 友情链接 149 | 150 | [拾荒者](https://blog.iecoxe.top) 151 | 152 | 153 | 154 | ## 捐赠 155 | 156 | > 关于捐赠,强求大家耐心看完 157 | 158 | 免费的东西不长久,毕竟家里没矿,生活在三次元的我总要考虑一下恰饭问题,所以有了这个赞助。你的赞助不仅会被用来支付一些开发成本:服务器、域名、付费软件等,还将帮助我奉献更多的时间到项目中。如果你正在使用我的 API,可以用赞助来表示你的谢意,并让项目保持健康稳定和得到更积极的维护 159 | 160 | 我不会忘记支持我的人,也不会把你的支持当作理所当然,它对我意义重大。 161 | 162 | 周期性赞助请联系我,你将会得到我的帮助;如果你不喜欢周期性赞助,也可以选择一次性赞助方式: 163 | 164 | ![支付宝、微信](./docs/1660899340003.jpg) 165 | 166 | 167 | 168 | ## 捐赠列表 169 | 170 | > 以下排名不分先后 171 | 172 | 特别感谢 **超 女士 173 | 174 | 感谢 **宇 先生 175 | 176 | 177 | 178 | ## 免责申明 179 | 180 | 1. 以上开发接口仅限于技术研究和项目开发练习使用,禁止商业用途,如有发现直接关闭服务 181 | 2. 音乐版权归各音乐平台所有,若有侵犯版权,请联系我删除 182 | 183 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | // 导入koa,和koa 1.x不同,在koa2中,我们导入的是一个class,因此用大写的Koa表示: 2 | const Koa = require('koa'); 3 | // 注意require('koa-router')返回的是函数: 4 | const v1 = require('./routes/v1/controllers'); 5 | const index = require('./public/controllers'); 6 | // 解析request的body的功能 7 | const koaBody = require("koa-body"); 8 | // 异常捕获 9 | const { restify } = require('./middlewares/rest'); 10 | const { validation } = require('./middlewares/auth'); 11 | 12 | const Cache = require('./util/cache'); 13 | 14 | const cors = require('koa2-cors'); 15 | 16 | // 自定义库 17 | const config = require('./setting'); 18 | const __Cookie = require('./util/cookie_util'); 19 | const { getWYCookie } = require('./l'); 20 | const { getQQCookie } = require('./refreshQQCookie'); 21 | const { default: axios } = require('axios'); 22 | 23 | 24 | // 设置qq音乐的cookie 25 | global.qq_cookie = __Cookie.parse(config.qq_cookie); 26 | global.migu_cookie = __Cookie.parse(config.migu_cookie); 27 | global.kugou_cookie = __Cookie.parse(config.kugou_cookie); 28 | global.wy_cookie = __Cookie.parse(config.wy_cookie); 29 | 30 | global.cache = new Cache(); 31 | 32 | 33 | setInterval(() => { 34 | global.cache.clear(); 35 | }, 3000); 36 | 37 | //更新网易云cookie 38 | // setTimeout(() => { 39 | // getWYCookie(); 40 | // }, 10 * 1000); 41 | 42 | setInterval(() => { 43 | getWYCookie(); 44 | }, 1000 * 60 * 60 * 24); 45 | 46 | // 更新QQcookie 47 | setTimeout(() => { 48 | getQQCookie(config.QQ_uin); 49 | }, 10 * 1000); 50 | 51 | setInterval(() => { 52 | getQQCookie(config.QQ_uin); 53 | }, 1000 * 60 * 60); 54 | 55 | // 创建一个Koa对象表示web app本身: 56 | const app = new Koa(); 57 | 58 | app.use(cors()); 59 | 60 | // 对于任何请求,app将调用该异步函数处理请求: 61 | app.use(async (ctx, next) => { 62 | const start = new Date().getTime(); // 当前时间 63 | try { 64 | await next(); // 调用下一个middleware 65 | const ms = new Date().getTime() - start; // 耗费时间 66 | console.log(`${ctx.request.method} ${ctx.request.url} ${ctx.status} Time: ${ms}ms`); // 打印耗费时间 67 | } catch (e) { 68 | const ms = new Date().getTime() - start; // 耗费时间 69 | console.log(`${ctx.request.method} ${ctx.request.url} ${ctx.status} Time: ${ms}ms`); // 打印耗费时间 70 | ctx.response.status = 400; 71 | ctx.response.type = 'application/json'; 72 | ctx.response.body = { 73 | code: e.code || 'internal:unknown_error', 74 | message: e.message || '' 75 | }; 76 | } 77 | }); 78 | 79 | // 异常捕获 80 | app.use(restify()); 81 | app.use(validation()); 82 | 83 | // add bodyparse middleware 84 | app.use(koaBody({ multipart: true })); 85 | // add router middleware: 86 | app.use(v1.routes()); 87 | app.use(index.routes()); 88 | 89 | 90 | // 查询当前版本号 91 | axios.get(`https://github-zc.github.io/wp_MusicApi/version.json`, { 92 | timeout: 3000 93 | }).then(res => { 94 | if (res.data.version !== config.version) { 95 | console.log(`最新版本: ${res.data.version}, 当前版本: ${config.version}, 请及时更新`); 96 | } else if (res.data.version === config.version) { 97 | console.log(`最新版本: ${res.data.version}, 当前版本: ${config.version}, 无需更新`); 98 | } 99 | }).catch(err => { 100 | console.log('版本检验错误,请检查网络或者联系作者'); 101 | }).finally(() => { 102 | // 在端口3000监听: 103 | app.listen(config.port, () => { 104 | console.log('app started at url http://localhost:5000 ...'); 105 | }); 106 | }) -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitHub-ZC/wp_MusicApi/bf9307dd138dc8ac6c4f7de29361209d4f5b665f/docs/.nojekyll -------------------------------------------------------------------------------- /docs/1660899340003.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitHub-ZC/wp_MusicApi/bf9307dd138dc8ac6c4f7de29361209d4f5b665f/docs/1660899340003.jpg -------------------------------------------------------------------------------- /docs/KG.md: -------------------------------------------------------------------------------- 1 | # 酷狗音乐 2 | 3 | ## 获取音乐播放地址(图片,歌名,作者) 4 | 5 | 请求地址:`/v1/kugou/song` 6 | 7 | 请求示例:`http://iecoxe.top:5000/v1/kugou/song?aid=156483846` 8 | 9 | | 参数 | 是否必须 | 接口说明 | 默认值 | 10 | | ---- | -------- | ---------- | -------------------------------- | 11 | | aid | √ | album_id | 966846 | 12 | | hash | √ | hash_value | 03571660BC0BD02FAA5994E19F97A005 | 13 | 14 | 说明:`如果只传入 **hash_value** ,可以只获取(图片,歌名,作者)` 15 | 16 | ​ `两个参数都正确传入,能够获取歌词和播放地址` 17 | 18 | ​ `如果想要获取会员歌曲,请自行传入COOKIE` 19 | 20 | 21 | 22 | 23 | 24 | ## 获取音乐播放地址 25 | 26 | 请求地址:`/v1/kugou/getsong` 27 | 28 | 请求示例:`http://iecoxe.top:5000/v1/kugou/getsong?aid=156483846` 29 | 30 | | 参数 | 是否必须 | 接口说明 | 默认值 | 31 | | ---- | -------- | ---------- | -------------------------------- | 32 | | aid | √ | album_id | 966846 | 33 | | hash | √ | hash_value | 03571660BC0BD02FAA5994E19F97A005 | 34 | 35 | 说明:不带cookie信息的接口,只能获取普通歌曲 36 | 37 | 38 | 39 | ## 获取音乐歌词(恢复) 40 | 41 | 请求地址:`/v1/kugou/lyric` 42 | 43 | 请求示例:`http://iecoxe.top:5000/v1/kugou/lyric?hash=2FF4014692AC079A9B8118966C891897` 44 | 45 | | 参数 | 是否必须 | 接口说明 | 默认值 | 46 | | ---- | -------- | -------- | -------------------------------- | 47 | | hash | √ | 音乐 ID | 2FF4014692AC079A9B8118966C891897 | 48 | 49 | 50 | 51 | ## 搜索(建议) 52 | 53 | 请求地址:`/v1/kugou/search` 54 | 55 | 请求示例:`http://iecoxe.top:5000/v1/kugou/search?key=周杰伦` 56 | 57 | | 参数 | 是否必须 | 接口说明 | 默认值 | 58 | | ------ | -------- | -------------- | -------- | 59 | | key | √ | 关键字 | 默认为空 | 60 | | limit | × | 搜索结果的数量 | 30 | 61 | | offset | × | 数据的偏移量 | 1 | 62 | 63 | 说明:`offset相当于页数,1,2,3,… 不需要 乘以 limit` 64 | 65 | 66 | 67 | ## 搜索 68 | 69 | 请求地址:`/v1/kugou/mobileSearch` 70 | 71 | 请求示例:`http://iecoxe.top:5000/v1/kugou/mobileSearch?key=周杰伦` 72 | 73 | | 参数 | 是否必须 | 接口说明 | 默认值 | 74 | | ------ | -------- | -------------- | -------- | 75 | | key | √ | 关键字 | 默认为空 | 76 | | limit | × | 搜索结果的数量 | 30 | 77 | | offset | × | 数据的偏移量 | 1 | 78 | 79 | 说明:`offset相当于页数,1,2,3,… 不需要 乘以 limit` 80 | 81 | 82 | 83 | ## 热门搜索 84 | 85 | 请求地址:`/v1/kugou/hotSearch` 86 | 87 | 请求示例:`http://iecoxe.top:5000/v1/kugou/hotSearch` 88 | 89 | | 参数 | 是否必须 | 接口说明 | 默认值 | 90 | | ---- | -------- | ------------------- | ------ | 91 | | from | × | from填`pc` 或 `web` | `pc` | 92 | 93 | 说明:`pc`返回的数据比较详细 94 | 95 | 96 | 97 | ## 搜索建议 98 | 99 | 请求地址:`/v1/kugou/suggestSearch` 100 | 101 | 请求示例:`http://iecoxe.top:5000/v1/kugou/suggestSearch?key=周` 102 | 103 | | 参数 | 是否必须 | 接口说明 | 默认值 | 104 | | ---- | -------- | -------- | -------- | 105 | | key | √ | 关键字 | 默认为空 | 106 | 107 | 108 | 109 | ## 获取排行榜 110 | 111 | 请求地址:`/v1/kugou/top` 112 | 113 | 请求示例:`http://iecoxe.top:5000/v1/kugou/top?topId=8888` 114 | 115 | | 参数 | 是否必须 | 接口说明 | 默认值 | 116 | | -------- | -------- | ---------- | ------ | 117 | | topId | √ | 排行榜 ID | 8888 | 118 | | offset | × | 偏移的页数 | 1 | 119 | | platform | × | 平台选择 | web | 120 | 121 | 说明:`默认每一页返回 **30** 首歌曲 **固定**` 122 | 123 | ​ ` platform 选择 pc 可以获取客户端酷狗排行榜数据, 建议大家使用这个` 124 | 125 | ## 获取排行榜分类信息 126 | 127 | 请求地址:`/v1/kugou/topCategory` 128 | 129 | 请求示例:`http://iecoxe.top:5000/v1/kugou/topCategory` 130 | 131 | | 参数 | 是否必须 | 接口说明 | 默认值 | 132 | | ---- | -------- | -------- | ------ | 133 | | 无 | | | | 134 | 135 | 说明:此次更新后端剔除了一些不可使用的排行榜榜单 136 | 137 | 138 | 139 | 140 | 141 | ## 获取评论 142 | 143 | 请求地址:`/v1/kugou/comment` 144 | 145 | 请求示例:`http://iecoxe.top:5000/v1/kugou/comment?hash=515002B2A4F27CC4C0911CE898335A17&limit=20&offset=1&type=0` 146 | 147 | | 参数 | 是否必须 | 接口说明 | 默认值 | 148 | | ------ | -------- | ---------------------------- | ------ | 149 | | hash | √ | 歌曲hash值 | 无 | 150 | | offset | × | 偏移量 | 1 | 151 | | limit | × | 返回数据量,最大为40 | 30 | 152 | | type | × | 0:获取最新评论,1:获取热评 | 0 | 153 | 154 | 返回结果说明: 155 | 156 | `reply_num` 表示这条评论的回复数量; 157 | 158 | `user_pic` 表示这条评论的用户头像; 159 | 160 | `images` 表示评论中发布的图片,有的评论中会有图片+文字; 161 | 162 | 163 | 164 | 165 | 166 | ## 获取回复评论 167 | 168 | 请求地址:`/v1/kugou/replyComment` 169 | 170 | 请求示例:`http://iecoxe.top:5000/v1/kugou/replyComment?special_child_id=20505418&id=500228896` 171 | 172 | | 参数 | 是否必须 | 接口说明 | 默认值 | 173 | | ---------------- | -------- | -------------------------- | ------ | 174 | | special_child_id | √ | 主评论中的special_child_id | 无 | 175 | | offset | × | 偏移量 | 1 | 176 | | limit | × | 返回数据量,最大为50 | 30 | 177 | | id | √ | 主评论中的id | 无 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | ## 获取歌单标签下的歌单信息 186 | 187 | 请求地址:`/v1/kugou/playlist/tag` 188 | 189 | 请求示例:`http://iecoxe.top:5000/v1/kugou/playlist/tag` 190 | 191 | | 参数 | 是否必须 | 接口说明 | 默认值 | 192 | | ------ | -------- | ------------ | ------ | 193 | | tagid | √ | 歌单标签 ID | | 194 | | sortId | × | 返回歌曲数量 | 5 | 195 | | offset | × | 数据偏移页数 | 1 | 196 | 197 | 说明:`每一页返回 **20** 数据(歌单数量)` 198 | 199 | ```json 200 | // sortId参数相关说明 201 | { 202 | name: '推荐', 203 | sortId: '5', 204 | }, 205 | { 206 | name: '最热', 207 | sortId: '6', 208 | }, 209 | { 210 | name: '最新', 211 | sortId: '7', 212 | }, 213 | { 214 | name: '热藏', 215 | sortId: '3', 216 | }, 217 | { 218 | name: '飙升', 219 | sortId: '8', 220 | } 221 | ``` 222 | 223 | 224 | 225 | ## 获取歌单标签分类 226 | 227 | 请求地址:`/v1/kugou/playlist/tagCategory` 228 | 229 | 请求示例:`http://iecoxe.top:5000/v1/kugou/playlist/tagCategory` 230 | 231 | | 参数 | 是否必须 | 接口说明 | 默认值 | 232 | | ---- | -------- | -------- | ------ | 233 | | 无 | | | | 234 | 235 | 236 | 237 | ## (新增)获取歌单广场 238 | 239 | 请求地址:`/v1/kugou/playlist/list` 240 | 241 | 请求示例:`http://iecoxe.top:5000/v1/kugou/playlist/list` 242 | 243 | | 参数 | 是否必须 | 接口说明 | 默认值 | 244 | | ------ | -------- | -------------- | ------ | 245 | | offset | × | 返回的偏移页数 | 1 | 246 | 247 | 说明:`返回歌单广场,可以用来编写客户端的歌单广场` 248 | 249 | 250 | 251 | 252 | 253 | ## 获取歌单歌曲列表 254 | 255 | 请求地址:`/v1/kugou/playlist/info` 256 | 257 | 请求示例:`http://iecoxe.top:5000/v1/kugou/playlist/info` 258 | 259 | | 参数 | 是否必须 | 接口说明 | 默认值 | 260 | | ---- | -------- | -------- | ------- | 261 | | pid | √ | 歌单 ID | 3233449 | 262 | 263 | 说明:`返回歌单中所有的歌曲信息` 264 | 265 | 266 | 267 | 268 | 269 | ## 导入自建歌单歌曲(新增) 270 | 271 | 请求地址:`/v1/kugou/playlist/import` 272 | 273 | 请求示例:`http://iecoxe.top:5000/v1/kugou/playlist/import?id=4129905` 274 | 275 | | 参数 | 是否必须 | 接口说明 | 默认值 | 276 | | ---- | -------- | -------- | ------ | 277 | | id | √ | 酷狗码 | | 278 | 279 | 说明:`通过 酷狗码 导入自建歌单 我喜欢 等等, 目前只能导入最大歌曲数目 500 首` 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | ## Cookie 288 | 289 | 290 | 291 | ### 设置服务器Cookie(POST) 292 | 293 | 接口:`/v1/kugou/setcookie/` 294 | 295 | 说明:具有 酷狗会员的用户,通过浏览器查看 Cookie ,通过此接口设置以后,需要登陆的接口可以获取登录才能获取的内容,比如 歌曲播放地址(无损) 296 | 297 | 必须参数: 298 | 299 | `data`: 300 | 301 | > 格式:aaa=bbb; ccc=ddd; .... 302 | 303 | > 例子:yqq_stat=0; pgv_info=ssid=s1520392; ts_last=y.qq.com/portal/player.html; pgv_pvid=1233495754; ts_uid=5486601780; pgv_pvi=4007713792; pgv_si=s6654436352; userAction=1; _qpsvr_localtk=0.8025676546673662; yq_index=0; yplayer_open=1; player_exist=1; qqmusic_fromtag=66 304 | 305 | 306 | 307 | ### 查看当前Cookie 308 | 309 | 接口:`/v1/kugou/getcookie/` 310 | 311 | 说明:cookie 已经在服务器端,进行的 Json 转化 312 | 313 | 示例:[/v1/kugou/getcookie/](http://iecoxe.top:5000/v1/kugou/getcookie/) 314 | 315 | -------------------------------------------------------------------------------- /docs/KW.md: -------------------------------------------------------------------------------- 1 | # 酷我音乐 2 | 3 | ## 获取音乐播放地址 4 | 5 | 请求地址:`/v1/kuwo/song` 6 | 7 | 请求示例:`http://iecoxe.top:5000/v1/kuwo/song?rid=156483846` 8 | 9 | | 参数 | 是否必须 | 接口说明 | 默认值 | 10 | | ---- | -------- | -------- | --------- | 11 | | rid | √ | 音乐 ID | 156483846 | 12 | | br | × | 音质 | 320 | 13 | 14 | 说明:`br 24 48 96 128 192 320 1000` `注: br=1000 获取 ape 无损格式` 15 | 16 | 17 | 18 | flac音质获取: 此服务单独部署再5500端口,此接口不在 wp_MusicAPI 接口中 19 | 20 | 请求地址:`/` 21 | 22 | flac音质请求示例:`http://iecoxe.top:5500?rid=156483846&br=2000` 23 | 24 | | 参数 | 是否必须 | 接口说明 | 默认值 | 25 | | ---- | -------- | -------- | --------- | 26 | | rid | √ | 音乐 ID | 156483846 | 27 | | br | × | 音质 | 无 | 28 | 29 | 30 | 31 | 32 | 33 | ## 获取音乐详情 34 | 35 | 请求地址:`/v1/kuwo/songInfo` 36 | 37 | 请求示例:`http://iecoxe.top:5000/v1/kuwo/songInfo?rid=156483846` 38 | 39 | | 参数 | 是否必须 | 接口说明 | 默认值 | 40 | | ---- | -------- | -------- | --------- | 41 | | rid | √ | 音乐 ID | 156483846 | 42 | 43 | 44 | 45 | 46 | 47 | ## 获取音乐歌词 48 | 49 | 请求地址:`/v1/kuwo/lyric` 50 | 51 | 请求示例:`http://iecoxe.top:5000/v1/kuwo/lyric?rid=156483846` 52 | 53 | | 参数 | 是否必须 | 接口说明 | 默认值 | 54 | | ---- | -------- | ------------- | --------- | 55 | | rid | √ | 音乐 ID | 156483846 | 56 | | from | × | `pc` 或 `web` | `pc` | 57 | 58 | 59 | 60 | ## 搜索 61 | 62 | 请求地址:`/v1/kuwo/search` 63 | 64 | 请求示例:`http://iecoxe.top:5000/v1/kuwo/search?key=周杰伦` 65 | 66 | | 参数 | 是否必须 | 接口说明 | 默认值 | 67 | | ------ | -------- | -------------- | -------- | 68 | | key | √ | 关键字 | 默认为空 | 69 | | limit | × | 搜索结果的数量 | 30 | 70 | | offset | × | 数据的偏移量 | 1 | 71 | | from | × | `pc` 或 `web` | web | 72 | 73 | 说明:`offset相当于页数,1,2,3,… 不需要 乘以 limit` 74 | 75 | 注意:from=pc,理论上讲更加稳定点,from=web,比较容易请求失败 76 | 77 | 78 | 79 | ## 热门搜索 80 | 81 | 请求地址:`/v1/kuwo/hotSearch` 82 | 83 | 请求示例:`http://iecoxe.top:5000/v1/kuwo/hotSearch` 84 | 85 | | 参数 | 是否必须 | 接口说明 | 默认值 | 86 | | ---- | -------- | -------------------- | ------ | 87 | | from | × | from填 `pc` 或 `web` | `pc` | 88 | 89 | 说明:`pc`返回的数据量比较详细 90 | 91 | 92 | 93 | ## 搜索建议 94 | 95 | 请求地址:`/v1/kuwo/suggestSearch` 96 | 97 | 请求示例:`http://iecoxe.top:5000/v1/kuwo/suggestSearch?key=周` 98 | 99 | | 参数 | 是否必须 | 接口说明 | 默认值 | 100 | | ---- | -------- | -------- | -------- | 101 | | key | √ | 关键字 | 默认为空 | 102 | 103 | 104 | 105 | 106 | 107 | ## 获取评论 108 | 109 | 请求地址:`/v1/kuwo/comment` 110 | 111 | 请求示例:`http://iecoxe.top:5000/v1/kuwo/comment?id=156483846&type=1&limit=50` 112 | 113 | | 参数 | 是否必须 | 接口说明 | 默认值 | 114 | | ------ | -------- | ---------------------------- | ------ | 115 | | id | √ | rid 或 DC_TARGETID | 无 | 116 | | offset | × | 偏移量 | 1 | 117 | | limit | × | 返回数据量 | 30 | 118 | | type | × | 0:获取最新评论,1:获取热评 | 0 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | ## 获取排行榜 129 | 130 | 请求地址:`/v1/kuwo/top` 131 | 132 | 请求示例:`http://iecoxe.top:5000/v1/kuwo/top?topId=16` 133 | 134 | | 参数 | 是否必须 | 接口说明 | 默认值 | 135 | | ------ | -------- | -------------- | ------ | 136 | | topId | √ | 排行榜 ID | 16 | 137 | | limit | × | 返回歌曲的数量 | 30 | 138 | | offset | × | 偏移的页数 | 1 | 139 | | from | × | `pc` 或 `web` | web | 140 | 141 | 注意:from=pc,理论上讲更加稳定点,from=web,比较容易请求失败 142 | 143 | 144 | 145 | ## 获取排行榜分类信息 146 | 147 | 请求地址:`/v1/kuwo/topCategory` 148 | 149 | 请求示例:`http://iecoxe.top:5000/v1/kuwo/topCategory` 150 | 151 | | 参数 | 是否必须 | 接口说明 | 默认值 | 152 | | ---- | -------- | -------------------- | ------ | 153 | | from | × | from填 `pc` 或 `web` | `pc` | 154 | 155 | 说明:`pc`返回的数据量少,但是默认`官方榜`提供前三首歌曲的信息 156 | 157 | ​ `web`返回的数据量多,但是所有榜单都不提供前三首歌曲的信息 158 | 159 | 160 | 161 | ## 获取歌单标签下的歌单信息 162 | 163 | 请求地址:`/v1/kuwo/playlist/tag` 164 | 165 | 请求示例:`http://iecoxe.top:5000/v1/kuwo/playlist/tag` 166 | 167 | | 参数 | 是否必须 | 接口说明 | 默认值 | 168 | | ------ | -------- | --------------------------------------------------------- | ------ | 169 | | id | √ | 歌单标签 ID | 146 | 170 | | flag | × | 标志参数 只能传入 `0/1` | 0 | 171 | | order | × | flag为 `1` 时,只能传入 `hot/new` ,反之不需要传入此参数 | hot | 172 | | limit | × | 返回歌曲数量 | 30 | 173 | | offset | × | 数据偏移页数 | 1 | 174 | 175 | 176 | 177 | ## 获取歌单标签分类 178 | 179 | 请求地址:`/v1/kuwo/playlist/tagCategory` 180 | 181 | 请求示例:`http://iecoxe.top:5000/v1/kuwo/playlist/tagCategory` 182 | 183 | | 参数 | 是否必须 | 接口说明 | 默认值 | 184 | | ---- | -------- | -------- | ------ | 185 | | 无 | | | | 186 | 187 | 188 | 189 | ## 获取歌单歌曲列表 190 | 191 | 请求地址:`/v1/kuwo/playlist/info` 192 | 193 | 请求示例:`http://iecoxe.top:5000/v1/kuwo/playlist/info?pid=3127794871` 194 | 195 | | 参数 | 是否必须 | 接口说明 | 默认值 | 196 | | ------ | -------- | ------------ | ---------- | 197 | | pid | √ | 歌单 ID | 3127794871 | 198 | | limit | × | 返回歌曲数量 | 30 | 199 | | offset | × | 数据偏移页数 | 1 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | ## 导入自建歌单歌曲(新增) 208 | 209 | 请求地址:`/v1/kuwo/playlist/import` 210 | 211 | 请求示例:`http://iecoxe.top:5000/v1/kuwo/playlist/import?id=3420768986` 212 | 213 | | 参数 | 是否必须 | 接口说明 | 默认值 | 214 | | ---- | -------- | -------- | ------ | 215 | | id | √ | 酷狗码 | | 216 | 217 | 说明:`通过分享链接中的 导入自建歌单 我喜欢 等等` 218 | 219 | 示例:`https://m.kuwo.cn/h5app/playlist/3420768986?t=plantform` 中的 **3420768986** 220 | 221 | -------------------------------------------------------------------------------- /docs/MG.md: -------------------------------------------------------------------------------- 1 | # 咪咕音乐 2 | 3 | ## 搜索 4 | 5 | ### 搜索 6 | 7 | 接口:`/v1/migu/search` 8 | 9 | 可选参数: 10 | 11 | `key`:关键字 默认 暗号 12 | 13 | `limit`:每一页返回的数量,默认30 14 | 15 | `offset`:页码,默认 1 16 | 17 | `type`:默认 2 ; // 歌曲: 2 歌手:1 专辑: 4 歌单:6 MV:5 歌词:7 18 | 19 | 说明:调用此接口 , 传入搜索关键词可以搜索该音乐 / 专辑 / 歌手 / 歌单 / 用户(需要自己传入type参数) , 默认会自动去除 关键词 前后的**空白字符** 20 | 21 | 示例:[/v1/migu/search?key=晴天](http://iecoxe.top:5000/v1/migu/search?key=晴天) 22 | 23 | 24 | 25 | ### 热搜 26 | 27 | 接口:`/v1/migu/hotSearch` 28 | 29 | 说明:调用此接口,默认会进行缓存处理 30 | 31 | 示例:[/v1/migu/hotSearch](http://iecoxe.top:5000/v1/migu/hotSearch) 32 | 33 | 34 | 35 | ### 搜索建议 36 | 37 | 接口:`/v1/migu/suggestSearch?key=周杰伦` 38 | 39 | 必选参数:`key` 40 | 41 | 示例:[/v1/migu/suggestSearch?key=周杰伦](http://iecoxe.top:5000/v1/migu/suggestSearch?key=周杰伦) 42 | 43 | 44 | 45 | ## 歌曲url 46 | 47 | 接口:`/v1/migu/song` 48 | 49 | 说明: 50 | 51 | - 由于目前时间紧促,目前此接口只能获取**128k**歌曲 52 | - migu没有采取 Cookie ,本人没有账号 53 | - 服务器会自动去除 id 以及之间的**空白字符** 54 | 55 | 可选参数: 56 | 57 | `cid`:歌曲的`copyrightId`,默认`60054701923` 58 | `br`:1: 普通 2: 高品质 3: 无损 4: 高无损,默认`1` 59 | 60 | 示例:[/v1/migu/song?cid=60054701923](http://iecoxe.top:5000/v1/migu/song?cid=60054701923&br=3) 61 | 62 | 63 | 64 | ## 歌词 65 | 66 | 接口:`/v1/migu/lyric` 67 | 68 | 可选参数:`cid` 歌曲的copyrightId 默认 `60084600554` 69 | 70 | 示例:[/v1/migu/lyric?cid=60084600554](http://iecoxe.top:5000/v1/migu/lyric?cid=60084600554) 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | ## 获取评论 79 | 80 | 请求地址:`/v1/migu/comment` 81 | 82 | 请求示例:`http://iecoxe.top:5000/v1/migu/comment?id=2643&type=1&limit=30` 83 | 84 | | 参数 | 是否必须 | 接口说明 | 默认值 | 85 | | ------ | -------- | ---------------------------- | ------ | 86 | | hash | √ | 歌曲hash值 | 无 | 87 | | offset | × | 偏移量 | 1 | 88 | | limit | × | 返回数据量,最大为50 | 30 | 89 | | type | × | 0:获取最新评论,1:获取热评 | 0 | 90 | 91 | 92 | 93 | 94 | 95 | ## 获取回复评论 96 | 97 | 请求地址:`/v1/kugou/replyComment` 98 | 99 | 请求示例:`http://iecoxe.top:5000/v1/migu/replyComment?commentId=1000000000000007203662` 100 | 101 | | 参数 | 是否必须 | 接口说明 | 默认值 | 102 | | --------- | -------- | -------------------- | ------ | 103 | | commentId | √ | 主评论中的commentId | 无 | 104 | | offset | × | 偏移量 | 1 | 105 | | limit | × | 返回数据量,最大为50 | 30 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | ## 排行榜 116 | 117 | ### 获取榜单列表 118 | 119 | 接口:`/v1/migu/topCategory` 120 | 121 | 说明:由于自我认为官方的 ajax 接口返回的数据太少,以及接口不健全,于是自己通过爬虫爬取获取的排行榜数据 122 | 123 | 说明:最新版本接口已经更新此接口数据,为了兼容移动端(2021-2-21更新) 124 | 125 | 示例:[/v1/migu/topCategory](http://iecoxe.top:5000/v1/migu/topCategory) 126 | 127 | 128 | 129 | ### 获取榜单详情 130 | 131 | 接口:`/v1/migu/top` 132 | 133 | 说明:由于自我认为官方的 ajax 接口返回的数据太少,以及接口不健全,于是自己通过爬虫爬取获取的排行榜数据 134 | 135 | 可选参数:`topId` 默认27553319,新歌榜 136 | 137 | 说明:`topId`参数通过`/v1/migu/topCategory`接口获取 138 | 139 | 示例:[/v1/migu/top?topId=27553319](http://iecoxe.top:5000/v1/migu/top?topId=27553319) 140 | 141 | 142 | 143 | ## 歌手 144 | 145 | ### 歌手详情 146 | 147 | 接口:`/v1/migu/singer/info` 148 | 149 | 可选参数: 150 | 151 | `artistId`:歌手ID ,默认 18196 152 | 153 | 示例:[/v1/migu/singer/info?artistId=18196](http://iecoxe.top:5000/v1/migu/singer/info?artistId=18196) 154 | 155 | 156 | 157 | ### 歌手歌曲列表 158 | 159 | 接口:`/v1/migu/singer/songList/` 160 | 161 | 说明:由于自我认为官方的 ajax 接口返回的数据太少,以及接口不健全,于是自己通过爬虫爬取获取的歌手歌曲列表数据 162 | 163 | 可选参数: 164 | 165 | `artistId`:歌手ID,默认 18196 166 | 167 | `offset`:分页,默认 1 168 | 169 | 示例:[/v1/migu/singer/songList/?artistId=18196&offset=1](http://iecoxe.top:5000/v1/migu/singer/songList/?artistId=18196&offset=1) 170 | 171 | 172 | 173 | ## 歌单 174 | 175 | ### 歌单(无效接口) 176 | 177 | 接口:`/v1/migu/playlist/` 178 | 179 | 说明:由于官方接口原因,默认每一页返回 10 条数据 180 | 181 | 可选参数: 182 | 183 | `offset`:分页, 默认 1 184 | 185 | `type`:1: 推荐 ; 2: 最新, 默认 推荐 186 | 187 | 示例:[/v1/migu/playlist/](http://iecoxe.top:5000/v1/migu/playlist/) 188 | 189 | 返回字段含义:`contentCount` 歌单 歌曲 的总数量,可用于 下面接口 中 `limit` 参数 190 | 191 | 192 | 193 | ### 歌单详情 194 | 195 | 接口:`/v1/migu/playlist/info/` 196 | 197 | 说明:`playListId`、`limit` 根据上面的接口( `/v1/migu/playlist/` )返回数据中获取 198 | 199 | ​ `limit` 对应上面接口中的 `contentCount` 200 | 201 | 可选参数: 202 | 203 | `playListId`:歌单的 ID,默认 179730639 204 | 205 | `limit`:返回数据的数量,默认 30 206 | 207 | 示例:[/v1/migu/playlist/info/](http://iecoxe.top:5000/v1/migu/playlist/info/) 208 | 209 | 210 | 211 | ## Cookie 212 | 213 | 214 | 215 | ### 设置服务器Cookie(POST) 216 | 217 | 接口:`/v1/migu/setcookie/` 218 | 219 | 说明:咪咕突然需要登陆了,通过浏览器查看 Cookie ,通过此接口设置以后,需要登陆的接口可以获取登录才能获取的内容,比如 歌曲播放地址(无损) **暂时不需要** 220 | 221 | 必须参数: 222 | 223 | `data`: 224 | 225 | > 格式:aaa=bbb; ccc=ddd; .... 226 | 227 | > 例子:yqq_stat=0; pgv_info=ssid=s1520392; ts_last=y.qq.com/portal/player.html; pgv_pvid=1233495754; ts_uid=5486601780; pgv_pvi=4007713792; pgv_si=s6654436352; userAction=1; _qpsvr_localtk=0.8025676546673662; yq_index=0; yplayer_open=1; player_exist=1; qqmusic_fromtag=66 228 | 229 | 230 | 231 | ### 查看当前Cookie 232 | 233 | 接口:`/v1/migu/getcookie/` 234 | 235 | 说明:cookie 已经在服务器端,进行的 Json 转化 236 | 237 | 示例:[/v1/migu/getcookie/](http://iecoxe.top:5000/v1/migu/getcookie/) 238 | 239 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | > wp_MusicApi 一个荒野拾荒者的 API 2 | 3 | 4 | 5 | ![](https://img.shields.io/badge/最新版本-v1.5.2-green) 6 | 7 | 8 | [![](https://img.shields.io/badge/QQ群1-922193759-green)](https://jq.qq.com/?_wv=1027&k=3USa76OC) 9 | 10 | 11 | 12 | [点击加入QQ群](https://jq.qq.com/?_wv=1027&k=3USa76OC) 13 | 14 | ## 公告 15 | 16 | 文档地址:[文档](https://github-zc.github.io/wp_MusicApi/) 17 | 18 | 网易云接口:[点击跳转](https://binaryify.github.io/NeteaseCloudMusicApi/) (服务器地址:http://iecoxe.top:3000 ) 19 | 20 | 个人博客CSDN:[CSDN地址](https://blog.csdn.net/weixin_44358443?type=blog) 21 | 22 | 基于本接口开发的软件: 23 | 24 | [wp music Android版](https://blog.iecoxe.top/2021/03/07/7/) 25 | 26 | [wp music 电脑版](https://github.com/GitHub-ZC/Wp_music/releases) 27 | 28 | 29 | 30 | 欢迎大家关注**微信公众号**,后续会慢慢更新优质资源 31 | 32 | ![请关注一下公众号,每天有精彩资源推送](.\请关注一下公众号,每天有精彩资源推送.jpg) 33 | 34 | ## 新版特性 35 | 36 | 1. 相对更加稳定,服务器不再过滤参数内容,用户自行解析参数,减少参数解析出错几率 37 | 2. 支持平台:QQ 音乐,酷狗音乐,酷我音乐,咪咕音乐,网易,具体音乐音质支持往下看 38 | 3. 接口更加丰富,增加部分接口和平台 39 | 4. 降低门槛,参数简单统一,支持 GET 请求,支持跨域调用 40 | 41 | 42 | 43 | ## 商务合作 44 | 45 | > 有意向 程序开发、爬虫、脚本开发、程序部署 等等 46 | 47 | > 同此项目相关开发,二次开发 48 | 49 | > 请联系 **QQ 153140965** (加好友请备注程序开发) 50 | 51 | 52 | 53 | ## 项目安装与使用(新增 npm 安装方式) 54 | 55 | [wp_MusicApi 安装使用说明](https://github-zc.github.io/wp_MusicApi/#/common) 56 | 57 | 58 | 59 | ## Cookie 共享仓库使用说明 60 | 61 | 最近新写的接口,单独部署,主要实现 QQ音乐 Cookie 自动刷新 62 | 63 | 64 | 65 | > 接口:(GET) http://42.192.118.65:5100/qq/getCookie 66 | 67 | 示例:http://42.192.118.65:5100/qq/getCookie?uin=123456789 68 | 69 | 参数:uin => QQ号 70 | 71 | 注意:获取前提是 你上传过(通过/qq/setCookie接口) 登录成功的 QQ音乐 Cookie 字符串 72 | 73 | 74 | 75 | > 接口:(POST) http://42.192.118.65:5100/qq/setCookie 76 | 77 | 参数:data => QQ音乐 Cookie 字符串格式 78 | 79 | 功能:上传的Cookie会自动刷新登录,通过(/qq/getCookie)获取的是刷新过后的Cookie 80 | 81 | 注意:请上传登录成功的 QQ音乐 Cookie 字符串格式(浏览器 F12 获取),请使用 `x-www-form-urlencoded` 格式提交 82 | 83 | 84 | 85 | **项目介绍**:此项目只需要在 `setting.js` 项目设置文件中,修改 `QQ_uin` 字段的值(请填入QQ号,如果需要获取VIP歌曲,请传入带绿钻的QQ号,**前提是先将登陆过的Cookie传入共享仓库**),就能实现Cookie的自动刷新 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | ## 工作原理 94 | 95 | 跨站请求伪造 (CSRF), 伪造请求头 , 调用官方 API 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | ## 关于项目 104 | 105 | > wp_MusicApi 拾荒者的音乐接口 106 | 107 | v1.0:项目开始 108 | 109 | v1.1:新建`cache_redis`分支,主要用于服务端缓存,默认两分钟,需要的请自行clone 110 | 111 | v1.2:新增的咪咕、酷我、QQ的热搜参数,新增`pc`和`web`,更正了文档的已知错误,修改了咪咕和酷我的排行榜数据内容格式,以前使用本API的请**注意** 112 | 113 | v1.3: 维护所有失效接口,新增网易播放接口以及Cookie上传,新增咪咕高无损,新增酷狗无损以及Cookie上传,新增QQ登录,新增缓存,默认缓存5分钟,更新文档,修改了咪咕排行榜的数据格式,修改了QQ搜索的数据格式, 具体请自行研究。 114 | 115 | v1.3.1: 新增 首页导航,提示项目正在运行,新增版本检测提示 116 | 117 | v1.3.2: 更新酷狗搜索,改走客户端接口,更改版本检测timeout时间为3秒 118 | 119 | v1.3.3: 新增酷狗 mobileSearch 接口 120 | 121 | v1.3.4: 更新 酷狗排行榜详情 接口 122 | 123 | v1.3.5: 维护部分接口 124 | 125 | v1.4: 新增QQ扫码登录,本次采用的是基于Android端QQ音乐的身份令牌,登陆时间会保证更长时间 126 | 127 | v1.4.1: 去除QQ基于Android端QQ音乐的扫码登录,新增接口加密,默认关闭,需要打开可以在 setting.js 中打开,新增网易云歌单歌曲获取,新增网易云无损音乐 128 | 129 | v1.4.2: 新增网易云搜索接口 130 | 131 | v1.4.3: 新增网易云歌词接口,网易云mv播放地址获取接口 132 | 133 | v1.4.4: 新增网易云排行榜详情接口 134 | 135 | v1.4.5: 新增网易云每日推荐歌曲接口,新增QQ登录自动刷新功能 => 在 `setting.js` 设置文件中 修改 **QQ_uin** 字段 ,QQ Cookie会自动刷新(需要在Cookie共享仓库中上传自己的Cookie信息) 136 | 137 | v1.5.0: 新增五个平台的评论接口,从此版本开始,项目支持 **npm** 安装方式([npm 使用说明](https://github-zc.github.io/wp_MusicApi/#/common)) 138 | 139 | v1.5.1: 新增一个新的网易获取接口,并且可以自定义传入 cookie 140 | 141 | v1.5.2: 新增酷狗歌单导入(酷狗码),新增酷我歌单导入,新增qq获取播放链接(批量获取) 142 | 143 | 144 | 145 | 146 | 147 | ## 用户须知 148 | 149 | !> 考虑到性能问题,可以使用专门服务器与本项目对接,例如`nginx`,具体的搭建方法还请用户自行百度,这里不做演示 150 | 151 | !> 该项目仅做接口转发,部分接口通过修改 `Referer` 实现,所有数据均不做存储处理,部分接口采用缓存,大家还是理性的保护好自己的个人信息,谨防诈骗 152 | 153 | !> 目前本项目刚刚开始,只提供QQ、咪咕、酷我、酷狗等音乐平台部分接口,后期再不断完善 154 | 155 | !> 本项目仅供学习使用,请尊重版权,请勿利用此项目从事商业行为 156 | 157 | 158 | 159 | ## 友情链接 160 | 161 | [拾荒者](https://blog.iecoxe.top) 162 | 163 | 164 | 165 | ## 捐赠 166 | 167 | > 关于捐赠,强求大家耐心看完 168 | 169 | 免费的东西不长久,毕竟家里没矿,生活在三次元的我总要考虑一下恰饭问题,所以有了这个赞助。你的赞助不仅会被用来支付一些开发成本:服务器、域名、付费软件等,还将帮助我奉献更多的时间到项目中。如果你正在使用我的 API,可以用赞助来表示你的谢意,并让项目保持健康稳定和得到更积极的维护 170 | 171 | 我不会忘记支持我的人,也不会把你的支持当作理所当然,它对我意义重大。 172 | 173 | 周期性赞助请联系我,你将会得到我的帮助;如果你不喜欢周期性赞助,也可以选择一次性赞助方式: 174 | 175 | ![支付宝、微信](./1660899340003.jpg) 176 | 177 | 178 | 179 | ## 捐赠列表 180 | 181 | > 以下排名不分先后 182 | 183 | 特别感谢 **超 女士 184 | 185 | 感谢 **宇 先生 186 | 187 | 感谢 **光 先生 188 | 189 | 190 | 191 | ## 免责申明 192 | 193 | 1. 以上开发接口仅限于技术研究和项目开发练习使用,禁止商业用途,如有发现直接关闭服务 194 | 2. 音乐版权归各音乐平台所有,若有侵犯版权,请联系我删除 195 | 196 | -------------------------------------------------------------------------------- /docs/TX.md: -------------------------------------------------------------------------------- 1 | # 企鹅音乐 2 | 3 | ## 搜索 4 | 5 | ### 搜索 6 | 7 | 接口:`/v1/qq/search` 8 | 9 | 说明:调用此接口 , 传入搜索关键词可以搜索该音乐 / 专辑 / 歌手 / 歌单 / 用户(需要自己传入type参数) , 默认会自动去除 关键词 前后的**空白字符** 10 | 11 | 可选参数: 12 | 13 | `key`:关键字 默认 暗号 14 | 15 | `limit`:每一页返回的数量,默认30 16 | 17 | `offset`:页码,默认1 18 | 19 | `type`:搜索类型 默认为0 取值意义 20 | 21 | 0:单曲 1:歌手 2:专辑 3:歌单 4:mv 7:歌词 8:用户 22 | 23 | 24 | 25 | `albummid` 为专辑的 mid, 下面为专辑封面图片的路径 26 | 27 | > https://y.gtimg.cn/music/photo_new/T002R300x300M000 + `albummid` + _.jpg_ 28 | 29 | 示例:[/v1/qq/search?key=暗号](http://iecoxe.top:5000/v1/qq/search?key=暗号) 30 | 31 | 32 | 33 | ### 热搜 34 | 35 | 接口:`/v1/qq/hotSearch` 36 | 37 | 可选参数:`from` 默认 `pc`, 可选值 `pc ` 和 `web` 38 | 39 | 说明:pc可以获取更加详细的热搜内容,web获取的数据比较少 40 | 41 | 示例:[/v1/qq/hotSearch](http://iecoxe.top:5000/v1/qq/hotSearch) 42 | 43 | 44 | 45 | ### 搜索建议 46 | 47 | 接口:`/v1/qq/suggestSearch` 48 | 49 | 必选参数:`key` 50 | 51 | 示例:[/v1/qq/suggestSearch?key=周杰伦](http://iecoxe.top:5000/v1/qq/suggestSearch?key=周杰伦) 52 | 53 | 54 | 55 | ## 歌曲url 56 | 57 | 接口:`/v1/qq/song` 58 | 59 | 说明: 60 | 61 | - 这个接口依赖服务器的 Cookie 信息的,支持批量获取,不一定是全部的歌曲都有无损、高品的, 要注意结合 size320,sizeape,sizeflac 等参数先判断下是否有播放链接 62 | - 服务器内置默认的 Cookie ,如果是未登陆或非 vip 用户的 `cookie`,只能获取到非 vip 用户可听的歌曲 63 | - 首页有一个cookie共享仓库,将自己的绿钻 cookie 传入之后, 会自动刷新 cookie ,保持长期有效,仓库使用说明 [cookie 共享仓库地址 点击跳转](https://github-zc.github.io/wp_MusicApi/#/?id=cookie-%e5%85%b1%e4%ba%ab%e4%bb%93%e5%ba%93%e4%bd%bf%e7%94%a8%e8%af%b4%e6%98%8e) 64 | - 服务器 Cookie 的设置,可以使用 [/qq/setcookie/](//#/?id=设置用户cookie) 65 | - 服务器会自动去除mid,br以及songmid之间的**空白字符** 66 | 67 | 可选参数: 68 | 69 | `mid`:歌曲的`songmid`,默认`004O1DHG4MjYOi` 70 | 71 | `br`:默认 128 72 | 73 | 取值意义: 128:mp3 128k,320:mp3 320k,m4a:m4a格式 128k,flac:flac格式 无损,ape:ape格式 无损 74 | 75 | 示例:[/v1/qq/song?mid=0039MnYb0qxYhV&br=flac](http://iecoxe.top:5000/v1/qq/song?mid=0039MnYb0qxYhV&br=flac) 76 | 77 | `http://localhost:5000/v1/qq/song?mid=001Qu4I30eVFYb,003EKHdv2KvTc6,0032ZOkm0LBgHW` 78 | 79 | ## 歌词 80 | 81 | 接口:`/v1/qq/lyric/` 82 | 83 | 可选参数:`mid` 默认 `004O1DHG4MjYOi` 84 | 85 | 示例:[/v1/qq/lyric/?mid=004O1DHG4MjYOi](http://iecoxe.top:5000/v1/qq/lyric/?mid=004O1DHG4MjYOi) 86 | 87 | 88 | 89 | ## 获取歌单歌曲列表 90 | 91 | 请求地址:`/v1/qq/playlist/info` 92 | 93 | 请求示例:`http://iecoxe.top:5000/v1/qq/playlist/info?pid=2164751441` 94 | 95 | | 参数 | 是否必须 | 接口说明 | 默认值 | 96 | | ---- | -------- | -------- | ------ | 97 | | pid | √ | 歌单 ID | 无 | 98 | 99 | 100 | 101 | 102 | 103 | ## 获取评论 104 | 105 | 请求地址:`/v1/qq/comment` 106 | 107 | 请求示例:`http://iecoxe.top:5000/v1/qq/comment?id=449198&limit=25&type=0` 108 | 109 | | 参数 | 是否必须 | 接口说明 | 默认值 | 110 | | ------- | -------- | ---------------------------------------------------- | ------ | 111 | | id | √ | singid, albumid, tid, topid, vid 必填 | 无 | 112 | | offset | × | 偏移量 | 1 | 113 | | limit | × | 返回数据量,最大为25 | 25 | 114 | | type | × | 0:获取最新评论,1:获取热评 | 0 | 115 | | biztype | × | 获取评论类型 1: 歌曲 2: 专辑 3: 歌单 4: 排行榜 5: mv | 1 | 116 | 117 | 返回结果说明: 118 | 119 | `ispraise` 表示这条评论是否被赞过,1: 是,0: 否; 120 | 121 | `enable_delete` 表示这条评论是否能被删除,1: 是,0: 否 122 | 123 | 124 | 125 | 126 | 127 | ## 排行榜 128 | 129 | ### 获取榜单列表 130 | 131 | 接口:`/v1/qq/topCategory` 132 | 133 | 说明:这个接口数据,包含了榜单名、榜单 id、更新时间、播放量,都是作为下一个接口的请求参数 134 | 135 | 示例:[/v1/qq/topCategory](http://iecoxe.top:5000/v1/qq/topCategory) 136 | 137 | 138 | 139 | ### 获取榜单详情 140 | 141 | 接口:`/v1/qq/top/` 142 | 143 | 可选参数: 144 | 145 | `topId`: 默认 26,从上面的`/top/category/`中取值 146 | 147 | `limit`: 默认 200 // 部分接口不支持这个字段,所以这里默认选择200 148 | 149 | `period`: 榜单的时间,从上面的 `/top/category/` 中取值,不填默认返回 **最新** 的排行榜数据 150 | 151 | `offset`:每一页返回的歌曲数量,默认 1 152 | 153 | 返回数据说明 154 | 155 | `time`: 当前榜单的发布时间,可能是天,也可能是周 156 | 157 | ``` 158 | timeType`: 当前榜单的时间格式 `YYYY_W` 或 `YYYY-MM-DD 159 | ``` 160 | 161 | `rank`: 在榜单的排名 162 | 163 | `rankType`: 1 上升,2 减少,3 持平,4 新歌,6 上升百分比 164 | 165 | `rankValue`: 排名改变值 166 | 167 | 示例:[/v1/qq/top/?topId=26](http://iecoxe.top:5000/v1/qq/top/?topId=26) 168 | 169 | 170 | 171 | ## 用户登录(新增) 172 | 173 | ### QQ账号登录 174 | 175 | 接口:`/v1/qq/login/` 176 | 177 | 说明:登录接口目前还是只能本地登录,异地登陆官方强制扫描二维码,各位有什么好的办法可以在GitHub中提出 178 | 179 | 必须参数: 180 | 181 | | 参数 | 是否必须 | 接口说明 | 默认 | 182 | | ---- | -------- | -------- | ---- | 183 | | u | √ | QQ账号 | 无 | 184 | | p | √ | QQ密码 | 无 | 185 | 186 | **注意**:返回 **异地登陆,需要验证码,请在QQ手机关闭登录保护** 代表强制需要扫描二维码 187 | 188 | ​ **如果登录成功会自动更新本地cookie** 189 | 190 | 示例:`http://iecoxe.top:3000/v1/qq/login?u=123456&p=123456` 191 | 192 | 193 | 194 | ### QQ扫码登录 195 | 196 | 接口:`/v1/qq/login_scan/` 197 | 198 | 示例:`http://iecoxe.top:3000/v1/qq/login_scan` 199 | 200 | 说明:本接口会默认返回二维码,用手机QQ扫码确认即可登录,自动部署Cookie,为防止恶意调用访问接口,默认 **限制访问间隔** 50 秒,重复调用会覆盖服务端Cookie 201 | 202 | 203 | 204 | 205 | 206 | ## Cookie 207 | 208 | 209 | 210 | ### 设置服务器Cookie(POST) 211 | 212 | 接口:`/v1/qq/setcookie/` 213 | 214 | 说明:具有 QQ 绿钻的用户,通过浏览器查看 Cookie ,通过此接口设置以后,需要登陆的接口可以获取登录才能获取的内容,比如 歌曲播放地址(无损) 215 | 216 | 必须参数: 217 | 218 | `data`: 219 | 220 | > 格式:aaa=bbb; ccc=ddd; .... 221 | 222 | > 例子:yqq_stat=0; pgv_info=ssid=s1520392; ts_last=y.qq.com/portal/player.html; pgv_pvid=1233495754; ts_uid=5486601780; pgv_pvi=4007713792; pgv_si=s6654436352; userAction=1; _qpsvr_localtk=0.8025676546673662; yq_index=0; yplayer_open=1; player_exist=1; qqmusic_fromtag=66 223 | 224 | 225 | 226 | ### 查看当前Cookie 227 | 228 | 接口:`/v1/qq/getcookie/` 229 | 230 | 说明:cookie 已经在服务器端,进行的 Json 转化 231 | 232 | 示例:[/v1/qq/getcookie/](http://iecoxe.top:5000/v1/qq/getcookie/) 233 | 234 | -------------------------------------------------------------------------------- /docs/WY.md: -------------------------------------------------------------------------------- 1 | # 网易云音乐 2 | 3 | ## 搜索 4 | 5 | 请求地址:`/v1/wy/search` 6 | 7 | 请求示例:`http://iecoxe.top:5000/v1/wy/search?key=周杰伦` 8 | 9 | | 参数 | 是否必须 | 接口说明 | 默认值 | 10 | | ------ | -------- | ------------------------------------------------------------ | -------- | 11 | | key | √ | 关键字 | 默认为空 | 12 | | limit | × | 搜索结果的数量 | 30 | 13 | | offset | × | 数据的偏移量 | 1 | 14 | | type | × | 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频 | 1 | 15 | 16 | 说明:`offset相当于页数,1,2,3,… 不需要 乘以 limit` 17 | 18 | 19 | 20 | ## 获取音乐歌词 21 | 22 | 请求地址:`/v1/wy/lyric` 23 | 24 | 请求示例:`http://iecoxe.top:5000/v1/wy/lyric?wid=33894312` 25 | 26 | | 参数 | 是否必须 | 接口说明 | 默认值 | 27 | | ---- | -------- | -------- | ------ | 28 | | wid | √ | 音乐 ID | | 29 | 30 | 31 | 32 | 33 | 34 | ## 获取mv播放地址 35 | 36 | 请求地址:`/v1/wy/mv_url` 37 | 38 | 请求示例:`http://iecoxe.top:5000/v1/wy/mv_url?wid=10896407` 39 | 40 | | 参数 | 是否必须 | 接口说明 | 默认值 | 41 | | ---- | -------- | ---------- | ------ | 42 | | wid | √ | 音乐 ID | | 43 | | r | × | 视频分辨率 | `1080` | 44 | 45 | 46 | 47 | 48 | 49 | ## 获取每日推荐歌曲 50 | 51 | 请求地址:`/v1/wy/recommendSongs` 52 | 53 | 请求示例:`http://iecoxe.top:5000/v1/wy/recommendSongs` 54 | 55 | | 参数 | 是否必须 | 接口说明 | 默认值 | 56 | | ---- | -------- | -------- | ------ | 57 | | 无 | | | | 58 | 59 | 说明 : 调用此接口 , 可获得每日推荐歌曲 ( 需要登录 ,需要传入已经登陆的Cookie) 60 | 61 | 62 | 63 | 64 | 65 | 66 | ## 获取音乐播放地址 67 | 68 | 请求地址:`/v1/wy/song` 69 | 70 | 请求示例:`http://iecoxe.top:5000/v1/wy/song?id=167655,353066` 71 | 72 | | 参数 | 是否必须 | 接口说明 | 默认值 | 73 | | ------ | -------- | ------------------------------------------------------------ | -------- | 74 | | id | √ | 音乐 ID,支持批量获取,id之间用逗号隔开 | 无 | 75 | | br | × | 播放音质等级, 分为 `standard` => `标准`,`higher` => `较高`, `exhigh`=>`极高`, `lossless`=>`无损`, `hires`=>`Hi-Res` | standard | 76 | | cookie | × | 传入会 覆盖全局cookie, 不传入默认使用 全局 cookie | 无 | 77 | 78 | 说明:`当前是默认音质,如果想获取VIP音乐,请传入Cookie` 79 | 80 | 81 | 82 | 83 | 84 | ## 获取音乐播放地址(新版) 85 | 86 | 请求地址:`/v1/wy/songurl` 87 | 88 | 请求示例:`http://iecoxe.top:5000/v1/wy/songurl?br=lossless&id=1463165983,167655,353066` 89 | 90 | | 参数 | 是否必须 | 接口说明 | 默认值 | 91 | | ---- | -------- | ------------------------------------------------------------ | ------ | 92 | | id | √ | 音乐 ID,支持批量获取,id之间用逗号隔开 | 无 | 93 | | br | × | 播放音质等级, 分为 `standard` => `标准`,`higher` => `较高`, `exhigh`=>`极高`, `lossless`=>`无损`, `hires`=>`Hi-Res` | higher | 94 | 95 | 说明:`当前是默认音质,如果想获取VIP音乐,请传入Cookie` 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | ## 获取评论 104 | 105 | 请求地址:`/v1/wy/comment` 106 | 107 | 请求示例:`http://iecoxe.top:5000/v1/wy/comment?type=0&id=167827&sortType=2` 108 | 109 | | 参数 | 是否必须 | 接口说明 | 默认值 | 110 | | -------- | -------- | ------------------------------------------------------------ | ------ | 111 | | id | √ | 资源 id, 如歌曲 id,mv id | 无 | 112 | | offset | × | 偏移量 | 1 | 113 | | limit | × | 返回数据量 | 30 | 114 | | type | × | 数字 , 资源类型 , 对应歌曲 , mv, 专辑 , 歌单 , 电台, 视频对应以下类型 | 0 | 115 | | sortType | × | 排序方式, 1:按推荐排序, 2:按热度排序, 3:按时间排序 | 0 | 116 | | cursor | × | 当`sortType`为 3 时且页数不是第一页时需传入,值为上一条数据的 time | | 117 | 118 | `type` 类型说明 119 | 120 | ```javascript 121 | 0: 歌曲 1: mv 2: 歌单 3: 专辑 4: 电台节目 5: 视频 6: 动态 7: 电台 122 | ``` 123 | 124 | 125 | 126 | 127 | 128 | ## 获取热门评论 129 | 130 | 请求地址:`/v1/wy/comment/hot` 131 | 132 | 请求示例:`http://iecoxe.top:5000/v1/wy/comment/hot?id=186016&type=0&offset=3` 133 | 134 | | 参数 | 是否必须 | 接口说明 | 默认值 | 135 | | ------ | -------- | ------------------------------------------------------------ | ------ | 136 | | id | √ | 资源 id, 如歌曲 id,mv id | 无 | 137 | | offset | × | 偏移量 | 1 | 138 | | limit | × | 返回数据量 | 30 | 139 | | type | × | 数字 , 资源类型 , 对应歌曲 , mv, 专辑 , 歌单 , 电台, 视频对应以下类型 | 0 | 140 | | before | × | 分页参数,取上一页最后一项的 `time` 获取下一页数据(获取超过 5000 条评论的时候需要用到) | 0 | 141 | 142 | `type` 类型说明 143 | 144 | ```javascript 145 | 0: 歌曲 1: mv 2: 歌单 3: 专辑 4: 电台节目 5: 视频 6: 动态 7: 电台 146 | ``` 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | ## 获取歌单歌曲列表 155 | 156 | 请求地址:`/v1/wy/playlist/info` 157 | 158 | 请求示例:`http://iecoxe.top:5000/v1/wy/playlist/info?pid=12423324` 159 | 160 | | 参数 | 是否必须 | 接口说明 | 默认值 | 161 | | ---- | -------- | -------- | ------ | 162 | | pid | √ | 歌单 ID | 无 | 163 | 164 | 165 | 166 | 167 | 168 | ## 获取排行榜详情 169 | 170 | 请求地址:`/v1/wy/topCategory` 171 | 172 | 请求示例:`http://iecoxe.top:5000/v1/wy/topCategory` 173 | 174 | | 参数 | 是否必须 | 接口说明 | 默认值 | 175 | | ---- | -------- | -------- | ------ | 176 | | 无 | | | | 177 | 178 | 注:排行榜歌曲详情可以调用 `/v1/wy/playlist/info` 接口获取 179 | 180 | 181 | 182 | ## Cookie 183 | 184 | 185 | 186 | ### 设置服务器Cookie(POST) 187 | 188 | 接口:`/v1/wy/setcookie/` 189 | 190 | 说明:具有 酷狗会员的用户,通过浏览器查看 Cookie ,通过此接口设置以后,需要登陆的接口可以获取登录才能获取的内容,比如 歌曲播放地址(无损) 191 | 192 | 必须参数: 193 | 194 | `data`: 195 | 196 | > 格式:aaa=bbb; ccc=ddd; .... 197 | 198 | > 例子:yqq_stat=0; pgv_info=ssid=s1520392; ts_last=y.qq.com/portal/player.html; pgv_pvid=1233495754; ts_uid=5486601780; pgv_pvi=4007713792; pgv_si=s6654436352; userAction=1; _qpsvr_localtk=0.8025676546673662; yq_index=0; yplayer_open=1; player_exist=1; qqmusic_fromtag=66 199 | 200 | 201 | 202 | ### 查看当前Cookie 203 | 204 | 接口:`/v1/wy/getcookie/` 205 | 206 | 说明:cookie 已经在服务器端,进行的 Json 转化 207 | 208 | 示例:[/v1/wy/getcookie/](http://iecoxe.top:5000/v1/wy/getcookie/) 209 | 210 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | * 快速开始 2 | * [关于](/) 3 | * [使用说明](common.md) 4 | * [常见问题](question.md) 5 | * 音乐接口 6 | * [企鹅音乐](TX.md "wp_MusicApi 音乐接口 企鹅音乐") 7 | * [咪咕音乐](MG.md "wp_MusicApi 音乐接口 咪咕音乐") 8 | * [酷我音乐](KW.md "wp_MusicApi 音乐接口 酷我音乐") 9 | * [酷狗音乐](KG.md "wp_MusicApi 音乐接口 酷狗音乐") 10 | * [网易云音乐](WY.md "wp_MusicApi 音乐接口 网易云音乐") 11 | * 其他接口 12 | * [拾荒者API](scavengers.md "wp_MusicApi 拾荒者") -------------------------------------------------------------------------------- /docs/common.md: -------------------------------------------------------------------------------- 1 | # 使用说明 2 | 3 | ## 安装 4 | 5 | ```shell 6 | $ git clone https://github.com/GitHub-ZC/wp_MusicApi.git 7 | $ cd wp_MusicApi/ 8 | $ npm install 9 | ``` 10 | 11 | 12 | 13 | ## 运行 14 | 15 | ```shell 16 | $ npm start 17 | ``` 18 | 19 | > 默认端口 5000 需要更改启动端口的可以在 __setting.js__ 文件中设置 20 | 21 | 22 | 23 | ## npm 24 | 25 | ```javascript 26 | // 如果需要在项目中引入 wp_MusicApi, 示例如下 27 | npm i wp_musicapi 28 | ``` 29 | 30 | 31 | 32 | ## 接口调用 33 | 34 | ```javascript 35 | 36 | // 引入 wp_MusicApi 37 | const wp_musicapi = require("wp_musicapi"); 38 | 39 | // 咪咕搜索 40 | wp_musicapi["/v1/migu/song"]({br: 1}).then(res => console.log(res)); 41 | // QQ搜索 42 | wp_musicapi["/v1/qq/search"]({key: '周杰伦'}).then(res => console.log(res)); 43 | 44 | // 酷我搜索 45 | (async () => { 46 | let res = await wp_musicapi["/v1/kuwo/search"]({ 47 | from: 'pc', 48 | key: '许嵩' 49 | }); 50 | 51 | console.log(res); 52 | })(); 53 | 54 | ``` 55 | 56 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Please wait...
15 | 25 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/question.md: -------------------------------------------------------------------------------- 1 | # 常见问题 2 | 3 | 4 | 5 | ## 文档错误 6 | 7 | 由于文档是快速复制粘贴而成,文档中的可能有错误的信息,请直接在项目中提[issue](https://github.com/GitHub-ZC/wp_MusicApi/issues) -------------------------------------------------------------------------------- /docs/scavengers.md: -------------------------------------------------------------------------------- 1 | # 拾荒者API 2 | 3 | ## 获取图片主要颜色 4 | 5 | 请求地址:`/v1/scavengers/getMasterColor` 6 | 7 | 请求示例:`http://iecoxe.top:5000/v1/scavengers/getMasterColor?imgUrl=http://iecoxe.top:90/exceptional.jpg` 8 | 9 | | 参数 | 是否必须 | 接口说明 | 默认值 | 10 | | ------ | -------- | :--------------------- | ------ | 11 | | imgUrl | √ | imgUrl为图片的网络地址 | 无 | 12 | 13 | 说明:如何图片颜色获取错误(大概率由于网络原因),错误返回`#FFFFFF`纯白色 14 | 15 | 注意:图片的url需要做url编码处理,否则接口无法正确接受参数 -------------------------------------------------------------------------------- /docs/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "v1.5.2" 3 | } -------------------------------------------------------------------------------- /docs/请关注一下公众号,每天有精彩资源推送.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitHub-ZC/wp_MusicApi/bf9307dd138dc8ac6c4f7de29361209d4f5b665f/docs/请关注一下公众号,每天有精彩资源推送.jpg -------------------------------------------------------------------------------- /l.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios"); 2 | const __Cookie = require('./util/cookie_util'); 3 | const config = require("./setting"); 4 | 5 | let parseNative = (cookie) => { 6 | let arr = cookie.split(";;"); 7 | let arr_ = arr.map(e => { 8 | return e.split("; ")[0] 9 | }) 10 | arr_ = [...new Set(arr_)]; 11 | return arr_.join("; "); 12 | } 13 | 14 | 15 | function getWYCookie(count = 0) { 16 | if(count > 2) { 17 | return; 18 | } 19 | 20 | axios.get(`http://localhost:${config.port}/v1/wy/login/refresh`).then( 21 | res => { 22 | console.log(res.data); 23 | } 24 | ).catch(err => { 25 | console.warn(`更新网易cookie函数内部错误, 重试中(${count+1} 次)`); 26 | getWYCookie(++count); 27 | }) 28 | } 29 | 30 | module.exports = { 31 | getWYCookie 32 | } -------------------------------------------------------------------------------- /middlewares/auth.js: -------------------------------------------------------------------------------- 1 | const CryptoJS = require('crypto-js'); 2 | const { APIError } = require('./rest'); 3 | const setting = require('../setting'); 4 | 5 | 6 | module.exports = { 7 | validation: (pathPrefix = '') => { 8 | flag = setting.is_open_validation || false; 9 | pathPrefix = pathPrefix || '/v1/'; 10 | return async (ctx, next) => { 11 | if (flag) { 12 | if (ctx.request.path.startsWith(pathPrefix)) { 13 | // 获取请求头 imax-music 14 | let ImaxMusic = ctx.request.headers['imax-music']; 15 | 16 | if (!ImaxMusic) { 17 | return; 18 | } 19 | 20 | // Decrypt 21 | var bytes = CryptoJS.AES.decrypt(ImaxMusic, (new Date()).getMinutes().toString() + 'QWEASDZXC'); 22 | var originalText = bytes.toString(CryptoJS.enc.Utf8); 23 | 24 | start_time = parseInt(originalText); 25 | // 获取当前时间戳 26 | let end_time = parseInt(new Date().getTime() / 1000); 27 | 28 | if (Math.abs(end_time - start_time) < 3) { 29 | await next(); 30 | } 31 | } else { 32 | await next(); 33 | } 34 | } else { 35 | await next(); 36 | } 37 | }; 38 | } 39 | }; -------------------------------------------------------------------------------- /middlewares/rest.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | APIError: function (code, message) { 3 | this.code = code || 'internal:unknown_error'; 4 | this.message = message || ''; 5 | }, 6 | restify: (pathPrefix) => { 7 | pathPrefix = pathPrefix || '/v1/'; 8 | return async (ctx, next) => { 9 | if (ctx.request.path.startsWith(pathPrefix)) { 10 | // 绑定rest()方法: 11 | ctx.rest = (data) => { 12 | ctx.response.type = 'application/json'; 13 | ctx.response.body = data; 14 | } 15 | try { 16 | await next(); 17 | } catch (e) { 18 | throw e; 19 | } 20 | } else { 21 | await next(); 22 | } 23 | }; 24 | } 25 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wp_musicapi", 3 | "version": "1.5.2", 4 | "description": "一款Node.js的音乐接口", 5 | "main": "./routes/v1/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node app.js" 9 | }, 10 | "keywords": [], 11 | "author": "IMAX-ZC", 12 | "license": "Apache-2.0", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/GitHub-ZC/wp_MusicApi.git" 16 | }, 17 | "dependencies": { 18 | "axios": "^0.27.2", 19 | "colorthief": "^2.3.2", 20 | "crypto-js": "^4.1.1", 21 | "iconv-lite": "^0.6.3", 22 | "koa": "^2.13.4", 23 | "koa-body": "^5.0.0", 24 | "koa-router": "^12.0.0", 25 | "koa2-cors": "^2.0.6", 26 | "moment": "^2.29.4", 27 | "node-jsencrypt": "^1.0.0", 28 | "qs": "^6.11.0", 29 | "zlib": "^1.0.5" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /public/controllers.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router'); 2 | const fs = require("fs"); 3 | const path = require('path'); 4 | 5 | const index = new Router(); 6 | 7 | index.get('/', async (ctx) => { 8 | const html = fs.readFileSync(path.join(__dirname, "./index.html")); 9 | // console.log(__dirname); 10 | ctx.response.type = 'text/html;charset=UTF-8'; 11 | ctx.response.body = html; 12 | }) 13 | 14 | module.exports = index; -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | wp_MusicApi 9 | 10 | 11 | 12 |

wp_MusicApi

13 | 当你看到这个页面时,这个服务已经成功跑起来了~ 14 | 查看文档 15 |

QQ 例子:

16 | 22 |

咪咕 例子:

23 | 29 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /refreshQQCookie.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios"); 2 | const __Cookie = require('./util/cookie_util'); 3 | 4 | function getQQCookie(uin, count = 0) { 5 | if(count >= 3) { 6 | return; 7 | } 8 | 9 | axios.get('http://42.192.118.65:5100/qq/getCookie' + `?uin=${uin || ""}`).then( 10 | res => { 11 | if (res.data.cookie) { 12 | global.qq_cookie = __Cookie.parse(res.data.cookie); 13 | console.log("QQ Cookie更新成功"); 14 | } 15 | } 16 | ).catch(err => { 17 | console.warn(`QQ Cookie更新函数内部错误, 重试中(${count+1} 次)`); 18 | getQQCookie(uin, ++count); 19 | }) 20 | } 21 | 22 | module.exports = { 23 | getQQCookie 24 | } -------------------------------------------------------------------------------- /routes/v1/controllers.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router'); 2 | 3 | const migu = require('./migu/controllers'); 4 | const qq = require('./qq/controllers'); 5 | const kuwo = require('./kuwo/controllers'); 6 | const kugou = require('./kugou/controllers'); 7 | const scavengers = require('./scavengers/controllers'); 8 | const wy = require('./wy/controllers'); 9 | 10 | const v1 = new Router({prefix: '/v1'}); 11 | 12 | 13 | v1.use('/migu', migu.routes()); 14 | v1.use('/qq', qq.routes()); 15 | v1.use('/kuwo', kuwo.routes()); 16 | v1.use('/kugou', kugou.routes()); 17 | v1.use('/wy', wy.routes()); 18 | v1.use('/scavengers', scavengers.routes()); 19 | 20 | module.exports = v1; -------------------------------------------------------------------------------- /routes/v1/index.js: -------------------------------------------------------------------------------- 1 | 2 | const Cache = require('../../util/cache'); 3 | const v1 = require('./controllers'); 4 | 5 | 6 | // 自定义库 7 | const config = require('../../setting'); 8 | const __Cookie = require('../../util/cookie_util'); 9 | const { getWYCookie } = require('../../l'); 10 | const { getQQCookie } = require('../../refreshQQCookie'); 11 | const { default: axios } = require('axios'); 12 | 13 | 14 | // 设置qq音乐的cookie 15 | global.qq_cookie = __Cookie.parse(config.qq_cookie); 16 | global.migu_cookie = __Cookie.parse(config.migu_cookie); 17 | global.kugou_cookie = __Cookie.parse(config.kugou_cookie); 18 | global.wy_cookie = __Cookie.parse(config.wy_cookie); 19 | 20 | global.cache = new Cache(); 21 | 22 | 23 | setInterval(() => { 24 | global.cache.clear(); 25 | }, 3000); 26 | 27 | // //更新网易云cookie 28 | // // setTimeout(() => { 29 | // // getWYCookie(); 30 | // // }, 10 * 1000); 31 | 32 | // setInterval(() => { 33 | // getWYCookie(); 34 | // }, 1000 * 60 * 60 * 24 * 12); 35 | 36 | // 更新QQcookie 37 | // setTimeout(() => { 38 | // getQQCookie(config.QQ_uin); 39 | // }, 0); 40 | 41 | // setInterval(() => { 42 | // getQQCookie(config.QQ_uin); 43 | // }, 1000 * 60 * 60); 44 | 45 | 46 | 47 | module.exports = (() => { 48 | // 遍历每一个对象 49 | let route = {}; 50 | v1.stack ? v1.stack.forEach(element => { 51 | route[element.path] = element.stack[0] ? (async (query = {}) => { 52 | let result; 53 | 54 | rest = function (data) { 55 | result = data; 56 | }; 57 | 58 | try { 59 | await element.stack[0]({ 60 | request: { 61 | url: `${element.path}---${JSON.stringify(query)}`, 62 | method: 'GET', 63 | query 64 | }, 65 | rest 66 | }); 67 | } catch (e) { 68 | result = { 69 | code: e.code || 'internal:unknown_error', 70 | message: e.message || '' 71 | }; 72 | } 73 | 74 | return result; 75 | }) : null; 76 | }) : {}; 77 | 78 | return route; 79 | })(); -------------------------------------------------------------------------------- /routes/v1/kugou/comment.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | // 第三方加密库 4 | const cryptoJs = require('crypto-js'); 5 | 6 | 7 | let comment = async (ctx) => { 8 | if (ctx.request.method === 'GET') { 9 | var hash = ctx.request.query.hash || ''; 10 | var offset = ctx.request.query.offset || '1'; 11 | var limit = ctx.request.query.limit || '30'; 12 | var type = ctx.request.query.type || '0'; 13 | } else if (ctx.request.method === 'POST') { 14 | var hash = ctx.request.body.hash || ''; 15 | var offset = ctx.request.body.offset || '1'; 16 | var limit = ctx.request.body.limit || '30'; 17 | var type = ctx.request.body.type || '0'; 18 | } 19 | 20 | const cacheData = global.cache.get(ctx.request.url); 21 | if (cacheData) { 22 | ctx.rest(cacheData); 23 | return; 24 | } 25 | 26 | if (!hash) { 27 | throw new APIError("argument_error", "hash is not found") 28 | } 29 | 30 | 31 | if (parseInt(limit) > 40) limit = '40'; 32 | 33 | 34 | let k = Math.round((new Date).getTime() / 1000); 35 | 36 | 37 | let result = null; 38 | if(type == '0') { 39 | let md5_str = `OIlwieks28dk2k092lksi2UIkpappid=1005clienttime=${k}clienttoken=clientver=10869code=fc4be23b4e972707f36b8a828a93ba8adfid=1tSE9K4VW4ZW0299bn0Gu1bNextdata=${hash}kugouid=0mid=143773497225871101952332916871990469790p=${offset.trim()}pagesize=${limit.trim()}schash=${hash}uuid=4f3e2278033606d95d92efddc0744d9cver=10OIlwieks28dk2k092lksi2UIkp`; 40 | let signature = cryptoJs.MD5(md5_str); 41 | 42 | // 最多获取40条评论 最新评论 43 | result = await axios.post(`http://m.comment.service.kugou.com:80/r/v1/rank/newest?dfid=1tSE9K4VW4ZW0299bn0Gu1bN&mid=143773497225871101952332916871990469790&signature=${signature.toString()}&clienttime=${k}&uuid=4f3e2278033606d95d92efddc0744d9c&extdata=${hash}&appid=1005&code=fc4be23b4e972707f36b8a828a93ba8a&schash=${hash}&clientver=10869&p=${offset.trim()}&clienttoken=&pagesize=${limit.trim()}&ver=10&kugouid=0`, null, { 44 | headers: { 45 | 'User-Agent': 'Android712-AndroidPhone-10869-52-0-COMMENT-wifi', 46 | } 47 | }); 48 | } else { 49 | let md5_str = `OIlwieks28dk2k092lksi2UIkpappid=1005clienttime=${k}clienttoken=clientver=10869code=fc4be23b4e972707f36b8a828a93ba8adfid=1tSE9K4VW4ZW0299bn0Gu1bNextdata=${hash}kugouid=0mid=143773497225871101952332916871990469790p=${offset.trim()}pagesize=${limit.trim()}schash=${hash}uuid=4f3e2278033606d95d92efddc0744d9cver=10OIlwieks28dk2k092lksi2UIkp` 50 | let signature = cryptoJs.MD5(md5_str); 51 | 52 | // 最多获取40条评论 最热评论 53 | result = await axios.post(`http://m.comment.service.kugou.com:80/r/v1/rank/topliked?dfid=1tSE9K4VW4ZW0299bn0Gu1bN&mid=143773497225871101952332916871990469790&signature=${signature.toString()}&clienttime=${k}&uuid=4f3e2278033606d95d92efddc0744d9c&extdata=${hash}&appid=1005&code=fc4be23b4e972707f36b8a828a93ba8a&schash=${hash}&clientver=10869&p=${offset.trim()}&clienttoken=&pagesize=${limit.trim()}&ver=10&kugouid=0`, null, { 54 | headers: { 55 | 'User-Agent': 'Android712-AndroidPhone-10869-52-0-COMMENT-wifi', 56 | } 57 | }); 58 | } 59 | 60 | 61 | global.cache.set(ctx.request.url, result ? result.data : {}); 62 | 63 | ctx.rest(result ? result.data : {}); 64 | } 65 | 66 | 67 | 68 | 69 | let replyComment = async (ctx) => { 70 | if (ctx.request.method === 'GET') { 71 | var special_child_id = ctx.request.query.special_child_id || ''; 72 | var id = ctx.request.query.id || ''; 73 | var offset = ctx.request.query.offset || '1'; 74 | var limit = ctx.request.query.limit || '30'; 75 | } else if (ctx.request.method === 'POST') { 76 | var special_child_id = ctx.request.body.special_child_id || ''; 77 | var id = ctx.request.body.id || ''; 78 | var offset = ctx.request.body.offset || '1'; 79 | var limit = ctx.request.body.limit || '30'; 80 | } 81 | 82 | 83 | const cacheData = global.cache.get(ctx.request.url); 84 | if (cacheData) { 85 | ctx.rest(cacheData); 86 | return; 87 | } 88 | 89 | if (!special_child_id || !id) { 90 | throw new APIError("argument_error", "special_child_id or id is not found") 91 | } 92 | 93 | if (parseInt(limit) > 50) limit = '50'; 94 | 95 | // 获取音乐评论 最多50条 96 | const result = await axios.post(`http://m.comment.service.kugou.com:80/index.php?appid=1005&uuid=4f3e2278033606d95d92efddc0744d9c&r=commentsv2/getReplyWithLike&code=fc4be23b4e972707f36b8a828a93ba8a&childrenid=${special_child_id.trim()}&kugouid=0&ver=10&clienttoken=&clientver=10869&mid=143773497225871101952332916871990469790&clienttime=1666945630&key=6bc76958b283e3901b85a75e1355f396&dfid=1tSE9K4VW4ZW0299bn0Gu1bN&tid=${id.trim()}&gitversion=2d3769c&clisource=common&p=${offset.trim()}&pagesize=${limit.trim()}&mixsongid=32100650&cmtdreturnserver=%7B%7D`, null, { 97 | headers: { 98 | 'User-Agent': 'Android712-AndroidPhone-10869-52-0-COMMENT-wifi', 99 | } 100 | }); 101 | 102 | global.cache.set(ctx.request.url, result ? result.data : {}); 103 | 104 | ctx.rest(result ? result.data : {}); 105 | } 106 | 107 | 108 | 109 | module.exports = { 110 | comment, 111 | replyComment 112 | } -------------------------------------------------------------------------------- /routes/v1/kugou/controllers.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router'); 2 | 3 | const { search, hotSearch, suggestSearch, mobileSearch } = require('./search'); 4 | const { song, getsong } = require('./song'); 5 | const { top, topCategory } = require('./top'); 6 | const { lyric } = require('./lyric'); 7 | const { playlist_tagCategory, playlist_Tag, playlist_Info, playlist_list, playlist_import } = require('./playlist'); 8 | const { setcookie, getcookie } = require('./cookie'); 9 | const { comment, replyComment } = require('./comment'); 10 | 11 | // 新建 kugou 路由 12 | const kugou = new Router(); 13 | 14 | // add get method 15 | 16 | kugou.get('/search', search); 17 | kugou.get('/hotSearch', hotSearch); 18 | kugou.get('/suggestSearch', suggestSearch); 19 | kugou.get('/mobileSearch', mobileSearch); 20 | 21 | kugou.get('/song', song); 22 | kugou.get('/getsong', getsong); 23 | // kugou.get('/songInfo', songInfo); 24 | 25 | kugou.get('/lyric', lyric); 26 | 27 | kugou.get('/top', top); 28 | kugou.get('/topCategory', topCategory); 29 | 30 | kugou.get('/comment', comment); 31 | kugou.get('/replyComment', replyComment); 32 | 33 | // kugou.get('/singer/info', singer_Info); 34 | // kugou.get('/singer/songList', singer_songList); 35 | 36 | kugou.get('/playlist/tagCategory', playlist_tagCategory); 37 | kugou.get('/playlist/tag', playlist_Tag); 38 | kugou.get('/playlist/info', playlist_Info); 39 | kugou.get('/playlist/list', playlist_list); 40 | kugou.get('/playlist/import', playlist_import); 41 | 42 | kugou.get('/getcookie', getcookie); 43 | // // add post method 44 | kugou.post('/setcookie', setcookie); 45 | 46 | module.exports = kugou; -------------------------------------------------------------------------------- /routes/v1/kugou/cookie.js: -------------------------------------------------------------------------------- 1 | const APIError = require("../../../middlewares/rest").APIError; 2 | 3 | // 解析cookie 4 | const __Cookie = require('../../../util/cookie_util'); 5 | // const { kugou_request } = require("../../../util/kugou_request"); 6 | // const { default: axios } = require("axios"); 7 | 8 | // 设置kugou cookie 9 | let setcookie = async (ctx) => { 10 | // 只允许POST方法 11 | if (ctx.request.method === 'POST') { 12 | var data = ctx.request.body.data || ''; 13 | } 14 | 15 | if (!data) { 16 | throw new APIError('Cookie:data_notfound', 'argument data is not found'); 17 | } 18 | 19 | global.kugou_cookie = __Cookie.parse(data); 20 | // console.log(global.qq_cookie); 21 | 22 | ctx.rest({ 23 | code: '成功', 24 | msg: 'Cookie add successful', 25 | status: 200 26 | }); 27 | } 28 | 29 | // 获取kugou cookie 30 | let getcookie = async (ctx) => { 31 | ctx.rest(global.kugou_cookie); 32 | } 33 | 34 | 35 | module.exports = { 36 | getcookie, 37 | setcookie 38 | } -------------------------------------------------------------------------------- /routes/v1/kugou/lyric.js: -------------------------------------------------------------------------------- 1 | // const { kugou_request } = require("../../../util/kugou_request"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | const axios = require('axios'); 4 | 5 | 6 | const { decodeKgLyric } = require('../../../util/decodeLyric'); 7 | 8 | const decodeName = (str = '') => str.replace(/(?:&|<|>|"|'|')/gm, s => encodeNames[s]) 9 | 10 | const headExp = /^.*\[id:\$\w+\]\n/ 11 | 12 | const parseLyric = str => { 13 | str = str.replace(/\r/g, '') 14 | if (headExp.test(str)) str = str.replace(headExp, '') 15 | let trans = str.match(/\[language:([\w=\\/+]+)\]/) 16 | let lyric 17 | let tlyric 18 | if (trans) { 19 | str = str.replace(/\[language:[\w=\\/+]+\]\n/, '') 20 | let json = JSON.parse(Buffer.from(trans[1], 'base64').toString()) 21 | for (const item of json.content) { 22 | if (item.type == 1) { 23 | tlyric = item.lyricContent 24 | break 25 | } 26 | } 27 | } 28 | let i = 0 29 | let rlyric = str.replace(/\[((\d+),\d+)\].*/g, str => { 30 | let result = str.match(/\[((\d+),\d+)\].*/) 31 | let time = parseInt(result[2]) 32 | let ms = time % 1000 33 | time /= 1000 34 | let m = parseInt(time / 60).toString().padStart(2, '0') 35 | time %= 60 36 | let s = parseInt(time).toString().padStart(2, '0') 37 | time = `${m}:${s}.${ms}` 38 | if (tlyric) tlyric[i] = `[${time}]${tlyric[i++][0]}` 39 | return str.replace(result[1], time) 40 | }) 41 | tlyric = tlyric ? tlyric.join('\n') : '' 42 | rlyric = rlyric.replace(/<(\d+,\d+),\d+>/g, '<$1>') 43 | rlyric = decodeName(rlyric) 44 | lyric = rlyric.replace(/<\d+,\d+>/g, '') 45 | tlyric = decodeName(tlyric) 46 | return { 47 | lyric, 48 | tlyric, 49 | rlyric, 50 | } 51 | } 52 | 53 | let searchLyric = async (hash, tryNum = 0) => { 54 | let result = await axios.get(`http://lyrics.kugou.com/search?ver=1&man=yes&client=pc&hash=${hash}`, {/* &keyword=${encodeURI(name)} */ 55 | headers: { 56 | 'KG-RC': 1, 57 | 'KG-THash': 'expand_search_manager.cpp:852736169:451', 58 | 'User-Agent': 'KuGou2012-9020-ExpandSearchManager', 59 | }, 60 | }); 61 | 62 | if (result.status !== 200) { 63 | if (tryNum > 3) throw new APIError('searchLyric', 'searchLyric is error'); 64 | let result_ = await searchLyric(hash, ++tryNum); 65 | return result_ 66 | } 67 | 68 | if (result.data.candidates.length) { 69 | let info = result.data.candidates[0] 70 | return { id: info.id, accessKey: info.accesskey } 71 | } 72 | return null; 73 | } 74 | 75 | 76 | let getLyricDownload = async (id, accessKey, tryNum = 0) => { 77 | let result = await axios.get(`http://lyrics.kugou.com/download?ver=1&client=pc&id=${id}&accesskey=${accessKey}&fmt=krc&charset=utf8`, { 78 | headers: { 79 | 'KG-RC': 1, 80 | 'KG-THash': 'expand_search_manager.cpp:852736169:451', 81 | 'User-Agent': 'KuGou2012-9020-ExpandSearchManager', 82 | }, 83 | }) 84 | if (result.status !== 200) { 85 | if (tryNum > 3) throw new APIError('getLyric', 'getLyricDownload is error'); 86 | let result_ = await searchLyric(hash, ++tryNum); 87 | return result_ 88 | } 89 | 90 | return decodeKgLyric(result.data.content).then(result => parseLyric(result)) 91 | } 92 | 93 | let getLyric = async (songInfo, tryNum = 0) => { 94 | let result = await searchLyric(songInfo.hash); 95 | 96 | if (!result) return { lyric: null, tlyric: null, rlyric: null } 97 | 98 | let requestObj = await getLyricDownload(result.id, result.accessKey) 99 | 100 | return requestObj 101 | } 102 | 103 | let lyric = async (ctx) => { 104 | if (ctx.request.method === 'GET') { 105 | // var name = ctx.request.query.name || '花海'; 106 | var hash = ctx.request.query.hash || '2FF4014692AC079A9B8118966C891897'; 107 | } else if (ctx.request.method === 'POST') { 108 | // var name = ctx.request.body.name || '花海'; 109 | var hash = ctx.request.body.hash || '2FF4014692AC079A9B8118966C891897'; 110 | } 111 | 112 | const cacheData = global.cache.get(ctx.request.url); 113 | if (cacheData) { 114 | ctx.rest(cacheData); 115 | return; 116 | } 117 | 118 | let result = await getLyric({ hash }); 119 | 120 | global.cache.set(ctx.request.url, result); 121 | 122 | ctx.rest(result); 123 | result = null; 124 | } 125 | 126 | module.exports = { 127 | lyric 128 | } -------------------------------------------------------------------------------- /routes/v1/kugou/pic.js: -------------------------------------------------------------------------------- 1 | const APIError = require("../../../middlewares/rest").APIError; 2 | const axios = require('axios'); 3 | 4 | 5 | let getPic = async (songInfo) => { 6 | 7 | if (ctx.request.method === 'GET') { 8 | // var name = ctx.request.query.name || '花海'; 9 | var hash = ctx.request.query.hash || '2FF4014692AC079A9B8118966C891897'; 10 | var songmid = ctx.request.query.songmid || '2FF4014692AC079A9B8118966C891897'; 11 | var singer = ctx.request.query.songmid || '2FF4014692AC079A9B8118966C891897'; 12 | } else if (ctx.request.method === 'POST') { 13 | // var name = ctx.request.body.name || '花海'; 14 | var hash = ctx.request.body.hash || '2FF4014692AC079A9B8118966C891897'; 15 | var songmid = ctx.request.body.songmid || '2FF4014692AC079A9B8118966C891897'; 16 | var songmid = ctx.request.body.songmid || '2FF4014692AC079A9B8118966C891897'; 17 | } 18 | 19 | const result = await axios.post( 20 | 'http://media.store.kugou.com/v1/get_res_privilege', 21 | { 22 | method: 'POST', 23 | headers: { 24 | 'KG-RC': 1, 25 | 'KG-THash': 'expand_search_manager.cpp:852736169:451', 26 | 'User-Agent': 'KuGou2012-9020-ExpandSearchManager', 27 | }, 28 | body: { 29 | appid: 1001, 30 | area_code: '1', 31 | behavior: 'play', 32 | clientver: '9020', 33 | need_hash_offset: 1, 34 | relate: 1, 35 | resource: [ 36 | { 37 | album_audio_id: 38 | songInfo.songmid.length == 32 // 修复歌曲ID存储变更导致图片获取失败的问题 39 | ? songInfo.audioId.split('_')[0] 40 | : songInfo.songmid, 41 | album_id: songInfo.albumId, 42 | hash: songInfo.hash, 43 | id: 0, 44 | name: `${songInfo.singer} - ${songInfo.name}.mp3`, 45 | type: 'audio', 46 | }, 47 | ], 48 | token: '', 49 | userid: 2626431536, 50 | vip: 1, 51 | }, 52 | }, 53 | ) 54 | 55 | if (result.data.error_code !== 0) return Promise.reject('图片获取失败') 56 | let info = result.data.data[0].info 57 | const img = info.imgsize ? info.image.replace('{size}', info.imgsize[0]) : info.image 58 | if (!img) return Promise.reject('Pic get failed') 59 | return img 60 | 61 | return requestObj 62 | } 63 | 64 | module.exports = { 65 | getPic 66 | } -------------------------------------------------------------------------------- /routes/v1/kugou/song.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios"); 2 | const { mid } = require("../../../util/kugou_mid"); 3 | const { kugou_request } = require("../../../util/kugou_request"); 4 | const APIError = require("../../../middlewares/rest").APIError; 5 | 6 | 7 | let song = async (ctx) => { 8 | 9 | if (ctx.request.method === 'GET') { 10 | var aid = ctx.request.query.aid || '978031'; 11 | var hash = ctx.request.query.hash || '6b20b79b951cbd33b7db57a4c3abc3c6'; 12 | var album_audio_id = ctx.request.query.album_audio_id || '64531905'; 13 | } else if (ctx.request.method === 'POST') { 14 | var aid = ctx.request.body.aid || '978031'; 15 | var hash = ctx.request.body.hash || '6b20b79b951cbd33b7db57a4c3abc3c6'; 16 | // var br = ctx.request.body.br || '320'; 17 | } 18 | 19 | const cacheData = global.cache.get(ctx.request.url); 20 | if (cacheData) { 21 | ctx.rest(cacheData); 22 | return; 23 | } 24 | 25 | 26 | // https://wwwapi.kugou.com/yy/index.php?r=play/getdata&hash=070248CD678149DDDFE5388A43BF1690&mid=0e80d32d772ad376377c68a2708ac92d&album_id=56495618 27 | // https://wwwapi.kugou.com/yy/index.php?r=play/getdata&hash=7E9BDD9E6F442397942B5E7B7916BAB7&mid=0e80d32d772ad376377c68a2708ac92d&album_id=56495618 28 | //新的url https://wwwapi.kugou.com/yy/index.php?r=play/getdata&hash=7E9BDD9E6F442397942B5E7B7916BAB7&mid=0e80d32d772ad376377c68a2708ac92d&album_id=56495618 29 | let result = await kugou_request('https://wwwapi.kugou.com/yy/index.php', { 30 | r: 'play/getdata', 31 | hash: hash.trim(), 32 | mid: global.kugou_cookie.mid ? global.kugou_cookie.mid : mid(), 33 | album_id: aid.trim() 34 | // album_audio_id: album_audio_id.trim(), 35 | // appid: 1014, 36 | // platid: 4, 37 | // '_': Date.now(), 38 | // dfid: global.kugou_cookie.dfid ? global.kugou_cookie.dfid : global.kugou_cookie.kg_dfid 39 | }, 1); 40 | 41 | if (result.data.err_code.toString() == '30020' || result.data.err_code.toString() == '20010') { 42 | // global.kugou_cookie = ''; 43 | throw new APIError("Cookie:ERROR", "Please set cookie") 44 | } 45 | // console.log(result); 46 | // 捕获序列化json出错,防止程序异常退出 47 | if (result.data === 'failed') { 48 | throw new APIError("Song:url_notfound", "Song url is not found") 49 | } 50 | 51 | global.cache.set(ctx.request.url, result.data); 52 | 53 | ctx.rest(result.data); 54 | } 55 | 56 | 57 | 58 | 59 | let getsong = async (ctx) => { 60 | 61 | if (ctx.request.method === 'GET') { 62 | var aid = ctx.request.query.aid || '973251'; 63 | var hash = ctx.request.query.hash || 'E0D2FE9DB754150BBA3CB042C5E8A498'; 64 | var album_audio_id = ctx.request.query.album_audio_id || '64531905'; 65 | } else if (ctx.request.method === 'POST') { 66 | var aid = ctx.request.body.aid || '978031'; 67 | var hash = ctx.request.body.hash || '6b20b79b951cbd33b7db57a4c3abc3c6'; 68 | // var br = ctx.request.body.br || '320'; 69 | } 70 | 71 | const cacheData = global.cache.get(ctx.request.url); 72 | if (cacheData) { 73 | ctx.rest(cacheData); 74 | return; 75 | } 76 | 77 | 78 | // https://wwwapi.kugou.com/yy/index.php?r=play/getdata&hash=070248CD678149DDDFE5388A43BF1690&mid=0e80d32d772ad376377c68a2708ac92d&album_id=56495618 79 | // https://wwwapi.kugou.com/yy/index.php?r=play/getdata&hash=7E9BDD9E6F442397942B5E7B7916BAB7&mid=0e80d32d772ad376377c68a2708ac92d&album_id=56495618 80 | //新的url https://wwwapi.kugou.com/yy/index.php?r=play/getdata&hash=7E9BDD9E6F442397942B5E7B7916BAB7&mid=0e80d32d772ad376377c68a2708ac92d&album_id=56495618 81 | let result = await axios.get('https://wwwapi.kugou.com/yy/index.php', { 82 | params: { 83 | r: 'play/getdata', 84 | hash: hash.trim(), 85 | mid: mid(), 86 | album_id: aid.trim() 87 | // album_audio_id: album_audio_id.trim(), 88 | // appid: 1014, 89 | // platid: 4, 90 | // '_': Date.now(), 91 | // dfid: global.kugou_cookie.dfid ? global.kugou_cookie.dfid : global.kugou_cookie.kg_dfid 92 | }, 93 | headers: { 94 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0', 95 | Referer: 'https://www.kugou.com/' 96 | } 97 | }); 98 | 99 | // if(result.data.err_code.toString() == '30020' || result.data.err_code.toString() == '20010') { 100 | // // global.kugou_cookie = ''; 101 | // throw new APIError("Cookie:ERROR", "Please set cookie") 102 | // } 103 | // console.log(result); 104 | // 捕获序列化json出错,防止程序异常退出 105 | // if (result.data === 'failed') { 106 | // throw new APIError("Song:url_notfound", "Song url is not found") 107 | // } 108 | 109 | global.cache.set(ctx.request.url, result.data); 110 | 111 | ctx.rest(result.data); 112 | } 113 | 114 | 115 | // let songInfo = async (ctx) => { 116 | 117 | // if (ctx.request.method === 'GET') { 118 | // var rid = ctx.request.query.rid || '156483846'; 119 | // } else if (ctx.request.method === 'POST') { 120 | // var rid = ctx.request.body.rid || '156483846'; 121 | // } 122 | 123 | // let result = await kuwo_request('http://kuwo.cn/api/www/music/musicInfo', { 124 | // mid: rid.trim(), 125 | // httpsStatus: 1, 126 | // reqId: 'e3f36a20-4c05-11eb-b0b7-8b03aa7e4b0d' 127 | // }); 128 | 129 | // ctx.rest(result.data); 130 | // } 131 | 132 | module.exports = { 133 | song, 134 | // songInfo 135 | getsong 136 | } -------------------------------------------------------------------------------- /routes/v1/kugou/top.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios"); 2 | const { kugou_request } = require("../../../util/kugou_request"); 3 | const APIError = require("../../../middlewares/rest").APIError; 4 | 5 | // [{ id: 'kg__8888', name: '酷狗TOP500', bangid: '8888' }, { id: 'kg__6666', name: '酷狗飙升榜', bangid: '6666' }, { id: 'kg__37361', name: '酷狗雷达榜', bangid: '37361' }, { id: 'kg__23784', name: '网络红歌榜', bangid: '23784' }, { id: 'kg__24971', name: 'DJ热歌榜', bangid: '24971' }, { id: 'kg__35811', name: '会员专享热歌榜', bangid: '35811' }, { id: 'kg__31308', name: '华语新歌榜', bangid: '31308' }, { id: 'kg__31310', name: '欧美新歌榜', bangid: '31310' }, { id: 'kg__31311', name: '韩国新歌榜', bangid: '31311' }, { id: 'kg__31312', name: '日本新歌榜', bangid: '31312' }, { id: 'kg__31313', name: '粤语新歌榜', bangid: '31313' }, { id: 'kg__33162', name: 'ACG新歌榜', bangid: '33162' }, { id: 'kg__21101', name: '酷狗分享榜', bangid: '21101' }, { id: 'kg__30972', name: '腾讯音乐人原创榜', bangid: '30972' }, { id: 'kg__22603', name: '5sing音乐榜', bangid: '22603' }, { id: 'kg__33160', name: '电音热歌榜', bangid: '33160' }, { id: 'kg__21335', name: '繁星音乐榜', bangid: '21335' }, { id: 'kg__33161', name: '古风新歌榜', bangid: '33161' }, { id: 'kg__33163', name: '影视金曲榜', bangid: '33163' }, { id: 'kg__33166', name: '欧美金曲榜', bangid: '33166' }, { id: 'kg__33165', name: '粤语金曲榜', bangid: '33165' }, { id: 'kg__36107', name: '小语种热歌榜', bangid: '36107' }, { id: 'kg__4681', name: '美国BillBoard榜', bangid: '4681' }, { id: 'kg__4680', name: '英国单曲榜', bangid: '4680' }, { id: 'kg__4673', name: '日本公信榜', bangid: '4673' }, { id: 'kg__38623', name: '韩国Melon音乐榜', bangid: '38623' }, { id: 'kg__42807', name: 'joox本地热歌榜', bangid: '42807' }, { id: 'kg__42808', name: '台湾KKBOX风云榜', bangid: '42808' }] 6 | // 排行榜歌曲详情 7 | let top = async (ctx) => { 8 | if (ctx.request.method === 'GET') { 9 | var topId = ctx.request.query.topId || '8888'; 10 | // var limit = ctx.request.query.limit || '30'; 11 | var offset = ctx.request.query.offset || '1'; 12 | var platform = ctx.request.query.platform || 'web'; 13 | } else if (ctx.request.method === 'POST') { 14 | var topId = ctx.request.body.topId || '8888'; 15 | // var limit = ctx.request.body.limit || '30'; 16 | var offset = ctx.request.body.offset || '1'; 17 | var platform = ctx.request.body.platform || 'web'; 18 | } 19 | 20 | const cacheData = global.cache.get(ctx.request.url); 21 | if (cacheData) { 22 | ctx.rest(cacheData); 23 | return; 24 | } 25 | 26 | // 酷狗 leaderboard 接口走爬虫 以下是正则表达式 27 | let regExps = { 28 | total: /total: '(\d+)',/, 29 | page: /page: '(\d+)',/, 30 | limit: /pagesize: '(\d+)',/, 31 | listData: /global\.features = (\[.+\]);/, 32 | }; 33 | 34 | 35 | let result = null; 36 | // 新增pc端 排行榜接口 37 | if (platform === 'pc') { 38 | result = await axios.post(`http://kmr.service.kugou.com/container/v2/rank_audio`, {"appid":1001,"clientver":"10053","mid":"70a02aad1ce4648e7dca77f2afa7b182","clienttime":1661650645,"key":"f1813b8c45644335d20b5054e255f8c5","area_code":"1","show_video":1,"page":1,"pagesize":500,"rank_id":topId,"rank_cid":topId,"zone":"tx6_gz_kmr"}) 39 | global.cache.set(ctx.request.url, result.data, 3600); 40 | ctx.rest(result.data); 41 | } else { 42 | result = await kugou_request(`http://www2.kugou.kugou.com/yueku/v9/rank/home/${offset}-${topId}.html`); 43 | 44 | // 正则匹配 45 | let total = regExps.total.exec(result.data); 46 | let page = regExps.page.exec(result.data); 47 | let limit = regExps.limit.exec(result.data); 48 | let listData = regExps.listData.exec(result.data); 49 | 50 | global.cache.set(ctx.request.url, { 51 | code: 'success', 52 | msg: 'leaderboard', 53 | total: JSON.parse(total[1]), 54 | page: JSON.parse(page[1]), 55 | limit: JSON.parse(limit[1]), 56 | data: JSON.parse(listData[1]) 57 | }, 3600); 58 | 59 | 60 | // 返回新数据,自己拼接的json数据 61 | ctx.rest({ 62 | code: 'success', 63 | msg: 'leaderboard', 64 | total: JSON.parse(total[1]), 65 | page: JSON.parse(page[1]), 66 | limit: JSON.parse(limit[1]), 67 | data: JSON.parse(listData[1]) 68 | }); 69 | } 70 | } 71 | 72 | // 排行榜分类 73 | let topCategory = async (ctx) => { 74 | const cacheData = global.cache.get(ctx.request.url); 75 | if (cacheData) { 76 | ctx.rest(cacheData); 77 | return; 78 | } 79 | 80 | 81 | // let result = await kugou_request('http://mobilecdnbj.kugou.com/api/v3/rank/list?version=9108&plat=0&showtype=2&parentid=0&apiver=6&area_code=1&withsong=1'); 82 | let result = await axios.get(`https://gateway.kugou.com/ocean/v6/rank/list?srcappid=2919&dfid=10xLht0e9p5G2Gfkup4IHVuV&mid=212826578698488017179831213621749832494&signature=52321c148cf00a55c64f8534e5e6929f&clienttime=1661880203&uuid=4f3e2278033606d95d92efddc0744d9c&area_code=1&apiver=14&plat=1&withsong=1&showtype=2&clientver=11289&parentid=0&version=11289&cctv=1`); 83 | 84 | // result.data.data.info.splice(2, 2); 85 | // result.data.data.total = 31; 86 | 87 | global.cache.set(ctx.request.url, result.data, 3600); 88 | 89 | ctx.rest(result.data); 90 | result = null; 91 | } 92 | 93 | module.exports = { 94 | top, 95 | topCategory 96 | } -------------------------------------------------------------------------------- /routes/v1/kuwo/comment.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios"); 2 | const qs = require("qs"); 3 | const APIError = require("../../../middlewares/rest").APIError; 4 | 5 | let comment = async (ctx) => { 6 | if (ctx.request.method === 'GET') { 7 | var id = ctx.request.query.id || ''; 8 | var offset = ctx.request.query.offset || '1'; 9 | var limit = ctx.request.query.limit || '30'; 10 | var type = ctx.request.query.type || '0'; 11 | } else if (ctx.request.method === 'POST') { 12 | var id = ctx.request.body.id || ''; 13 | var offset = ctx.request.body.offset || '1'; 14 | var limit = ctx.request.body.limit || '30'; 15 | var type = ctx.request.body.type || '0'; 16 | } 17 | 18 | const cacheData = global.cache.get(ctx.request.url); 19 | if (cacheData) { 20 | ctx.rest(cacheData); 21 | return; 22 | } 23 | 24 | if (!id) { 25 | throw new APIError("argument_error", "id is not found") 26 | } 27 | 28 | 29 | let result = null; 30 | if (type == '0') { 31 | result = await axios.get(`http://comment.kuwo.cn/com.s?type=get_comment&uid=0&digest=15&sid=${id}&page=${offset}&rows=${limit}&f=web&prod=MUSIC_8.7.7.0_BCS37&devid=28556413`, 32 | { 33 | headers: { 34 | 'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 9;)', 35 | } 36 | } 37 | ); 38 | } else { 39 | result = await axios.get(`http://comment.kuwo.cn/com.s?type=get_rec_comment&uid=0&digest=15&sid=${id}&page=${offset}&rows=${limit}&f=web&prod=MUSIC_8.7.7.0_BCS37&devid=28556413`, 40 | { 41 | headers: { 42 | 'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 9;)', 43 | } 44 | } 45 | ); 46 | } 47 | 48 | global.cache.set(ctx.request.url, result ? result.data : {}); 49 | 50 | ctx.rest(result ? result.data : {}); 51 | } 52 | 53 | 54 | 55 | 56 | module.exports = { 57 | comment 58 | } -------------------------------------------------------------------------------- /routes/v1/kuwo/controllers.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router'); 2 | 3 | const { search, hotSearch, suggestSearch } = require('./search'); 4 | const { song, songInfo } = require('./song'); 5 | const { top, topCategory } = require('./top'); 6 | const { lyric } = require('./lyric'); 7 | const { playlist_tagCategory, playlist_Tag, playlist_Info, playlist_import } = require('./playlist'); 8 | const { comment } = require('./comment'); 9 | 10 | // 新建 咪咕 路由 11 | const kuwo = new Router(); 12 | 13 | // add get method 14 | 15 | kuwo.get('/search', search); 16 | kuwo.get('/hotSearch', hotSearch); 17 | kuwo.get('/suggestSearch', suggestSearch); 18 | 19 | kuwo.get('/song', song); 20 | kuwo.get('/songInfo', songInfo); 21 | 22 | kuwo.get('/lyric', lyric); 23 | 24 | kuwo.get('/top', top); 25 | kuwo.get('/topCategory', topCategory); 26 | 27 | kuwo.get('/comment', comment); 28 | 29 | // kuwo.get('/singer/info', singer_Info); 30 | // kuwo.get('/singer/songList', singer_songList); 31 | 32 | kuwo.get('/playlist/tagCategory', playlist_tagCategory); 33 | kuwo.get('/playlist/tag', playlist_Tag); 34 | kuwo.get('/playlist/info', playlist_Info); 35 | kuwo.get('/playlist/import', playlist_import); 36 | 37 | // // add post method 38 | // kuwo.post('/song', song); 39 | 40 | module.exports = kuwo; -------------------------------------------------------------------------------- /routes/v1/kuwo/lyric.js: -------------------------------------------------------------------------------- 1 | const { kuwo_request } = require("../../../util/kuwo_request"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | const axios = require('axios'); 4 | 5 | const { decodeKwLyric } = require('../../../util/decodeLyric'); 6 | 7 | const decodeName = (str = '') => str ? str.replace(/(?:&|<|>|"|'|')/gm, s => encodeNames[s]) : ''; 8 | 9 | let lyric = async (ctx) => { 10 | if (ctx.request.method === 'GET') { 11 | var rid = ctx.request.query.rid || '80488731'; 12 | var from = ctx.request.query.from || 'pc'; 13 | } else if (ctx.request.method === 'POST') { 14 | var rid = ctx.request.body.rid || '80488731'; 15 | var from = ctx.request.body.from || 'pc'; 16 | } 17 | 18 | const cacheData = global.cache.get(ctx.request.url); 19 | if (cacheData) { 20 | ctx.rest(cacheData); 21 | return; 22 | } 23 | 24 | if (from === 'pc') { 25 | let lyric_str = await axios.get(`http://m.kuwo.cn/newh5/singles/songinfoandlrc?musicId=${rid.trim()}`); 26 | 27 | if (!lyric_str.data.data.lrclist.length) throw new APIError('KuwoLyric', 'Get lyric failed'); 28 | 29 | let lrcInfo 30 | try { 31 | lrcInfo = sortLrcArr(lyric_str.data.data.lrclist) 32 | } catch { 33 | throw new APIError('KuwoLyric', 'Get lyric failed'); 34 | } 35 | 36 | global.cache.set(ctx.request.url, { 37 | lyric_str: decodeName(transformLrc(lyric_str.data.data.songinfo, lrcInfo.lrc)), 38 | tlyric: lrcInfo.lrcT.length ? decodeName(transformLrc(lyric_str.data.data.songinfo, lrcInfo.lrcT)) : '' 39 | }); 40 | 41 | 42 | ctx.rest({ 43 | lyric_str: decodeName(transformLrc(lyric_str.data.data.songinfo, lrcInfo.lrc)), 44 | tlyric: lrcInfo.lrcT.length ? decodeName(transformLrc(lyric_str.data.data.songinfo, lrcInfo.lrcT)) : '' 45 | }); 46 | } else { 47 | var result = await kuwo_request(`http://m.kuwo.cn/newh5/singles/songinfoandlrc`, { 48 | musicId: rid.trim(), 49 | reqId: 'f8081460-9318-11ec-b5ec-39270da8c956' 50 | }); 51 | 52 | global.cache.set(ctx.request.url, result.data); 53 | 54 | ctx.rest(result.data); 55 | } 56 | } 57 | 58 | let transformLrc = (songinfo, lrclist) => { 59 | return `[ti:${songinfo.songName}]\n[ar:${songinfo.artist}]\n[al:${songinfo.album}]\n[by:]\n[offset:0]\n${lrclist ? lrclist.map(l => `[${formatTime(l.time)}]${l.lineLyric}\n`).join('') : '暂无歌词'}` 60 | } 61 | 62 | let formatTime = (time) => { 63 | let m = parseInt(time / 60) 64 | let s = (time % 60).toFixed(2) 65 | return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s) 66 | } 67 | 68 | let sortLrcArr = (arr) => { 69 | const lrcSet = new Set() 70 | let lrc = [] 71 | let lrcT = [] 72 | 73 | for (const item of arr) { 74 | if (lrcSet.has(item.time)) { 75 | const tItem = lrc.pop() 76 | tItem.time = lrc[lrc.length - 1].time 77 | lrcT.push(tItem) 78 | lrc.push(item) 79 | } else { 80 | lrc.push(item) 81 | lrcSet.add(item.time) 82 | } 83 | } 84 | 85 | if (lrcT.length && lrc.length > lrcT.length) { 86 | const tItem = lrc.pop() 87 | tItem.time = lrc[lrc.length - 1].time 88 | lrcT.push(tItem) 89 | } 90 | 91 | return { 92 | lrc, 93 | lrcT, 94 | } 95 | } 96 | 97 | /* 98 | let lyric = async (ctx) => { 99 | if (ctx.request.method === 'GET') { 100 | var rid = ctx.request.query.rid || '80488731'; 101 | var from = ctx.request.query.from || 'pc'; 102 | // console.log(typeof ctx.request.query.limit, limit); 103 | } else if (ctx.request.method === 'POST') { 104 | var rid = ctx.request.body.rid || '80488731'; 105 | var from = ctx.request.body.from || 'pc'; 106 | } 107 | 108 | if (from === 'pc') { 109 | let lyric_str = await getLyric(rid); 110 | if (lyric_str === 'lyric not found') { 111 | throw new APIError('KuwoLyric', lyric_str); 112 | } 113 | ctx.rest({ 114 | lyric_str 115 | }); 116 | } else { 117 | var result = await kuwo_request(`http://m.kuwo.cn/newh5/singles/songinfoandlrc`, { 118 | musicId: rid.trim(), 119 | reqId: 'f8081460-9318-11ec-b5ec-39270da8c956' 120 | }); 121 | ctx.rest(result.data); 122 | } 123 | } 124 | 125 | let getLyric = async (rid, isGetLyricx = false) => { 126 | let res = await axios.get(`http://player.kuwo.cn/webmusic/st/getNewMuiseByRid?rid=MUSIC_${rid.trim()}`); 127 | let info = parseLyricInfo(res.data); 128 | 129 | if (!info) { 130 | return 'lyric not found'; 131 | } 132 | 133 | let lyric = await axios.get(`http://newlyric.kuwo.cn/newlyric.lrc?${isGetLyricx ? info.lyric_zz : info.lyric}`, { 134 | responseType: 'arraybuffer' 135 | }); 136 | 137 | let base64Data = await decodeKwLyric({ lrcBase64: lyric.data.toString('base64'), isGetLyricx }); 138 | return Buffer.from(base64Data, 'base64').toString(); 139 | } 140 | 141 | parseLyricInfo = (str) => { 142 | let lrcInfoRxp = /(.+?)<\/lyric>[\s\S]+(.+?)<\/lyric_zz>/; 143 | let result = str.match(lrcInfoRxp); 144 | return result ? { lyric: result[1], lyric_zz: result[2] } : null 145 | } 146 | */ 147 | 148 | module.exports = { 149 | lyric 150 | } -------------------------------------------------------------------------------- /routes/v1/kuwo/playlist.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios"); 2 | const { kuwo_request } = require("../../../util/kuwo_request"); 3 | const APIError = require("../../../middlewares/rest").APIError; 4 | 5 | 6 | // 歌单标签分类 7 | let playlist_tagCategory = async (ctx) => { 8 | 9 | const cacheData = global.cache.get(ctx.request.url); 10 | if (cacheData) { 11 | ctx.rest(cacheData); 12 | return; 13 | } 14 | 15 | let result = await kuwo_request(`http://kuwo.cn/api/www/playlist/getTagList`, { 16 | httpsStatus: 1, 17 | reqId: 'fe4ca711-9317-11ec-b143-115a6c5b31c8' 18 | }); 19 | 20 | global.cache.set(ctx.request.url, result.data); 21 | 22 | ctx.rest(result.data); 23 | } 24 | 25 | // 歌单标签下的歌单列表 26 | let playlist_Tag = async (ctx) => { 27 | 28 | if (ctx.request.method === 'GET') { 29 | var id = ctx.request.query.id || '146'; 30 | var flag = ctx.request.query.flag || '0'; 31 | var order = ctx.request.query.order || 'hot'; 32 | var limit = ctx.request.query.limit || '30'; 33 | var offset = ctx.request.query.offset || '1'; 34 | } else if (ctx.request.method === 'POST') { 35 | var id = ctx.request.body.id || '146'; 36 | var flag = ctx.request.body.flag || '0'; 37 | var order = ctx.request.body.order || 'hot'; 38 | var limit = ctx.request.body.limit || '30'; 39 | var offset = ctx.request.body.offset || '1'; 40 | } 41 | 42 | const cacheData = global.cache.get(ctx.request.url); 43 | if (cacheData) { 44 | ctx.rest(cacheData); 45 | return; 46 | } 47 | 48 | // 如果flag field value equal one ,不走 最新、最热 歌单分类 路由 49 | if (flag === '1') { 50 | var result = await kuwo_request(`http://kuwo.cn/api/pc/classify/playlist/getTagPlayList`, { 51 | pn: offset.trim(), 52 | rn: limit.trim(), 53 | id: id.trim(), 54 | httpsStatus: 1, 55 | reqId: 'fe4ca711-9317-11ec-b143-115a6c5b31c8' 56 | }); 57 | } else { 58 | if (order.trim() !== 'new' && order.trim() !== 'hot') { 59 | throw new APIError("Playlist:order_error", "ardument order is error"); 60 | } 61 | var result = await kuwo_request(`http://kuwo.cn/api/pc/classify/playlist/getRcmPlayList`, { 62 | pn: offset.trim(), 63 | rn: limit.trim(), 64 | order: order.trim(), 65 | httpsStatus: 1, 66 | reqId: 'fe4ca711-9317-11ec-b143-115a6c5b31c8' 67 | }) 68 | } 69 | 70 | global.cache.set(ctx.request.url, result.data); 71 | 72 | ctx.rest(result.data); 73 | } 74 | 75 | 76 | // 歌单的歌曲信息 77 | let playlist_Info = async (ctx) => { 78 | 79 | if (ctx.request.method === 'GET') { 80 | var pid = ctx.request.query.pid || '3127794871'; 81 | var limit = ctx.request.query.limit || '30'; 82 | var offset = ctx.request.query.offset || '1'; 83 | } else if (ctx.request.method === 'POST') { 84 | var pid = ctx.request.body.pid || '3127794871'; 85 | var limit = ctx.request.body.limit || '30'; 86 | var offset = ctx.request.body.offset || '1'; 87 | } 88 | 89 | const cacheData = global.cache.get(ctx.request.url); 90 | if (cacheData) { 91 | ctx.rest(cacheData); 92 | return; 93 | } 94 | 95 | let result = await kuwo_request(`http://kuwo.cn/api/www/playlist/playListInfo`, { 96 | pid: pid.trim(), 97 | pn: offset.trim(), 98 | rn: limit.trim(), 99 | httpsStatus: 1, 100 | reqId: '71e2f370-4c22-11eb-af49-1156a1732ca7' 101 | }); 102 | 103 | global.cache.set(ctx.request.url, result.data); 104 | 105 | ctx.rest(result.data) 106 | } 107 | 108 | 109 | 110 | 111 | //外部导入歌单 => 酷狗码(只支持500首) PC 112 | let playlist_import = async (ctx) => { 113 | 114 | if (ctx.request.method === 'GET') { 115 | var id = ctx.request.query.id || ''; 116 | } else if (ctx.request.method === 'POST') { 117 | var id = ctx.request.body.id || ''; 118 | } 119 | 120 | if (id.trim() === '') { 121 | throw new APIError("Playlist:Error", "id is not found"); 122 | } 123 | 124 | const cacheData = global.cache.get(ctx.request.url); 125 | if (cacheData) { 126 | ctx.rest(cacheData); 127 | return; 128 | } 129 | 130 | 131 | let result = await axios.get(`http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=${id}&pn=0&rn=10000&encode=utf8&keyset=pl2012&identity=kuwo&pcmp4=1&vipver=MUSIC_9.0.5.0_W1&newver=1`); 132 | 133 | 134 | global.cache.set(ctx.request.url, result.data); 135 | 136 | ctx.rest(result.data); 137 | } 138 | 139 | 140 | 141 | module.exports = { 142 | playlist_tagCategory, 143 | playlist_Tag, 144 | playlist_Info, 145 | playlist_import 146 | } -------------------------------------------------------------------------------- /routes/v1/kuwo/search.js: -------------------------------------------------------------------------------- 1 | const { kuwo_request } = require("../../../util/kuwo_request"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | 4 | 5 | let search = async (ctx) => { 6 | if (ctx.request.method === 'GET') { 7 | var key = ctx.request.query.key || ''; 8 | var limit = ctx.request.query.limit || '30'; 9 | var offset = ctx.request.query.offset || '1'; 10 | var from = ctx.request.query.from || 'web'; 11 | // var type = ctx.request.query.type || '2'; 12 | // console.log(typeof ctx.request.query.limit, limit); 13 | } else if (ctx.request.method === 'POST') { 14 | var key = ctx.request.body.key || ''; 15 | var limit = ctx.request.body.limit || '30'; 16 | var offset = ctx.request.body.offset || '1'; 17 | var from = ctx.request.body.from || 'web'; 18 | // var type = ctx.request.body.type || '2'; 19 | } 20 | 21 | const cacheData = global.cache.get(ctx.request.url); 22 | if (cacheData) { 23 | ctx.rest(cacheData); 24 | return; 25 | } 26 | 27 | if (from === 'pc') { 28 | var result = await kuwo_request(`http://search.kuwo.cn/r.s?user=351564826140493&android_id=fa8c43e5884bf6d6&prod=kwplayer_ar_9.3.7.5&corp=kuwo&newver=2&vipver=9.3.7.5&source=kwplayer_ar_9.3.7.5_pcguanwangmobile.apk&p2p=1&loginUid=0&loginSid=0¬race=0&client=kt&all=${encodeURI(key)}&pn=${parseInt(offset.trim())-1}&rn=${limit.trim()}&uid=1904267592&ver=kwplayer_ar_9.3.7.5&vipver=1&show_copyright_off=1&newver=2&correct=1&ft=music&cluster=0&strategy=2012&encoding=utf8&rformat=json&vermerge=1&mobi=1&searchapi=2&issubtitle=1&province=&city=&latitude=&longtitude=&userIP=120.242.190.218&spPrivilege=1`) 29 | } else { 30 | var result = await kuwo_request("http://kuwo.cn/api/www/search/searchMusicBykeyWord", { 31 | key: key, 32 | pn: offset.trim(), 33 | rn: limit.trim(), 34 | httpsStatus: 1, 35 | reqId: 'fe4ca711-9317-11ec-b143-115a6c5b31c8' 36 | }); 37 | } 38 | 39 | global.cache.set(ctx.request.url, result.data); 40 | 41 | // 捕捉服务端解析错误,防止程序退出 42 | ctx.rest(result.data); 43 | result = null; 44 | } 45 | 46 | // 热搜 47 | let hotSearch = async (ctx) => { 48 | if (ctx.request.method === 'GET') { 49 | var from = ctx.request.query.from || 'pc'; 50 | } else if (ctx.request.method === 'POST') { 51 | var from = ctx.request.body.from || 'pc'; 52 | } 53 | 54 | const cacheData = global.cache.get(ctx.request.url); 55 | if (cacheData) { 56 | ctx.rest(cacheData); 57 | return; 58 | } 59 | 60 | if (from === 'pc') { 61 | var result = await kuwo_request('http://m.kuwo.cn/newh5app/api/mobile/v1/search/hotword', null, 0); 62 | } else if (from === 'web') { 63 | var result = await kuwo_request('https://www.kuwo.cn/api/www/search/searchKey', { 64 | // key: '', 65 | // httpsStatus: 1, 66 | // reqId: '4d1b4040-cc34-11ec-9383-858d169c5de7' 67 | }); 68 | } 69 | 70 | global.cache.set(ctx.request.url, result.data); 71 | 72 | ctx.rest(result.data); 73 | result = null; 74 | } 75 | 76 | let suggestSearch = async (ctx) => { 77 | if (ctx.request.method === 'GET') { 78 | var key = ctx.request.query.key || ''; 79 | // console.log(typeof ctx.request.query.limit, limit); 80 | } else if (ctx.request.method === 'POST') { 81 | var key = ctx.request.body.key || ''; 82 | } 83 | 84 | let result = await kuwo_request('http://kuwo.cn/api/www/search/searchKey', { 85 | key: key.trim(), 86 | httpsStatus: 1, 87 | reqId: '06a52ee0-9486-11ec-9372-9b980bbe276f' 88 | }); 89 | ctx.rest(result.data); 90 | result = null; 91 | } 92 | 93 | module.exports = { 94 | search, 95 | hotSearch, 96 | suggestSearch 97 | } -------------------------------------------------------------------------------- /routes/v1/kuwo/song.js: -------------------------------------------------------------------------------- 1 | const { kuwo_request } = require("../../../util/kuwo_request"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | 4 | 5 | let song = async (ctx) => { 6 | 7 | if (ctx.request.method === 'GET') { 8 | var rid = ctx.request.query.rid || '156483846'; 9 | var br = ctx.request.query.br || '320'; 10 | } else if (ctx.request.method === 'POST') { 11 | var rid = ctx.request.body.rid || '156483846'; 12 | var br = ctx.request.body.br || '320'; 13 | } 14 | 15 | const cacheData = global.cache.get(ctx.request.url); 16 | if (cacheData) { 17 | ctx.rest(cacheData); 18 | return; 19 | } 20 | 21 | // aac:24,48 22 | // wma:96,128 23 | // mp3:128,192,320 24 | // ape:1000 25 | // flac:2000 无法获取,等待以后逆向 26 | // let format = 'mp3'; 27 | // switch (br) { 28 | // case '24': 29 | // case '48': 30 | // format = 'aac'; 31 | // break; 32 | // case '96': 33 | // format = 'wma'; 34 | // break; 35 | // case '128': 36 | // case '192': 37 | // case '320': 38 | // format = 'mp3'; 39 | // break; 40 | // case '1000': 41 | // format = 'ape'; 42 | // break; 43 | // case '2000': 44 | // format = 'flac'; 45 | // break; 46 | // default: 47 | // format = 'format_error'; 48 | // } 49 | 50 | // if (format === 'format_error') { 51 | // throw new APIError("Song:br_error", "br is not 24, 48, 96, 128, 192, 320, 1000"); 52 | // } 53 | 54 | // let result = await kuwo_request('http://kuwo.cn/url', { 55 | // br: `${br}k${format}`, 56 | // type: 'convert_url3', 57 | // rid: rid.trim() 58 | // // from: 'pc' 59 | // }) 60 | // console.log(result); 61 | // 捕获序列化json出错,防止程序异常退出 62 | 63 | let result = await kuwo_request('http://www.kuwo.cn/api/v1/www/music/playUrl', { 64 | // br: `${br}k${format}`, 65 | type: 'music', 66 | mid: rid.trim() 67 | // from: 'pc' 68 | }) 69 | if (result.data === 'failed') { 70 | throw new APIError("Song:url_notfound", "Song url is not found") 71 | } 72 | 73 | global.cache.set(ctx.request.url, result.data); 74 | 75 | ctx.rest(result.data); 76 | } 77 | 78 | 79 | let songInfo = async (ctx) => { 80 | 81 | if (ctx.request.method === 'GET') { 82 | var rid = ctx.request.query.rid || '156483846'; 83 | } else if (ctx.request.method === 'POST') { 84 | var rid = ctx.request.body.rid || '156483846'; 85 | } 86 | 87 | const cacheData = global.cache.get(ctx.request.url); 88 | if (cacheData) { 89 | ctx.rest(cacheData); 90 | return; 91 | } 92 | 93 | let result = await kuwo_request('http://kuwo.cn/api/www/music/musicInfo', { 94 | mid: rid.trim(), 95 | httpsStatus: 1, 96 | reqId: 'fe4ca711-9317-11ec-b143-115a6c5b31c8' 97 | }); 98 | 99 | global.cache.set(ctx.request.url, result.data); 100 | 101 | ctx.rest(result.data); 102 | } 103 | 104 | module.exports = { 105 | song, 106 | songInfo 107 | } -------------------------------------------------------------------------------- /routes/v1/kuwo/top.js: -------------------------------------------------------------------------------- 1 | const { kuwo_request } = require("../../../util/kuwo_request"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | 4 | // 排行榜歌单详情 5 | let top = async (ctx) => { 6 | if (ctx.request.method === 'GET') { 7 | var topId = ctx.request.query.topId || '16'; 8 | var limit = ctx.request.query.limit || '100'; 9 | var offset = ctx.request.query.offset || '1'; 10 | var from = ctx.request.query.from || 'web'; 11 | } else if (ctx.request.method === 'POST') { 12 | var topId = ctx.request.body.topId || '16'; 13 | var limit = ctx.request.body.limit || '100'; 14 | var offset = ctx.request.body.offset || '1'; 15 | var from = ctx.request.body.from || 'web'; 16 | } 17 | 18 | const cacheData = global.cache.get(ctx.request.url); 19 | if (cacheData) { 20 | ctx.rest(cacheData); 21 | return; 22 | } 23 | 24 | if (from === 'pc') { 25 | // 理论上讲访问速度更加快 26 | var result = await kuwo_request(`http://kbangserver.kuwo.cn/ksong.s?from=pc&fmt=json&pn=${parseInt(offset) - 1}&rn=${limit}&type=bang&data=content&id=${topId}&show_copyright_off=0&pcmp4=1&isbang=1`); 27 | } else { 28 | var result = await kuwo_request(`http://kuwo.cn/api/www/bang/bang/musicList`, { 29 | bangId: topId.trim(), 30 | pn: offset.trim(), 31 | rn: limit.trim(), 32 | httpsStatus: 1, 33 | reqId: '06a52ee0-9486-11ec-9372-9b980bbe276f' 34 | }); 35 | } 36 | 37 | global.cache.set(ctx.request.url, result.data, 3600); 38 | 39 | ctx.rest(result.data); 40 | result = null; 41 | } 42 | 43 | // 排行榜分类 44 | let topCategory = async (ctx) => { 45 | if (ctx.request.method === 'GET') { 46 | var from = ctx.request.query.from || 'pc'; 47 | } else if (ctx.request.method === 'POST') { 48 | var from = ctx.request.body.from || 'pc'; 49 | } 50 | 51 | const cacheData = global.cache.get(ctx.request.url); 52 | if (cacheData) { 53 | ctx.rest(cacheData); 54 | return; 55 | } 56 | 57 | if (from === 'pc') { 58 | var result = await kuwo_request('http://m.kuwo.cn/newh5app/api/mobile/v1/typelist/rank', null, 0); 59 | } else if (from === 'web') { 60 | var result = await kuwo_request('http://kuwo.cn/api/www/bang/bang/bangMenu', { 61 | httpsStatus: 1, 62 | reqId: '06a52ee0-9486-11ec-9372-9b980bbe276f' 63 | }); 64 | } 65 | 66 | global.cache.set(ctx.request.url, result.data, 3600); 67 | 68 | ctx.rest(result.data); 69 | result = null; 70 | from = null; 71 | } 72 | 73 | module.exports = { 74 | top, 75 | topCategory 76 | } -------------------------------------------------------------------------------- /routes/v1/migu/comment.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios"); 2 | const { migu_request } = require("../../../util/migu_request"); 3 | const APIError = require("../../../middlewares/rest").APIError; 4 | 5 | let comment = async (ctx) => { 6 | if (ctx.request.method === 'GET') { 7 | var id = ctx.request.query.id || ''; 8 | var offset = ctx.request.query.offset || '1'; 9 | var limit = ctx.request.query.limit || '30'; 10 | var type = ctx.request.query.type || '0'; 11 | } else if (ctx.request.method === 'POST') { 12 | var id = ctx.request.body.id || ''; 13 | var offset = ctx.request.body.offset || '1'; 14 | var limit = ctx.request.body.limit || '30'; 15 | var type = ctx.request.body.type || '0'; 16 | } 17 | 18 | const cacheData = global.cache.get(ctx.request.url); 19 | if (cacheData) { 20 | ctx.rest(cacheData); 21 | return; 22 | } 23 | 24 | if (!id) { 25 | throw new APIError("argument_error", "id is not found") 26 | } 27 | 28 | if (parseInt(limit) > 50) limit = '50'; 29 | 30 | 31 | let result = null; 32 | if (type == '0') { 33 | result = await migu_request(`https://music.migu.cn/v3/api/comment/listComments?targetId=${id}&pageSize=${limit}&pageNo=${offset}`); 34 | } else { 35 | result = await migu_request(`https://music.migu.cn/v3/api/comment/listTopComments?targetId=${id}&pageSize=${limit}&pageNo=${offset}`); 36 | } 37 | 38 | global.cache.set(ctx.request.url, result ? result.data : {}); 39 | 40 | ctx.rest(result ? result.data : {}); 41 | } 42 | 43 | 44 | 45 | 46 | let replyComment = async (ctx) => { 47 | if (ctx.request.method === 'GET') { 48 | var commentId = ctx.request.query.commentId || ''; 49 | var offset = ctx.request.query.offset || '1'; 50 | var limit = ctx.request.query.limit || '30'; 51 | } else if (ctx.request.method === 'POST') { 52 | var commentId = ctx.request.body.commentId || ''; 53 | var offset = ctx.request.body.offset || '1'; 54 | var limit = ctx.request.body.limit || '30'; 55 | } 56 | 57 | 58 | const cacheData = global.cache.get(ctx.request.url); 59 | if (cacheData) { 60 | ctx.rest(cacheData); 61 | return; 62 | } 63 | 64 | if (!commentId) { 65 | throw new APIError("argument_error", "commentId is not found") 66 | } 67 | 68 | if (parseInt(limit) > 50) limit = '50'; 69 | 70 | 71 | const result = await migu_request(`https://music.migu.cn/v3/api/comment/listCommentsById?commentId=${commentId}&pageSize=${limit}&pageNo=${offset}`); 72 | 73 | global.cache.set(ctx.request.url, result ? result.data : {}); 74 | 75 | ctx.rest(result ? result.data : {}); 76 | } 77 | 78 | 79 | 80 | 81 | 82 | module.exports = { 83 | comment, 84 | replyComment 85 | } -------------------------------------------------------------------------------- /routes/v1/migu/controllers.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router'); 2 | 3 | const { search, hotSearch, suggestSearch } = require('./search'); 4 | const { song } = require('./song'); 5 | const { lyric } = require('./lyric'); 6 | const { top, topCategory } = require('./top'); 7 | const { singer_Info, singer_songList } = require('./singer'); 8 | const { playList_info } = require('./playlist'); 9 | const { getcookie, setcookie } = require('./cookie'); 10 | const { comment, replyComment } = require('./comment'); 11 | 12 | 13 | // 新建 咪咕 路由 14 | const migu = new Router(); 15 | 16 | // add get method 17 | 18 | migu.get('/search', search); 19 | migu.get('/hotSearch', hotSearch); 20 | migu.get('/suggestSearch', suggestSearch); 21 | 22 | migu.get('/song', song); 23 | 24 | migu.get('/lyric', lyric); 25 | 26 | migu.get('/top', top); 27 | migu.get('/topCategory', topCategory); 28 | 29 | migu.get('/singer/info', singer_Info); 30 | migu.get('/singer/songList', singer_songList); 31 | 32 | migu.get('/comment', comment); 33 | migu.get('/replyComment', replyComment); 34 | 35 | migu.get('/playlist/info', playList_info); 36 | 37 | migu.get('/getcookie', getcookie); 38 | 39 | // add post method 40 | migu.post('/song', song); 41 | // // add post method 42 | migu.post('/setcookie', setcookie); 43 | 44 | module.exports = migu; -------------------------------------------------------------------------------- /routes/v1/migu/cookie.js: -------------------------------------------------------------------------------- 1 | // const { migu_request } = require("../../../util/migu_request"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | 4 | // 解析cookie 5 | const __Cookie = require('../../../util/cookie_util'); 6 | 7 | // 设置qq cookie 8 | let setcookie = async (ctx) => { 9 | // 只允许POST方法 10 | if (ctx.request.method === 'POST') { 11 | var data = ctx.request.body.data || ''; 12 | } 13 | 14 | if (!data) { 15 | throw new APIError('Cookie:data_notfound', 'argument data is not found'); 16 | } 17 | 18 | global.migu_cookie = __Cookie.parse(data); 19 | 20 | ctx.rest({ 21 | code: '成功', 22 | msg: 'Cookie add successful', 23 | status: 200 24 | }); 25 | } 26 | 27 | // 获取qq cookie 28 | let getcookie = async (ctx) => { 29 | 30 | ctx.rest(global.migu_cookie); 31 | } 32 | 33 | module.exports = { 34 | getcookie, 35 | setcookie 36 | } -------------------------------------------------------------------------------- /routes/v1/migu/lyric.js: -------------------------------------------------------------------------------- 1 | const { migu_request } = require("../../../util/migu_request"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | 4 | 5 | let lyric = async (ctx) => { 6 | if (ctx.request.method === 'GET') { 7 | var cid = ctx.request.query.cid || ''; 8 | // console.log(typeof ctx.request.query.limit, limit); 9 | } else if (ctx.request.method === 'POST') { 10 | var cid = ctx.request.body.cid || ''; 11 | } 12 | 13 | const cacheData = global.cache.get(ctx.request.url); 14 | if (cacheData) { 15 | ctx.rest(cacheData); 16 | return; 17 | } 18 | 19 | //https://music.migu.cn/v3/api/music/audioPlayer/getLyric 20 | let result = await migu_request(`https://music.migu.cn/v3/api/music/audioPlayer/getLyric`, { 21 | copyrightId: cid.trim() 22 | }); 23 | 24 | global.cache.set(ctx.request.url, result.data); 25 | 26 | ctx.rest(result.data); 27 | // try { 28 | // ctx.body = JSON.stringify(result.data); 29 | // } catch (error) { 30 | // ctx.body = JSON.stringify({ 31 | // error: '服务端数据解析错误', 32 | // status: 400 33 | // }) 34 | // } 35 | // ctx.type = 'application/json'; 36 | } 37 | 38 | module.exports = { 39 | lyric 40 | } -------------------------------------------------------------------------------- /routes/v1/migu/playlist.js: -------------------------------------------------------------------------------- 1 | const { migu_request } = require("../../../util/migu_request"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | 4 | 5 | // 歌单歌曲 6 | let playList_info = async (ctx) => { 7 | if (ctx.request.method === 'GET') { 8 | var playListId = ctx.request.query.playListId || '179730639'; 9 | var limit = ctx.request.query.limit || '30'; 10 | // console.log(typeof ctx.request.query.limit, limit); 11 | } else if (ctx.request.method === 'POST') { 12 | var playListId = ctx.request.query.playListId || '179730639'; 13 | var limit = ctx.request.body.limit || '30'; 14 | } 15 | 16 | const cacheData = global.cache.get(ctx.request.url); 17 | if (cacheData) { 18 | ctx.rest(cacheData); 19 | return; 20 | } 21 | 22 | let result = await migu_request(`https://m.music.migu.cn/migu/remoting/playlistcontents_query_tag`, { 23 | playListType: 2, 24 | playListId: playListId.trim(), 25 | contentCount: limit.trim() 26 | }); 27 | 28 | global.cache.set(ctx.request.url, result.data); 29 | 30 | ctx.rest(result.data); 31 | 32 | // try { 33 | // ctx.body = JSON.stringify(result.data); 34 | // } catch (error) { 35 | // ctx.body = JSON.stringify({ 36 | // error: '服务端数据解析错误', 37 | // status: 400 38 | // }) 39 | // } 40 | // ctx.type = 'application/json'; 41 | } 42 | 43 | 44 | module.exports = { 45 | playList_info 46 | } -------------------------------------------------------------------------------- /routes/v1/migu/search.js: -------------------------------------------------------------------------------- 1 | const { migu_request } = require("../../../util/migu_request"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | 4 | 5 | // 关键字搜索(type:歌曲 2 歌手:1 专辑: 4 歌单:6 ​MV:5 ​ 歌词:7) 6 | let search = async (ctx) => { 7 | if (ctx.request.method === 'GET') { 8 | var key = ctx.request.query.key || ''; 9 | var limit = ctx.request.query.limit || '30'; 10 | var offset = ctx.request.query.offset || '1'; 11 | var type = ctx.request.query.type || '2'; 12 | // console.log(typeof ctx.request.query.limit, limit); 13 | } else if (ctx.request.method === 'POST') { 14 | var key = ctx.request.body.key || ''; 15 | var limit = ctx.request.body.limit || '30'; 16 | var offset = ctx.request.body.offset || '1'; 17 | var type = ctx.request.body.type || '2'; 18 | } 19 | 20 | 21 | const cacheData = global.cache.get(ctx.request.url); 22 | if (cacheData) { 23 | ctx.rest(cacheData); 24 | return; 25 | } 26 | 27 | //https://music.migu.cn/v3/search?page=1&type=song&i=4d71f8dd249c45d63975c038ecb938529da8c160&f=html&s=1658066679&c=001002A&keyword=周杰伦&v=3.23.4 28 | // url = `https://m.music.migu.cn/migu/remoting/scr_search_tag?rows=${limit}&type=${type}&keyword=${key}&pgc=${offset}`; 29 | let result = await migu_request('http://m.music.migu.cn/migu/remoting/scr_search_tag', { 30 | rows: limit.trim(), 31 | type: type.trim(), 32 | keyword: key, 33 | pgc: offset.trim() 34 | }, 0); 35 | 36 | 37 | global.cache.set(ctx.request.url, result.data); 38 | // 捕捉服务端解析错误,防止程序退出 39 | ctx.rest(result.data); 40 | result = null; 41 | 42 | // try { 43 | // ctx.body = JSON.stringify(result.data); 44 | // } catch (error) { 45 | // ctx.body = JSON.stringify({ 46 | // error: '服务端数据解析错误', 47 | // status: 400 48 | // }) 49 | // } 50 | // ctx.type = 'application/json'; 51 | } 52 | 53 | // 热搜 54 | let hotSearch = async (ctx) => { 55 | const cacheData = global.cache.get(ctx.request.url); 56 | if (cacheData) { 57 | ctx.rest(cacheData); 58 | return; 59 | } 60 | 61 | let result = await migu_request('https://music.migu.cn/v3/api/search/hotwords'); 62 | 63 | global.cache.set(ctx.request.url, result.data); 64 | ctx.rest(result.data); 65 | // try { 66 | // ctx.body = JSON.stringify(result.data); 67 | // } catch (error) { 68 | // ctx.body = JSON.stringify({ 69 | // error: '服务端数据解析错误', 70 | // status: 400 71 | // }) 72 | // } 73 | // ctx.type = 'application/json'; 74 | } 75 | 76 | let suggestSearch = async (ctx) => { 77 | if (ctx.request.method === 'GET') { 78 | var key = ctx.request.query.key || ''; 79 | // console.log(typeof ctx.request.query.limit, limit); 80 | } else if (ctx.request.method === 'POST') { 81 | var key = ctx.request.body.key || ''; 82 | } 83 | 84 | let result = await migu_request(`https://m.music.migu.cn/migu/remoting/autocomplete_tag`, { 85 | keyword: key 86 | }); 87 | 88 | ctx.rest(result.data); 89 | // try { 90 | // ctx.body = JSON.stringify(result.data); 91 | // } catch (error) { 92 | // ctx.body = JSON.stringify({ 93 | // error: '服务端数据解析错误', 94 | // status: 400 95 | // }) 96 | // } 97 | // ctx.type = 'application/json'; 98 | } 99 | 100 | module.exports = { 101 | search, 102 | hotSearch, 103 | suggestSearch 104 | } -------------------------------------------------------------------------------- /routes/v1/migu/singer.js: -------------------------------------------------------------------------------- 1 | const { migu_request } = require("../../../util/migu_request"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | 4 | 5 | // 歌手基本信息显示 6 | let singer_Info = async (ctx) => { 7 | if (ctx.request.method === 'GET') { 8 | var artistId = ctx.request.query.artistId || '18196'; 9 | // console.log(typeof ctx.request.query.limit, limit); 10 | } else if (ctx.request.method === 'POST') { 11 | var artistId = ctx.request.body.artistId || '18196'; 12 | } 13 | 14 | const cacheData = global.cache.get(ctx.request.url); 15 | if (cacheData) { 16 | ctx.rest(cacheData); 17 | return; 18 | } 19 | 20 | let result = await migu_request(`https://m.music.migu.cn/migu/remoting/cms_artist_detail_tag`, { 21 | artistId: artistId.trim() 22 | }); 23 | 24 | global.cache.set(ctx.request.url, result.data); 25 | 26 | ctx.rest(result.data); 27 | // try { 28 | // ctx.body = JSON.stringify(result.data); 29 | // } catch (error) { 30 | // ctx.body = JSON.stringify({ 31 | // error: '服务端数据解析错误', 32 | // status: 400 33 | // }) 34 | // } 35 | // ctx.type = 'application/json'; 36 | } 37 | 38 | // 歌手的相关歌曲显示 39 | // 每一页数据都是 默认 20 首 40 | let singer_songList = async (ctx) => { 41 | if (ctx.request.method === 'GET') { 42 | var artistId = ctx.request.query.artistId || '112'; 43 | var offset = ctx.request.query.offset || '1'; 44 | // console.log(typeof ctx.request.query.limit, limit); 45 | } else if (ctx.request.method === 'POST') { 46 | var artistId = ctx.request.query.artistId || '112'; 47 | var offset = ctx.request.body.offset || '1'; 48 | } 49 | 50 | const cacheData = global.cache.get(ctx.request.url); 51 | if (cacheData) { 52 | ctx.rest(cacheData); 53 | return; 54 | } 55 | 56 | let result = await migu_request(`https://music.migu.cn/v3/music/artist/${artistId.trim()}/song`, { 57 | page: offset.trim() 58 | }); 59 | 60 | let re_str = /
.*?data-aid="(.*?)".*?data-mids="(.*?)".*?data-cids="(.*?)".*?data-share='(.*?)'.*?<\/a>/gs; 61 | let re_total = /全部歌曲((.*?))<\/span>/s; 62 | 63 | try { 64 | // ctx.body = JSON.stringify(result.data); 65 | let arr; 66 | let songList = []; 67 | while ((arr = re_str.exec(result.data)) !== null) { 68 | // console.log(arr[1], arr[2], arr[3], arr[4]); 69 | songList.push({ 70 | albumId: arr[1], 71 | id: arr[2], 72 | copyrightId: arr[3], 73 | al: JSON.parse(arr[4]) 74 | }); 75 | } 76 | let _result = { 77 | status: 200, 78 | total: re_total.exec(result.data)[1], 79 | num: 20, 80 | songList: songList 81 | }; 82 | 83 | global.cache.set(ctx.request.url, _result); 84 | 85 | ctx.body = JSON.stringify(_result); 86 | 87 | arr = null; 88 | _result = null; 89 | songList = null; 90 | } catch (error) { 91 | ctx.body = JSON.stringify({ 92 | error: '服务端数据解析错误', 93 | status: 400 94 | }) 95 | } 96 | ctx.type = 'application/json'; 97 | } 98 | 99 | module.exports = { 100 | singer_Info, 101 | singer_songList 102 | } -------------------------------------------------------------------------------- /routes/v1/migu/song.js: -------------------------------------------------------------------------------- 1 | const CrypotJs = require("crypto-js"); 2 | const JsEncrypt = require('node-jsencrypt'); 3 | const { migu_request } = require("../../../util/migu_request"); 4 | const APIError = require("../../../middlewares/rest").APIError; 5 | 6 | 7 | let song = async (ctx) => { 8 | // var 9 | // cid = ctx.request.body.cid || '69910406417', 10 | // br = ctx.request.body.br || '1'; 11 | if (ctx.request.method === 'GET') { 12 | var cid = ctx.request.query.cid || '69910406417'; 13 | var br = ctx.request.query.br || '1'; 14 | } else if (ctx.request.method === 'POST') { 15 | var cid = ctx.request.body.cid || '69910406417'; 16 | var br = ctx.request.body.br || '1'; 17 | } 18 | 19 | const cacheData = global.cache.get(ctx.request.url); 20 | if (cacheData) { 21 | ctx.rest(cacheData); 22 | return; 23 | } 24 | 25 | // 一套神奇的加密环节 26 | // const publicKey = `-----BEGIN PUBLIC KEY----- 27 | // MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8asrfSaoOb4je+DSmKdriQJKW 28 | // VJ2oDZrs3wi5W67m3LwTB9QVR+cE3XWU21Nx+YBxS0yun8wDcjgQvYt625ZCcgin 29 | // 2ro/eOkNyUOTBIbuj9CvMnhUYiR61lC1f1IGbrSYYimqBVSjpifVufxtx/I3exRe 30 | // ZosTByYp4Xwpb1+WAQIDAQAB 31 | // -----END PUBLIC KEY-----`; 32 | 33 | // const o = `{"copyrightId":"${cid.trim()}","type":${br.trim()},"auditionsFlag":0}`; 34 | // const s = new JsEncrypt; 35 | // s.setPublicKey(publicKey); 36 | // const a = 1e3 * Math.random(); 37 | // const u = CrypotJs.SHA256(String(a)).toString(); 38 | // const c = CrypotJs.AES.encrypt(o, u).toString(); 39 | // const f = s.encrypt(u); 40 | 41 | // let result = await migu_request('http://music.migu.cn/v3/api/music/audioPlayer/getPlayInfo', { 42 | // dataType: 2, 43 | // data: c, 44 | // secKey: f 45 | // }) 46 | 47 | // console.log(result); 48 | // 捕获序列化json出错,防止程序异常退出 49 | 50 | let result = await migu_request('http://c.musicapp.migu.cn/MIGUM2.0/v1.0/content/resourceinfo.do', { 51 | copyrightId: cid, 52 | resourceType: 2 53 | }) 54 | 55 | const typeMap = { 56 | '1': 'PQ', 57 | '2': 'HQ', 58 | '3': 'SQ', 59 | '4': 'ZQ' 60 | }; 61 | 62 | let urlData = { 63 | "returnCode":"000000", 64 | "msg":"成功", 65 | "data":{} 66 | }; 67 | 68 | if(result.data.resource && result.data.resource[0]) { 69 | const data = result.data.resource[0]; 70 | if(data.newRateFormats instanceof Array) { 71 | data.newRateFormats.forEach(({ formatType, androidUrl, url = androidUrl }) => { 72 | if (formatType == typeMap[br]) { 73 | urlData.data.playUrl = url.replace(/ftp:\/\/[^/]+/, 'https://freetyst.nf.migu.cn'); 74 | return; 75 | } 76 | }) 77 | } 78 | } 79 | 80 | global.cache.set(ctx.request.url, urlData); 81 | 82 | ctx.rest(urlData); 83 | result = null; 84 | urlData = null; 85 | // try { 86 | // ctx.body = JSON.stringify(result.data); 87 | // } catch (error) { 88 | // ctx.body = JSON.stringify({ 89 | // error: '服务端数据解析错误', 90 | // status: 400 91 | // }) 92 | // } 93 | // ctx.type = 'application/json'; 94 | // ctx.body = "true"; 95 | } 96 | 97 | module.exports = { 98 | song 99 | } -------------------------------------------------------------------------------- /routes/v1/qq/comment.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios"); 2 | const qs = require("qs"); 3 | const APIError = require("../../../middlewares/rest").APIError; 4 | 5 | const emojis = { 6 | e400846: '😘', 7 | e400874: '😴', 8 | e400825: '😃', 9 | e400847: '😙', 10 | e400835: '😍', 11 | e400873: '😳', 12 | e400836: '😎', 13 | e400867: '😭', 14 | e400832: '😊', 15 | e400837: '😏', 16 | e400875: '😫', 17 | e400831: '😉', 18 | e400855: '😡', 19 | e400823: '😄', 20 | e400862: '😨', 21 | e400844: '😖', 22 | e400841: '😓', 23 | e400830: '😈', 24 | e400828: '😆', 25 | e400833: '😋', 26 | e400822: '😀', 27 | e400843: '😕', 28 | e400829: '😇', 29 | e400824: '😂', 30 | e400834: '😌', 31 | e400877: '😷', 32 | e400132: '🍉', 33 | e400181: '🍺', 34 | e401067: '☕️', 35 | e400186: '🥧', 36 | e400343: '🐷', 37 | e400116: '🌹', 38 | e400126: '🍃', 39 | e400613: '💋', 40 | e401236: '❤️', 41 | e400622: '💔', 42 | e400637: '💣', 43 | e400643: '💩', 44 | e400773: '🔪', 45 | e400102: '🌛', 46 | e401328: '🌞', 47 | e400420: '👏', 48 | e400914: '🙌', 49 | e400408: '👍', 50 | e400414: '👎', 51 | e401121: '✋', 52 | e400396: '👋', 53 | e400384: '👉', 54 | e401115: '✊', 55 | e400402: '👌', 56 | e400905: '🙈', 57 | e400906: '🙉', 58 | e400907: '🙊', 59 | e400562: '👻', 60 | e400932: '🙏', 61 | e400644: '💪', 62 | e400611: '💉', 63 | e400185: '🎁', 64 | e400655: '💰', 65 | e400325: '🐥', 66 | e400612: '💊', 67 | e400198: '🎉', 68 | e401685: '⚡️', 69 | e400631: '💝', 70 | e400768: '🔥', 71 | e400432: '👑', 72 | } 73 | 74 | 75 | let comment = async (ctx) => { 76 | if (ctx.request.method === 'GET') { 77 | var id = ctx.request.query.id || ''; 78 | var offset = ctx.request.query.offset || '1'; 79 | var limit = ctx.request.query.limit || '30'; 80 | var type = ctx.request.query.type || '0'; 81 | var biztype = ctx.request.query.biztype || '1'; 82 | } else if (ctx.request.method === 'POST') { 83 | var id = ctx.request.body.id || ''; 84 | var offset = ctx.request.body.offset || '1'; 85 | var limit = ctx.request.body.limit || '30'; 86 | var type = ctx.request.body.type || '0'; 87 | var biztype = ctx.request.body.biztype || '1'; 88 | } 89 | 90 | const cacheData = global.cache.get(ctx.request.url); 91 | if (cacheData) { 92 | ctx.rest(cacheData); 93 | return; 94 | } 95 | 96 | if(!id) { 97 | throw new APIError("argument_error", "id is not found") 98 | } 99 | 100 | if(parseInt(limit) > 25) limit = '25'; 101 | 102 | 103 | const result = await axios.post('http://c.y.qq.com/base/fcgi-bin/fcg_global_comment_h5.fcg', 104 | qs.stringify({ 105 | biztype, 106 | topid: id, 107 | LoginUin: global.qq_cookie.uin ? global.qq_cookie.uin : '0', 108 | cmd: { 109 | 1: ['8', '6'], // 歌曲 110 | 2: ['8', '9'], // 专辑 111 | 3: ['8', '9'], // 歌单 112 | 4: ['8', '9'], // 排行榜 113 | 5: ['8', '6'], // mv 114 | }[biztype][type], 115 | pagenum: parseInt(offset) - 1, 116 | pagesize: parseInt(limit), 117 | }) 118 | ); 119 | 120 | const comment = result.data.comment; 121 | 122 | global.cache.set(ctx.request.url, { 123 | comments: filterComment(comment.commentlist), 124 | total: comment.commenttotal, 125 | offset, 126 | limit, 127 | maxOffset: Math.ceil(comment.commenttotal / limit) || 1, 128 | }); 129 | 130 | ctx.rest({ 131 | comments: filterComment(comment.commentlist), 132 | total: comment.commenttotal, 133 | offset, 134 | limit, 135 | maxOffset: Math.ceil(comment.commenttotal / limit) || 1, 136 | }); 137 | } 138 | 139 | 140 | function replaceEmoji(msg) { 141 | let rxp = /^\[em\](e\d+)\[\/em\]$/ 142 | let result = msg.match(/\[em\]e\d+\[\/em\]/g) 143 | if (!result) return msg 144 | result = Array.from(new Set(result)) 145 | for (let item of result) { 146 | let code = item.replace(rxp, '$1') 147 | msg = msg.replace(new RegExp(item.replace('[em]', '\\[em\\]').replace('[/em]', '\\[\\/em\\]'), 'g'), emojis[code] || '') 148 | } 149 | return msg 150 | } 151 | 152 | function filterComment(rawList) { 153 | return rawList.map(item => { 154 | let time = String(item.time).length < 10 ? null : parseInt(item.time + '000') 155 | if (item.middlecommentcontent) { 156 | let firstItem = item.middlecommentcontent[0] 157 | firstItem.avatarurl = item.avatarurl 158 | firstItem.praisenum = item.praisenum 159 | item.avatarurl = null 160 | item.praisenum = null 161 | item.middlecommentcontent.reverse() 162 | } 163 | return { 164 | id: `${item.rootcommentid}_${item.commentid}`, 165 | rootId: item.rootcommentid, 166 | text: item.rootcommentcontent ? replaceEmoji(item.rootcommentcontent).replace(/\\n/g, '\n').split('\n') : [], 167 | time: item.rootcommentid == item.commentid ? time : null, 168 | userName: item.rootcommentnick ? item.rootcommentnick.substring(1) : '', 169 | avatar: item.avatarurl, 170 | userId: item.encrypt_rootcommentuin, 171 | likedCount: item.praisenum, 172 | reply: item.middlecommentcontent 173 | ? item.middlecommentcontent.map(c => { 174 | // let index = c.subcommentid.lastIndexOf('_') 175 | return { 176 | id: `sub_${item.rootcommentid}_${c.subcommentid}`, 177 | text: replaceEmoji(c.subcommentcontent).replace(/\\n/g, '\n').split('\n'), 178 | time: c.subcommentid == item.commentid ? time : null, 179 | userName: c.replynick.substring(1), 180 | avatar: c.avatarurl, 181 | userId: c.encrypt_replyuin, 182 | likedCount: c.praisenum, 183 | } 184 | }) 185 | : [], 186 | } 187 | }) 188 | } 189 | 190 | module.exports = { 191 | comment 192 | } -------------------------------------------------------------------------------- /routes/v1/qq/controllers.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router'); 2 | 3 | const { search, hotSearch, suggestSearch } = require('./search'); 4 | const { song } = require('./song'); 5 | const { top, topCategory } = require('./top'); 6 | const { lyric } = require('./lyric'); 7 | const { getcookie, setcookie, refresh } = require('./cookie'); 8 | const { playlist_Info } = require('./playlist'); /* playlist_tagCategory, playlist_Tag, */ 9 | const { login, login_scan } = require('./login'); 10 | const { comment } = require('./comment'); 11 | 12 | // 新建 QQ 路由 13 | const qq = new Router(); 14 | 15 | // add get method 16 | 17 | qq.get('/search', search); 18 | qq.get('/hotSearch', hotSearch); 19 | qq.get('/suggestSearch', suggestSearch); 20 | 21 | qq.get('/song', song); 22 | // qq.get('/songInfo', songInfo); 23 | 24 | qq.get('/lyric', lyric); 25 | 26 | qq.get('/top', top); 27 | qq.get('/topCategory', topCategory); 28 | 29 | qq.get('/getcookie', getcookie); 30 | qq.get('/refresh', refresh); 31 | 32 | qq.get('/comment', comment); 33 | 34 | 35 | qq.get('/login', login); 36 | qq.get('/login_scan', login_scan); 37 | // qq.get('/login_scan_long', login_scan_long); 38 | // qq.get('/singer/info', singer_Info); 39 | // qq.get('/singer/songList', singer_songList); 40 | 41 | // qq.get('/playlist/tagCategory', playlist_tagCategory); 42 | // qq.get('/playlist/tag', playlist_Tag); 43 | qq.get('/playlist/info', playlist_Info); 44 | 45 | // // add post method 46 | qq.post('/login', login); 47 | qq.post('/setcookie', setcookie); 48 | 49 | module.exports = qq; -------------------------------------------------------------------------------- /routes/v1/qq/cookie.js: -------------------------------------------------------------------------------- 1 | // const { qq_request } = require("../../../util/qq_request"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | const getSign = require("../../../util/sign"); 4 | 5 | // 解析cookie 6 | const __Cookie = require('../../../util/cookie_util'); 7 | const { qq_request } = require("../../../util/qq_request"); 8 | const { default: axios } = require("axios"); 9 | 10 | // 设置qq cookie 11 | let setcookie = async (ctx) => { 12 | // 只允许POST方法 13 | if (ctx.request.method === 'POST') { 14 | var data = ctx.request.body.data || ''; 15 | } 16 | 17 | if (!data) { 18 | throw new APIError('Cookie:data_notfound', 'argument data is not found'); 19 | } 20 | 21 | global.qq_cookie = __Cookie.parse(data); 22 | // console.log(global.qq_cookie); 23 | 24 | ctx.rest({ 25 | code: '成功', 26 | msg: 'Cookie add successful', 27 | status: 200 28 | }); 29 | } 30 | 31 | // 获取qq cookie 32 | let getcookie = async (ctx) => { 33 | // console.log(global.qq_cookie); 34 | ctx.rest(global.qq_cookie); 35 | } 36 | 37 | // 刷新cookie 38 | let refresh = async (ctx) => { 39 | const {uin, qm_keyst, qqmusic_key} = global.qq_cookie 40 | if (!uin || !(qm_keyst || qqmusic_key)) { 41 | ctx.rest({ 42 | result: 301, 43 | errMsg: '未登陆' 44 | }); 45 | return ; 46 | } 47 | const data = { 48 | req1: { 49 | module: "QQConnectLogin.LoginServer", 50 | method: "QQLogin", 51 | param: { 52 | expired_in: 7776000, //不用管 53 | // onlyNeedAccessToken: 0, //不用管 54 | // forceRefreshToken: 0, //不用管 55 | // access_token: "6B0C62126368CA1ACE16C932C679747E", //access_token 56 | // refresh_token: "25BACF1650EE2592D06BCC19EEAD7AD6", //refresh_token 57 | musicid: uin, //uin或者web_uin 微信没试过 58 | musickey: qm_keyst || qqmusic_key, //key 59 | }, 60 | }, 61 | }; 62 | const sign = getSign(data); 63 | let url = `https://u6.y.qq.com/cgi-bin/musics.fcg?sign=${sign}&format=json&inCharset=utf8&outCharset=utf-8&data=${encodeURIComponent( 64 | JSON.stringify(data) 65 | )}`; 66 | 67 | const d = await axios.get(url); 68 | const result = d.data; 69 | 70 | // if (result.req1 && result.req1.data && result.req1.data.musickey) { 71 | // const musicKey = result.req1.data.musickey; 72 | // ['qm_keyst', 'qqmusic_key'].forEach((k) => { 73 | // __Cookie.parse({...global.qq_cookie, [k]: val}) 74 | // __Cookie.parse(k, musicKey, {expires: new Date(Date.now() + 86400000)}) 75 | // }) 76 | // ctx.rest({ 77 | // result: 100, 78 | // data: { 79 | // musickey: result.req1.data.musickey, 80 | // } 81 | // }); 82 | // return ; 83 | // } 84 | ctx.rest({ 85 | result: 200, 86 | errMsg: '刷新失败,建议重新设置cookie' 87 | }) 88 | } 89 | 90 | module.exports = { 91 | getcookie, 92 | setcookie, 93 | refresh 94 | } -------------------------------------------------------------------------------- /routes/v1/qq/login.js: -------------------------------------------------------------------------------- 1 | // const { qq_request } = require("../../../util/qq_request"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | 4 | // 解析cookie 5 | const __Cookie = require('../../../util/cookie_util'); 6 | const { login: QQlogin } = require('../../../util/p'); 7 | const { login_qq_scan, get_qrlogin_pic } = require('../../../util/login_qq_scan'); 8 | const { default: axios } = require("axios"); 9 | 10 | // Login QQ 11 | let login = async (ctx) => { 12 | if (ctx.request.method === 'GET') { 13 | var u = ctx.request.query.u || ''; 14 | var p = ctx.request.query.p || ''; 15 | } else if (ctx.request.method === 'POST') { 16 | var u = ctx.request.body.u || ''; 17 | var p = ctx.request.body.p || ''; 18 | } 19 | 20 | if (u.trim() == '' || p.trim() == '') { 21 | throw new APIError('Login:argument_notfound', 'argument data is not found'); 22 | } 23 | 24 | let cookie = await QQlogin(u.trim(), p.trim()); 25 | 26 | if (cookie !== "异地登陆,需要验证码,请在QQ手机关闭登录保护") { 27 | global.qq_cookie = __Cookie.parse(data); 28 | } 29 | 30 | ctx.rest({ 31 | cookie 32 | }); 33 | } 34 | 35 | 36 | // Login QQ 37 | let login_scan = async (ctx) => { 38 | // if (ctx.request.method === 'GET') { 39 | // var u = ctx.request.query.u || ''; 40 | // var p = ctx.request.query.p || ''; 41 | // } else if (ctx.request.method === 'POST') { 42 | // var u = ctx.request.body.u || ''; 43 | // var p = ctx.request.body.p || ''; 44 | // } 45 | 46 | const cacheData = global.cache.get(ctx.request.url); 47 | if (cacheData) { 48 | // 设置Content-Type: 49 | ctx.response.type = 'image/png'; 50 | // 设置Response Body: 51 | ctx.response.body = cacheData; 52 | return; 53 | } 54 | 55 | let d = get_qrlogin_pic(); 56 | let res = await axios.request({ 57 | url: d, 58 | method: 'GET', 59 | responseType: 'arraybuffer' 60 | }); 61 | // console.log(res.data); 62 | 63 | await login_qq_scan(res.headers['set-cookie']); 64 | 65 | 66 | global.cache.set(ctx.request.url, res.data, 50); 67 | 68 | // 设置Content-Type: 69 | ctx.response.type = 'image/png'; 70 | // 设置Response Body: 71 | ctx.response.body = res.data; 72 | } 73 | 74 | 75 | 76 | // let login_scan_long = async (ctx) => { 77 | 78 | // if (ctx.request.method === 'GET') { 79 | // var u = ctx.request.query.u || '0'; 80 | // } else if (ctx.request.method === 'POST') { 81 | // var u = ctx.request.body.u || '0'; 82 | // } 83 | 84 | // const cacheData = global.cache.get('LKHKJAHKJGKADLKHAKFJAHKDJH'); 85 | // if (cacheData) { 86 | // // 设置Content-Type: 87 | // ctx.response.type = 'image/png'; 88 | // // 设置Response Body: 89 | // ctx.response.body = cacheData; 90 | // return; 91 | // } 92 | 93 | 94 | // // 清除上一次的定时器 95 | // if(global.clearTime) { 96 | // clearInterval(global.clearTime); 97 | // } 98 | 99 | // // 账号不能为空 100 | // if(u === '0') { 101 | // throw new APIError('login Error', '参数 u 不存在, u为账号'); 102 | // } 103 | 104 | // const account = u; 105 | // const client = createClient(parseInt(account)); 106 | 107 | // let data = null; 108 | 109 | // client.on("system.online", () => { 110 | // console.log("Logged in!"); 111 | // global.qq_cookie = __Cookie.parse(client.cookies['v.qq.com'].toString('utf8')); 112 | 113 | // setTimeout(async () => { 114 | // console.log("Logout out!"); 115 | // await client.logout(); 116 | // }, 10000); 117 | // }) 118 | 119 | // // client.on("system.login.qrcode", function (e) { 120 | // // data = e; 121 | // // }).login(); 122 | 123 | // let a = await client.fetchQrcode_1(); 124 | // console.log(a); 125 | // if(a.status === 200) { 126 | // data = a.data 127 | // } 128 | 129 | // if(a.status === 400) { 130 | // throw new APIError('login Error', '获取二维码失败,服务器内部错误'); 131 | // } 132 | 133 | // try { 134 | // global.clearTime = setInterval(async () => { 135 | // let result = await client.queryQrcodeResult(); 136 | // console.log(result); 137 | 138 | // if (result.retcode === 0) { 139 | // let a = await client.qrcodeLogin(); 140 | // console.log(a); 141 | // clearInterval(global.clearTime); 142 | // } 143 | 144 | // // 二维码失效 145 | // if (result.retcode === 17) { 146 | // clearInterval(global.clearTime); 147 | // } 148 | 149 | // // 二维码认证中 150 | // if (result.retcode === 53) { 151 | // // client.login(); 152 | // } 153 | 154 | // // 二维码认证被拒 155 | // if (result.retcode === 54) { 156 | // clearInterval(global.clearTime); 157 | // } 158 | 159 | // }, 2000); 160 | // } catch (error) { 161 | // clearInterval(clearTime); 162 | // } 163 | 164 | 165 | 166 | // global.cache.set('LKHKJAHKJGKADLKHAKFJAHKDJH', data, 90); 167 | // // 设置Content-Type: 168 | // ctx.response.type = 'image/png'; 169 | // // 设置Response Body: 170 | // ctx.response.body = data; 171 | 172 | // } 173 | 174 | module.exports = { 175 | login, 176 | login_scan, 177 | // login_scan_long 178 | } -------------------------------------------------------------------------------- /routes/v1/qq/lyric.js: -------------------------------------------------------------------------------- 1 | const { qq_request } = require("../../../util/qq_request"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | 4 | 5 | let lyric = async (ctx) => { 6 | if (ctx.request.method === 'GET') { 7 | var mid = ctx.request.query.mid || '004O1DHG4MjYOi'; 8 | // console.log(typeof ctx.request.query.limit, limit); 9 | } else if (ctx.request.method === 'POST') { 10 | var mid = ctx.request.body.mid || '004O1DHG4MjYOi'; 11 | } 12 | 13 | const cacheData = global.cache.get(ctx.request.url); 14 | if (cacheData) { 15 | ctx.rest(cacheData); 16 | return; 17 | } 18 | 19 | let result = await qq_request(`https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg`, { 20 | songmid: mid.trim(), 21 | format: 'json', 22 | nobase64: 1, 23 | g_tk: 5381 24 | }); 25 | 26 | global.cache.set(ctx.request.url, result.data); 27 | 28 | ctx.rest(result.data); 29 | result = null; 30 | } 31 | 32 | module.exports = { 33 | lyric 34 | } -------------------------------------------------------------------------------- /routes/v1/qq/playlist.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios"); 2 | const { qq_request } = require("../../../util/qq_request"); 3 | const APIError = require("../../../middlewares/rest").APIError; 4 | 5 | 6 | let playlist_Info = async (ctx) => { 7 | if (ctx.request.method === 'GET') { 8 | var pid = ctx.request.query.pid || '2642142791'; 9 | // console.log(typeof ctx.request.query.limit, limit); 10 | } else if (ctx.request.method === 'POST') { 11 | var pid = ctx.request.body.pid || '2642142791'; 12 | } 13 | 14 | const cacheData = global.cache.get(ctx.request.url); 15 | if (cacheData) { 16 | ctx.rest(cacheData); 17 | return; 18 | } 19 | 20 | if (pid.length === 0) { 21 | throw new APIError("QQ_playlist", "pid not found"); 22 | } 23 | 24 | // let result = await qq_request(`http://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg`, { 25 | // type: 1, 26 | // utf8: 1, 27 | // disstid: pid.trim(), // 歌单的id 28 | // loginUin: 0, 29 | // format: 'json' 30 | // }); 31 | 32 | let result = await axios.get(`https://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg?type=1&json=1&utf8=1&onlysong=0&new_format=1&disstid=${pid.trim()}&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0`, { 33 | headers: { 34 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0', 35 | Referer: 'https://y.qq.com/', 36 | Host: 'u.y.qq.com' 37 | } 38 | }); 39 | global.cache.set(ctx.request.url, result.data); 40 | 41 | ctx.rest(result.data); 42 | result = null; 43 | } 44 | 45 | module.exports = { 46 | playlist_Info 47 | } -------------------------------------------------------------------------------- /routes/v1/qq/song.js: -------------------------------------------------------------------------------- 1 | const { qq_request } = require("../../../util/qq_request"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | 4 | 5 | let song = async (ctx) => { 6 | 7 | if (ctx.request.method === 'GET') { 8 | var mid = ctx.request.query.mid || '002mZevo3wHvsc'; 9 | var br = ctx.request.query.br || '128'; 10 | } else if (ctx.request.method === 'POST') { 11 | var mid = ctx.request.body.mid || '002mZevo3wHvsc'; 12 | var br = ctx.request.body.br || '320'; 13 | } 14 | 15 | 16 | const cacheData = global.cache.get(ctx.request.url); 17 | if (cacheData) { 18 | ctx.rest(cacheData); 19 | return; 20 | } 21 | 22 | 23 | let uin = global.qq_cookie.uin || '0'; 24 | 25 | let typeMap = { 26 | 'm4a': { 27 | 's': 'C400', 28 | 'e': '.m4a', 29 | }, 30 | '128': { 31 | 's': 'M500', 32 | 'e': '.mp3', 33 | }, 34 | '320': { 35 | 's': 'M800', 36 | 'e': '.mp3', 37 | }, 38 | 'ape': { 39 | 's': 'A000', 40 | 'e': '.ape', 41 | }, 42 | 'flac': { 43 | 's': 'F000', 44 | 'e': '.flac', 45 | }, 46 | 'mflac': { 47 | 's': 'F0M0', 48 | 'e': '.mflac', 49 | }, 50 | 'Hi-Res': { 51 | 's': 'RS01', 52 | 'e': '.flac', 53 | } 54 | } 55 | 56 | if (typeMap[br] === undefined) { 57 | throw new APIError("Song:br_error", "br is not m4a, 128, 320, flac, mflac, Hi-Res"); 58 | } 59 | 60 | 61 | let filename = mid.split(",").filter(e => String(e).trim()).map(id => `"${typeMap[br].s}${id.trim() + id.trim()}${typeMap[br].e}"`).join(","); 62 | let mids = mid.split(",").filter(e => String(e).trim()).map(id => `"${id.trim()}"`).join(","); 63 | 64 | // let filename = `"${typeMap[br].s}${mid.trim() + mid.trim()}${typeMap[br].e}"`; 65 | // let mids = `"${mid.trim()}"`; 66 | 67 | let result = await qq_request(`https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&data={"req":{"module":"CDN.SrfCdnDispatchServer","method":"GetCdnDispatch","param":{"guid":"658650575","calltype":0,"userip":""}},"req_0":{"module":"vkey.GetVkeyServer","method":"CgiGetVkey","param":{"filename":[${filename}],"guid":"658650575","songmid":[${mids}],"songtype":[0],"uin":"${uin}","loginflag":1,"platform":"20"}},"comm":{"uin":${uin},"format":"json","ct":24,"cv":0}}`); 68 | // console.log(result); 69 | // 捕获序列化json出错,防止程序异常退出 70 | 71 | 72 | let arrUrls = []; 73 | result.data.req_0.data.midurlinfo.length && result.data.req_0.data.midurlinfo.forEach(e => { 74 | arrUrls.push(e.purl ? 'https://isure.stream.qqmusic.qq.com/' + e.purl : null); 75 | }); 76 | 77 | if (result.data.req_0.data.midurlinfo.length && result.data.req_0.data.midurlinfo[0].purl) { 78 | global.cache.set(ctx.request.url, { 79 | data: { 80 | // url: result.data.req_0.data.midurlinfo.length && result.data.req_0.data.midurlinfo[0].purl ? 'https://isure.stream.qqmusic.qq.com/' + result.data.req_0.data.midurlinfo[0].purl : null 81 | url: arrUrls.length === 1 ? arrUrls[0] : arrUrls 82 | }, 83 | code: "成功" 84 | }); 85 | } 86 | 87 | 88 | ctx.rest({ 89 | data: { 90 | // url: result.data.req_0.data.midurlinfo.length && result.data.req_0.data.midurlinfo[0].purl ? 'https://isure.stream.qqmusic.qq.com/' + result.data.req_0.data.midurlinfo[0].purl : null 91 | url: arrUrls.length === 1 ? arrUrls[0] : arrUrls 92 | }, 93 | code: "成功" 94 | }); 95 | // ctx.rest(result.data); 96 | result = null; 97 | } 98 | 99 | 100 | // let songInfo = async (ctx) => { 101 | 102 | // if (ctx.request.method === 'GET') { 103 | // var rid = ctx.request.query.rid || '156483846'; 104 | // } else if (ctx.request.method === 'POST') { 105 | // var rid = ctx.request.body.rid || '156483846'; 106 | // } 107 | 108 | // let result = await qq_request('http://kuwo.cn/api/www/music/musicInfo', { 109 | // mid: rid.trim(), 110 | // httpsStatus: 1, 111 | // reqId: 'e3f36a20-4c05-11eb-b0b7-8b03aa7e4b0d' 112 | // }); 113 | 114 | // ctx.rest(result.data); 115 | // } 116 | 117 | module.exports = { 118 | song 119 | // songInfo 120 | } -------------------------------------------------------------------------------- /routes/v1/qq/song_back.js: -------------------------------------------------------------------------------- 1 | const { qq_request } = require("../../../util/qq_request"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | 4 | 5 | let song = async (ctx) => { 6 | 7 | if (ctx.request.method === 'GET') { 8 | var mid = ctx.request.query.mid || '002mZevo3wHvsc'; 9 | var br = ctx.request.query.br || '128'; 10 | } else if (ctx.request.method === 'POST') { 11 | var mid = ctx.request.body.mid || '002mZevo3wHvsc'; 12 | var br = ctx.request.body.br || '320'; 13 | } 14 | 15 | 16 | let uin = global.qq_cookie.uin || '0'; 17 | 18 | let typeMap = { 19 | 'm4a': { 20 | 's': 'C400', 21 | 'e': '.m4a', 22 | }, 23 | '128': { 24 | 's': 'M500', 25 | 'e': '.mp3', 26 | }, 27 | '320': { 28 | 's': 'M800', 29 | 'e': '.mp3', 30 | }, 31 | 'ape': { 32 | 's': 'A000', 33 | 'e': '.ape', 34 | }, 35 | 'flac': { 36 | 's': 'F000', 37 | 'e': '.flac', 38 | } 39 | } 40 | 41 | if (typeMap[br] === undefined) { 42 | throw new APIError("Song:br_error", "br is not m4a, 128, 320, flac"); 43 | } 44 | 45 | let filename = `"${typeMap[br].s}${mid.trim() + mid.trim()}${typeMap[br].e}"`; 46 | // let mids = `"${mid.trim()}"`; 47 | let guid = (Math.random() * 10000000).toFixed(0); 48 | 49 | let purl = ''; 50 | let count = 0; 51 | // let cacheKey = `song_url_${filename}`; 52 | // let cacheData = cache.get(cacheKey); 53 | // if (cacheData) { 54 | // return res.send(cacheData); 55 | // } 56 | 57 | // while (!purl && count < 10) { 58 | count += 1; 59 | let domain = ''; 60 | let result = await qq_request('https://u.y.qq.com/cgi-bin/musicu.fcg', { 61 | '-': 'getplaysongvkey', 62 | g_tk: 5381, 63 | loginUin: uin, 64 | hostUin: 0, 65 | format: 'json', 66 | inCharset: 'utf8', 67 | outCharset: 'utf-8¬ice=0', 68 | platform: 'yqq.json', 69 | needNewCode: 0, 70 | data: JSON.stringify({ 71 | req_0: { 72 | module: 'vkey.GetVkeyServer', 73 | method: 'CgiGetVkey', 74 | param: { 75 | filename: [filename], 76 | guid: guid, 77 | songmid: [mid], 78 | songtype: [0], 79 | uin: uin, 80 | loginflag: 1, 81 | platform: '20', 82 | }, 83 | }, 84 | comm: { 85 | uin: uin, 86 | format: 'json', 87 | ct: 19, 88 | cv: 0, 89 | authst: global.qq_cookie.qqmusic_key ? global.qq_cookie.qqmusic_key : '', 90 | } 91 | }) 92 | }); 93 | 94 | ctx.rest(result.data); 95 | 96 | 97 | // if (!result.data.req_0 || !result.data.req_0.data) { 98 | // throw new APIError('URL:Error', '获取链接出错,建议检查是否携带 cookie ') 99 | // } 100 | 101 | // if (result.data.req_0 && result.data.req_0.data && result.data.req_0.data.midurlinfo) { 102 | // purl = result.data.req_0.data.midurlinfo[0].purl; 103 | // } 104 | // if (domain === '') { 105 | // domain = 106 | // result.req_0.data.sip.find(i => !i.startsWith('http://ws')) || 107 | // result.req_0.data.sip[0]; 108 | // } 109 | 110 | // if (!purl) { 111 | // throw new APIError('URL:Error', '获取播放链接出错'); 112 | // } 113 | // console.log(purl); 114 | 115 | // ctx.rest(result.data); 116 | 117 | // let result = await qq_request(`https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&data={"req":{"module":"CDN.SrfCdnDispatchServer","method":"GetCdnDispatch","param":{"guid":"658650575","calltype":0,"userip":""}},"req_0":{"module":"vkey.GetVkeyServer","method":"CgiGetVkey","param":{"filename":[${filename}],"guid":"658650575","songmid":[${mids}],"songtype":[0],"uin":"${uin}","loginflag":1,"platform":"20"}},"comm":{"uin":${uin},"format":"json","ct":24,"cv":0}}`); 118 | // console.log(result); 119 | // 捕获序列化json出错,防止程序异常退出 120 | 121 | // ctx.rest({ 122 | // data: { 123 | // url: result.data.req_0.data.midurlinfo.length && result.data.req_0.data.midurlinfo[0].purl ? 'https://isure.stream.qqmusic.qq.com/' + result.data.req_0.data.midurlinfo[0].purl : {} 124 | // }, 125 | // code: "成功" 126 | // }); 127 | // ctx.rest(result.data); 128 | result = null; 129 | } 130 | 131 | 132 | // let songInfo = async (ctx) => { 133 | 134 | // if (ctx.request.method === 'GET') { 135 | // var rid = ctx.request.query.rid || '156483846'; 136 | // } else if (ctx.request.method === 'POST') { 137 | // var rid = ctx.request.body.rid || '156483846'; 138 | // } 139 | 140 | // let result = await qq_request('http://kuwo.cn/api/www/music/musicInfo', { 141 | // mid: rid.trim(), 142 | // httpsStatus: 1, 143 | // reqId: 'e3f36a20-4c05-11eb-b0b7-8b03aa7e4b0d' 144 | // }); 145 | 146 | // ctx.rest(result.data); 147 | // } 148 | 149 | module.exports = { 150 | song 151 | // songInfo 152 | } -------------------------------------------------------------------------------- /routes/v1/qq/top.js: -------------------------------------------------------------------------------- 1 | const { qq_request } = require("../../../util/qq_request"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | 4 | // 排行榜歌单详情 5 | let top = async (ctx) => { 6 | if (ctx.request.method === 'GET') { 7 | var topId = ctx.request.query.topId || '26'; 8 | var limit = ctx.request.query.limit || '200'; 9 | var offset = ctx.request.query.offset || '1'; 10 | var period = ctx.request.query.offset || ''; 11 | } else if (ctx.request.method === 'POST') { 12 | var topId = ctx.request.body.topId || '26'; 13 | var limit = ctx.request.body.limit || '200'; 14 | var offset = ctx.request.body.offset || '1'; 15 | var period = ctx.request.body.offset || ''; 16 | } 17 | 18 | const cacheData = global.cache.get(ctx.request.url); 19 | if (cacheData) { 20 | ctx.rest(cacheData); 21 | return; 22 | } 23 | 24 | if (period) { 25 | var url = `https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&data={"detail":{"module":"musicToplist.ToplistInfoServer","method":"GetDetail","param":{"topId":${topId.trim()},"offset":${(parseInt(offset)-1) * limit},"num":${limit.trim()},"period":"${period.trim()}"}},"comm":{"ct":24,"cv":0}}`; 26 | } else { 27 | var url = `https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&data={"detail":{"module":"musicToplist.ToplistInfoServer","method":"GetDetail","param":{"topId":${topId.trim()},"offset":${(parseInt(offset)-1) * limit},"num":${limit.trim()}}},"comm":{"ct":24,"cv":0}}`; 28 | } 29 | 30 | let result = await qq_request(url); 31 | 32 | 33 | global.cache.set(ctx.request.url, result.data, 3600); 34 | 35 | ctx.rest(result.data); 36 | result = null; 37 | } 38 | 39 | // 排行榜分类 40 | let topCategory = async (ctx) => { 41 | const cacheData = global.cache.get(ctx.request.url); 42 | if (cacheData) { 43 | ctx.rest(cacheData); 44 | return; 45 | } 46 | 47 | let result = await qq_request('https://u.y.qq.com/cgi-bin/musicu.fcg?_=1601389640007&data={"comm":{"g_tk":308189849,"uin":1528773794,"format":"json","inCharset":"utf-8","outCharset":"utf-8","notice":0,"platform":"h5","needNewCode":1,"ct":23,"cv":0},"topList":{"module":"musicToplist.ToplistInfoServer","method":"GetAll","param":{}}}'); 48 | 49 | 50 | global.cache.set(ctx.request.url, result.data, 3600); 51 | 52 | ctx.rest(result.data); 53 | result = null; 54 | } 55 | 56 | module.exports = { 57 | top, 58 | topCategory 59 | } -------------------------------------------------------------------------------- /routes/v1/scavengers/controllers.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router'); 2 | 3 | const { getMasterColor } = require('./getMasterColor'); 4 | 5 | // 新建 咪咕 路由 6 | const scavengers = new Router(); 7 | 8 | // add get method 9 | 10 | scavengers.get('/getMasterColor', getMasterColor); 11 | 12 | module.exports = scavengers; -------------------------------------------------------------------------------- /routes/v1/scavengers/getMasterColor.js: -------------------------------------------------------------------------------- 1 | const APIError = require("../../../middlewares/rest").APIError; 2 | 3 | const ColorThief = require('colorthief'); 4 | 5 | const rgbToHex = (r, g, b) => '#' + [r, g, b].map(x => { 6 | const hex = x.toString(16) 7 | return hex.length === 1 ? '0' + hex : hex 8 | }).join('') 9 | 10 | let getMasterColor = async (ctx) => { 11 | if (ctx.request.method === 'GET') { 12 | var imgUrl = ctx.request.query.imgUrl || ''; 13 | // console.log(typeof ctx.request.query.limit, limit); 14 | } else if (ctx.request.method === 'POST') { 15 | var imgUrl = ctx.request.body.imgUrl || ''; 16 | } 17 | 18 | try { 19 | let palette = await ColorThief.getColor(imgUrl); 20 | 21 | ctx.rest({color: rgbToHex(...palette)}); 22 | } catch (error) { 23 | ctx.rest({color: rgbToHex(255, 255, 255)}); 24 | } 25 | 26 | } 27 | 28 | module.exports = { 29 | getMasterColor 30 | } -------------------------------------------------------------------------------- /routes/v1/wy/controllers.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router'); 2 | 3 | const { song, songUrl } = require('./song'); 4 | const { getcookie, setcookie } = require('./cookie'); 5 | const { playlist_Info } = require('./playlist'); 6 | const { search } = require('./search'); 7 | const { lyric } = require('./lyric'); 8 | const { mv_url } = require('./mv'); 9 | const { topCategory } = require('./top'); 10 | const { recommendSongs } = require('./recommendSongs'); 11 | const { comment, comment_hot } = require('./comment'); 12 | const { login_refresh, login } = require('./login'); 13 | 14 | 15 | // 新建 wy 路由 16 | const wy = new Router(); 17 | 18 | // add get method 19 | wy.get('/song', song); 20 | wy.get('/songUrl', songUrl); 21 | wy.get('/getcookie', getcookie); 22 | 23 | wy.get('/playlist/info', playlist_Info); 24 | 25 | wy.get('/search', search); 26 | 27 | wy.get('/lyric', lyric); 28 | wy.get('/mv_url', mv_url); 29 | 30 | wy.get('/login/refresh', login_refresh); 31 | wy.get('/login', login); 32 | 33 | wy.get('/topCategory', topCategory); 34 | 35 | wy.get('/recommendSongs', recommendSongs); 36 | 37 | wy.get('/comment', comment); 38 | wy.get('/comment/hot', comment_hot); 39 | 40 | 41 | // // add post method 42 | wy.post('/setcookie', setcookie); 43 | 44 | module.exports = wy; -------------------------------------------------------------------------------- /routes/v1/wy/cookie.js: -------------------------------------------------------------------------------- 1 | // 解析cookie 2 | const __Cookie = require('../../../util/cookie_util'); 3 | 4 | // 设置wy cookie 5 | let setcookie = async (ctx) => { 6 | // 只允许POST方法 7 | if (ctx.request.method === 'POST') { 8 | var data = ctx.request.body.data || ''; 9 | } 10 | 11 | if (!data) { 12 | throw new APIError('Cookie:data_notfound', 'argument data is not found'); 13 | } 14 | 15 | global.wy_cookie = __Cookie.parse(data); 16 | // console.log(global.qq_cookie); 17 | 18 | ctx.rest({ 19 | code: '成功', 20 | msg: 'Cookie add successful', 21 | status: 200 22 | }); 23 | } 24 | 25 | // 获取qq cookie 26 | let getcookie = async (ctx) => { 27 | // console.log(global.qq_cookie); 28 | ctx.rest(global.wy_cookie); 29 | } 30 | 31 | 32 | module.exports = { 33 | getcookie, 34 | setcookie 35 | } -------------------------------------------------------------------------------- /routes/v1/wy/login.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | const qs = require('qs'); 4 | const encrypt = require('../../../util/crypto'); 5 | const { serialization, parse } = require("../../../util/cookie_util"); 6 | const crypto = require('crypto'); 7 | 8 | let login_refresh = async (ctx) => { 9 | if (ctx.request.method === 'GET') { 10 | 11 | } else if (ctx.request.method === 'POST') { 12 | 13 | } 14 | 15 | // const cacheData = global.cache.get(ctx.request.url); 16 | // if (cacheData) { 17 | // ctx.rest(cacheData); 18 | // return; 19 | // } 20 | 21 | let json_ = {}; 22 | let csrfToken = (serialization(global.wy_cookie) || '').match(/_csrf=([^(;|$)]+)/); 23 | 24 | json_.csrf_token = csrfToken ? csrfToken[1] : ''; 25 | 26 | 27 | let data = encrypt.weapi(json_); 28 | 29 | let result = await axios.post(`https://music.163.com/weapi/login/token/refresh `, new URLSearchParams(data).toString(), { 30 | headers: { 31 | "User-Agent": 'Mozilla/5.0 (Linux; U; Android 8.1.0; zh-cn; BKK-AL10 Build/HONORBKK-AL10) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/10.6 Mobile Safari/537.36', 32 | Referer: 'https://music.163.com', 33 | "Content-Type": "application/x-www-form-urlencoded", 34 | Cookie: serialization({ ...global.wy_cookie, _ntes_nuid: crypto.randomBytes(16).toString('hex'), NMTID: crypto.randomBytes(16).toString('hex') }) 35 | } 36 | }); 37 | 38 | 39 | let cookieArr = result.headers["set-cookie"].map(cookie => { 40 | return cookie.split("; ")[0]; 41 | }); 42 | 43 | cookieArr = cookieArr.filter(cookie => { 44 | if (cookie.split("=").pop() === "") { 45 | return false; 46 | } 47 | return true; 48 | }) 49 | let cookieSet = new Set(cookieArr); 50 | cookieArr = [...cookieSet]; 51 | 52 | result.data.cookie = cookieArr.join("; "); 53 | global.wy_cookie = { ...global.wy_cookie, ...parse(cookieArr.join("; ")) }; 54 | 55 | // global.cache.set(ctx.request.url, {...result.data, cookie: result.headers['set-cookie'].join("; ")}); 56 | 57 | ctx.rest(result.data); 58 | } 59 | 60 | 61 | 62 | let login = async (ctx) => { 63 | if (ctx.request.method === 'GET') { 64 | var phone = ctx.request.query.phone || ''; 65 | var countrycode = ctx.request.query.countrycode || '86'; 66 | var captcha = ctx.request.query.captcha; 67 | var password = ctx.request.query.password || ''; 68 | } else if (ctx.request.method === 'POST') { 69 | var phone = ctx.request.body.phone || ''; 70 | var countrycode = ctx.request.body.countrycode || '86'; 71 | var captcha = ctx.request.body.captcha; 72 | var password = ctx.request.body.password || ''; 73 | } 74 | 75 | const cacheData = global.cache.get(ctx.request.url); 76 | if (cacheData) { 77 | ctx.rest(cacheData); 78 | return; 79 | } 80 | 81 | let csrfToken = (serialization({ ...global.wy_cookie, os: "ios", appver: '8.7.01' }) || '').match(/_csrf=([^(;|$)]+)/); 82 | 83 | csrf_token = csrfToken ? csrfToken[1] : ''; 84 | 85 | let data = encrypt.weapi({ 86 | phone: phone, 87 | captcha: captcha, 88 | countrycode: countrycode, 89 | password: crypto.createHash('md5').update(password).digest('hex'), 90 | rememberLogin: 'true', 91 | csrf_token: csrf_token 92 | }); 93 | 94 | let result = await axios.post(`https://music.163.com/weapi/login/cellphone`, qs.stringify(data), { 95 | headers: { 96 | "User-Agent": 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0', 97 | Referer: 'https://music.163.com', 98 | 'Content-Type': 'application/x-www-form-urlencoded', 99 | Cookie: serialization({ ...global.wy_cookie, os: "ios", appver: '8.7.01' }) 100 | } 101 | }); 102 | 103 | 104 | let cookieArr = result.headers["set-cookie"].map(cookie => { 105 | return cookie.split("; ")[0]; 106 | }); 107 | 108 | cookieArr = cookieArr.filter(cookie => { 109 | if (cookie.split("=").pop() === "") { 110 | return false; 111 | } 112 | return true; 113 | }) 114 | 115 | let cookieSet = new Set(cookieArr); 116 | cookieArr = [...cookieSet]; 117 | 118 | result.data.cookie = cookieArr.join("; "); 119 | global.wy_cookie = parse(cookieArr.join("; ")); 120 | 121 | global.cache.set(ctx.request.url, result.data); 122 | 123 | ctx.rest(result.data); 124 | } 125 | 126 | 127 | module.exports = { 128 | login_refresh, 129 | login 130 | } 131 | 132 | //7b6c96634c9942dd9666f855411ac67e -------------------------------------------------------------------------------- /routes/v1/wy/lyric.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | const qs = require('qs'); 4 | 5 | let lyric = async (ctx) => { 6 | if (ctx.request.method === 'GET') { 7 | var wid = ctx.request.query.wid || ''; 8 | } else if (ctx.request.method === 'POST') { 9 | var wid = ctx.request.body.wid || ''; 10 | } 11 | 12 | const cacheData = global.cache.get(ctx.request.url); 13 | if (cacheData) { 14 | ctx.rest(cacheData); 15 | return; 16 | } 17 | 18 | let result = await axios.post(`https://music.163.com/api/song/lyric?_nmclfl=1`, qs.stringify({ 19 | id: parseInt(wid.trim()), 20 | tv: -1, 21 | lv: -1, 22 | rv: -1, 23 | kv: -1, 24 | }), { 25 | headers: { 26 | "User-Agent": 'Mozilla/5.0 (Linux; U; Android 8.1.0; zh-cn; BKK-AL10 Build/HONORBKK-AL10) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/10.6 Mobile Safari/537.36', 27 | Referer: 'https://music.163.com' 28 | } 29 | }); 30 | 31 | global.cache.set(ctx.request.url, result.data); 32 | 33 | ctx.rest(result.data); 34 | } 35 | 36 | module.exports = { 37 | lyric 38 | } -------------------------------------------------------------------------------- /routes/v1/wy/mv.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | const qs = require('qs'); 4 | const encrypt = require('../../../util/crypto') 5 | 6 | let mv_url = async (ctx) => { 7 | if (ctx.request.method === 'GET') { 8 | var wid = ctx.request.query.wid || ''; 9 | var r = ctx.request.query.r || '1080'; 10 | } else if (ctx.request.method === 'POST') { 11 | var wid = ctx.request.body.wid || ''; 12 | var r = ctx.request.body.r || '1080'; 13 | } 14 | 15 | const cacheData = global.cache.get(ctx.request.url); 16 | if (cacheData) { 17 | ctx.rest(cacheData); 18 | return; 19 | } 20 | 21 | let data = encrypt.weapi({ id: parseInt(wid.trim()), r: parseInt(r.trim()), csrf_token: '' }); 22 | 23 | let result = await axios.post(`https://music.163.com/weapi/song/enhance/play/mv/url`, qs.stringify(data), { 24 | headers: { 25 | "User-Agent": 'Mozilla/5.0 (Linux; U; Android 8.1.0; zh-cn; BKK-AL10 Build/HONORBKK-AL10) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/10.6 Mobile Safari/537.36', 26 | Referer: 'https://music.163.com' 27 | } 28 | }); 29 | 30 | global.cache.set(ctx.request.url, result.data); 31 | 32 | ctx.rest(result.data); 33 | } 34 | 35 | module.exports = { 36 | mv_url 37 | } -------------------------------------------------------------------------------- /routes/v1/wy/playlist.js: -------------------------------------------------------------------------------- 1 | // const { qq_request } = require("../../../util/qq_request"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | const { default: axios } = require("axios"); 4 | const qs = require('qs') 5 | 6 | 7 | const { createCipheriv, createDecipheriv, publicEncrypt, randomBytes, createHash, constants } = require('crypto'); 8 | const iv = Buffer.from('0102030405060708') 9 | const presetKey = Buffer.from('0CoJUm6Qyw8W8jud') 10 | const linuxapiKey = Buffer.from('rFgB&h#%2?^eDg:Q') 11 | const base62 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' 12 | const publicKey = '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB\n-----END PUBLIC KEY-----' 13 | const eapiKey = 'e82ckenh8dichen8' 14 | 15 | 16 | const aesEncrypt = (buffer, mode, key, iv) => { 17 | const cipher = createCipheriv(mode, key, iv) 18 | return Buffer.concat([cipher.update(buffer), cipher.final()]) 19 | } 20 | 21 | const aesDecrypt = function (cipherBuffer, mode, key, iv) { 22 | let decipher = createDecipheriv(mode, key, iv) 23 | return Buffer.concat([decipher.update(cipherBuffer), decipher.final()]) 24 | } 25 | 26 | const rsaEncrypt = (buffer, key) => { 27 | buffer = Buffer.concat([Buffer.alloc(128 - buffer.length), buffer]) 28 | return publicEncrypt({ key, padding: constants.RSA_NO_PADDING }, buffer) 29 | } 30 | 31 | 32 | const linuxapi = object => { 33 | const text = JSON.stringify(object) 34 | return { 35 | eparams: aesEncrypt(Buffer.from(text), 'aes-128-ecb', linuxapiKey, '').toString('hex').toUpperCase(), 36 | } 37 | } 38 | 39 | 40 | const weapi = object => { 41 | const text = JSON.stringify(object) 42 | const secretKey = randomBytes(16).map(n => (base62.charAt(n % 62).charCodeAt())) 43 | return { 44 | params: aesEncrypt(Buffer.from(aesEncrypt(Buffer.from(text), 'aes-128-cbc', presetKey, iv).toString('base64')), 'aes-128-cbc', secretKey, iv).toString('base64'), 45 | encSecKey: rsaEncrypt(secretKey.reverse(), publicKey).toString('hex'), 46 | } 47 | } 48 | 49 | 50 | 51 | 52 | 53 | let playlist_Info = async (ctx) => { 54 | if (ctx.request.method === 'GET') { 55 | var pid = ctx.request.query.pid || ''; 56 | // console.log(typeof ctx.request.query.limit, limit); 57 | } else if (ctx.request.method === 'POST') { 58 | var pid = ctx.request.body.pid || ''; 59 | } 60 | 61 | const cacheData = global.cache.get(ctx.request.url); 62 | if (cacheData) { 63 | ctx.rest(cacheData); 64 | return; 65 | } 66 | 67 | if (pid.length === 0) { 68 | throw new APIError("WY_playlist", "pid not found"); 69 | } 70 | 71 | let a = linuxapi({ 72 | method: 'POST', 73 | url: 'https://music.163.com/api/v3/playlist/detail', 74 | params: { 75 | id: pid, 76 | n: 100000, 77 | s: 8 78 | }, 79 | }) 80 | 81 | 82 | 83 | const listDetail = await axios.post('https://music.163.com/api/linux/forward', qs.stringify(a), { 84 | headers: { 85 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36', 86 | // Cookie: this.cookie, 87 | }, 88 | }); 89 | 90 | // console.log(JSON.stringify(listDetail.data)); 91 | if(parseInt(listDetail.data.code) !== 200) { 92 | throw new APIError("PlayList_Error", listDetail.data.message); 93 | } 94 | let ids = 95 | listDetail.data.playlist.trackIds.map(trackId => trackId.id); 96 | 97 | let data = weapi({ 98 | c: '[' + ids.map(id => ('{"id":' + id + '}')).join(',') + ']', 99 | ids: '[' + ids.join(',') + ']', 100 | }); 101 | let result = await axios.post('https://music.163.com/weapi/v3/song/detail', qs.stringify( 102 | data 103 | ), { 104 | headers: { 105 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36', 106 | origin: 'https://music.163.com' 107 | } 108 | }); 109 | 110 | global.cache.set(ctx.request.url, result.data); 111 | 112 | ctx.rest(result.data); 113 | result = null; 114 | } 115 | 116 | module.exports = { 117 | playlist_Info 118 | } -------------------------------------------------------------------------------- /routes/v1/wy/recommendSongs.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios"); 2 | const APIError = require("../../../middlewares/rest").APIError; 3 | const qs = require('qs'); 4 | const { serialization } = require('../../../util/cookie_util'); 5 | const encrypt = require('../../../util/crypto'); 6 | 7 | let recommendSongs = async (ctx) => { 8 | if (ctx.request.method === 'GET') { 9 | } else if (ctx.request.method === 'POST') { 10 | } 11 | 12 | const cacheData = global.cache.get(ctx.request.url); 13 | if (cacheData) { 14 | ctx.rest(cacheData); 15 | return; 16 | } 17 | 18 | 19 | let data = {}; 20 | let csrfToken = (serialization(global.wy_cookie) || '').match(/_csrf=([^(;|$)]+)/); 21 | data.csrf_token = csrfToken ? csrfToken[1] : ''; 22 | data = encrypt.weapi(data); 23 | 24 | 25 | let result = await axios.post(`https://music.163.com/api/v3/discovery/recommend/songs`, qs.stringify(data), { 26 | headers: { 27 | "User-Agent": 'Mozilla/5.0 (Linux; U; Android 8.1.0; zh-cn; BKK-AL10 Build/HONORBKK-AL10) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/10.6 Mobile Safari/537.36', 28 | Referer: 'https://music.163.com', 29 | cookie: serialization(global.wy_cookie) 30 | } 31 | }); 32 | 33 | global.cache.set(ctx.request.url, result.data); 34 | 35 | ctx.rest(result.data); 36 | } 37 | 38 | module.exports = { 39 | recommendSongs 40 | } -------------------------------------------------------------------------------- /routes/v1/wy/search.js: -------------------------------------------------------------------------------- 1 | const APIError = require("../../../middlewares/rest").APIError; 2 | const { default: axios } = require("axios"); 3 | const { eapi } = require('../../../util/crypto'); 4 | const cookie_util = require("../../../util/cookie_util"); 5 | const qs = require('qs') 6 | 7 | 8 | let search = async (ctx) => { 9 | // 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频 10 | if (ctx.request.method === 'GET') { 11 | var key = ctx.request.query.key || ''; 12 | var limit = ctx.request.query.limit || '30'; 13 | var offset = ctx.request.query.offset || '1'; 14 | var type = ctx.request.query.type || '1'; 15 | } else if (ctx.request.method === 'POST') { 16 | var key = ctx.request.body.key || ''; 17 | var limit = ctx.request.body.limit || '30'; 18 | var offset = ctx.request.body.offset || '1'; 19 | var type = ctx.request.body.type || '1'; 20 | } 21 | 22 | const cacheData = global.cache.get(ctx.request.url); 23 | if (cacheData) { 24 | ctx.rest(cacheData); 25 | return; 26 | } 27 | 28 | let result = await axios.post('http://interface.music.163.com/eapi/batch', qs.stringify(eapi('/api/cloudsearch/pc', { 29 | s: key, 30 | type: parseInt(type), 31 | limit: parseInt(limit), 32 | total: offset == 1, 33 | offset: parseInt(limit) * (parseInt(offset) - 1), 34 | })), { 35 | method: 'post', 36 | headers: { 37 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36', 38 | origin: 'https://music.163.com', 39 | Cookie: cookie_util.serialization(global.wy_cookie) 40 | } 41 | }); 42 | 43 | 44 | global.cache.set(ctx.request.url, result.data); 45 | // 捕捉服务端解析错误,防止程序退出 46 | ctx.rest(result.data); 47 | } 48 | 49 | 50 | module.exports = { 51 | search 52 | } -------------------------------------------------------------------------------- /routes/v1/wy/top.js: -------------------------------------------------------------------------------- 1 | const APIError = require("../../../middlewares/rest").APIError; 2 | const { default: axios } = require("axios"); 3 | const { weapi } = require('../../../util/crypto'); 4 | const qs = require('qs'); 5 | 6 | // 排行榜分类 7 | let topCategory = async (ctx) => { 8 | if (ctx.request.method === 'GET') { 9 | // var from = ctx.request.query.from || 'pc'; 10 | } else if (ctx.request.method === 'POST') { 11 | // var from = ctx.request.body.from || 'pc'; 12 | } 13 | 14 | const cacheData = global.cache.get(ctx.request.url); 15 | if (cacheData) { 16 | ctx.rest(cacheData); 17 | return; 18 | } 19 | 20 | let result = await axios.post('https://music.163.com/weapi/toplist/detail', qs.stringify(weapi({ 21 | csrf_token: '' 22 | })), { 23 | method: 'post', 24 | headers: { 25 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36', 26 | origin: 'https://music.163.com' 27 | } 28 | }); 29 | 30 | global.cache.set(ctx.request.url, result.data, 3600); 31 | 32 | ctx.rest(result.data); 33 | } 34 | 35 | module.exports = { 36 | topCategory 37 | } -------------------------------------------------------------------------------- /setting.js: -------------------------------------------------------------------------------- 1 | 2 | // 配置文件 3 | module.exports = { 4 | migu_cookie: 'WT_FPC=id=26b591d159ccb9db45c1612840016398:lv=1617166607084:ss=1617166393661; migu_cookie_id=25e83caa-5578-43eb-8d0c-5e5a0b179d47-n41616673107345; migu_music_status=true; migu_music_uid=91226204734; migu_music_avatar=; migu_music_nickname=%E5%92%AA%E5%92%95%E7%94%A8%E6%88%B7; migu_music_level=0; migu_music_credit_level=1; migu_music_platinum=0; migu_music_msisdn=15609665675; migu_music_email=; migu_music_passid=423322612961323579; migu_music_sid=s%3AkgVJ6LXk1UXYOBKj_hZeQYS50gaMY41X.kStBVkuYzeY12O6mTRXCFW6U30pmTMfoV1LqdipPnfY; player_stop_open=1; playlist_adding=1; addplaylist_has=1; audioplayer_new=1; playlist_change=0; add_play_now=1; mg_uem_user_id_9fbe6599400e43a4a58700a822fd57f8=c4398178-8c0e-4f3c-a696-d81e00c4ab7d; mg_uem_session_id_9fbe6599400e43a4a58700a822fd57f8=fb163c22-3588-4567-ae9b-d66c115b684d; audioplayer_open=0; audioplayer_exist=1', 5 | qq_cookie: 'yqq_stat=0; pgv_info=ssid=s1520392; ts_last=y.qq.com/portal/player.html; pgv_pvid=1233495754; ts_uid=5486601780; pgv_pvi=4007713792; pgv_si=s6654436352; userAction=1; _qpsvr_localtk=0.8025676546673662; yq_index=0; yplayer_open=1; player_exist=1; qqmusic_fromtag=66', 6 | kugou_cookie: '', 7 | wy_cookie : `NMTID=345db430137bc1b21670d9e860472e27; __remember_me=true; MUSIC_U=004D69BCF104865D8078E43ACC21F2C25E51921613B40563A285BB31CBC94133FA3F687BCB5B39A26F466A3FFDAC1CC470523750C44BEC705893F6FA748072398A26FD1C65C70D57CAE6DDE4370802D2EA318D3F0318888FFE8012EBA4C4FAD4F879689E27DF01C262C94FAF82A17CABF1CDA904FB4048A450283225DA9C6C53CFB44205EBFE2ABA2A96391296FD634A86E80AF5BB75ECB49718F82CFA4390B0FCE40E9B0A6A615B23C127EB7BFE3833D592228DE80403C77AD69E474712657F2A448B15ACED2D2F78869DC68FB6AACE6D9170D84A4564ED149955B6AF8F8C1F5B6417331AC81ABA969A7F45435DC1024372F9FA5F7AC5BFC4204276054BB1C8B5D353AECE08A8D350EFBF47B4E84CDA069027952656B56748C99D7B4887BF2057; __csrf=2422b75771d3afbd3cbc90cc6e379e9f; _ntes_nuid=8bb6c1e91fce61fc4bd8517828d05a5e`, 8 | port: '5000',//服务器端口 9 | QQ_uin: "XXXXXXXX",//用于自动刷新的Cookie的QQ账号,前提是 已经上传在共享仓库中,如果想要获取Vip歌曲,请传入带绿钻的QQ号 https://github-zc.github.io/wp_MusicApi/#/ 10 | is_open_validation: false,//是否打开加密验证 11 | version: 'v1.5.2' 12 | } -------------------------------------------------------------------------------- /test/a.php: -------------------------------------------------------------------------------- 1 | 'body' => json_encode( 2 | array( 3 | 'relate' => 1, 4 | 'userid' => '0', 5 | 'vip' => 0, 6 | 'appid' => 1000, 7 | 'token' => '', 8 | 'behavior' => 'download', 9 | 'area_code' => '1', 10 | 'clientver' => '8990', 11 | 'resource' => array(array( 12 | 'id' => 0, 13 | 'type' => 'audio', 14 | 'hash' => $id, 15 | )), ) 16 | ), 17 | 18 | 19 | JSON.stringify([ 20 | relate 21 | ]) -------------------------------------------------------------------------------- /test/t copy.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios"); 2 | const CryptoJS = require('crypto-js'); 3 | 4 | 5 | (async () => { 6 | 7 | 8 | let a = parseInt((new Date()).getTime()).toString(); 9 | 10 | var words = CryptoJS.enc.Utf8.parse("SGVsbG8ssdfadfIFdvcmxQSGVsbG8ssd"); 11 | var iv = CryptoJS.enc.Utf8.parse("1011121314151617"); 12 | 13 | var encrypted = CryptoJS.AES.encrypt(a, words , {iv}); 14 | 15 | try { 16 | let result = await axios.get('http://42.192.118.65:5100/aggregate/search?key=%E8%AE%B8%E5%B5%A9&offset=1', { 17 | headers: { 18 | 'imax-music': encrypted.toString() 19 | } 20 | }); 21 | console.log(result.data); 22 | } catch (error) { 23 | console.log(error); 24 | } 25 | 26 | try { 27 | let result = await axios.get('http://42.192.118.65:5100/kugou/song?hash=A1105CC90AF7168721E805E4ADED8F7A', { 28 | headers: { 29 | 'imax-music': encrypted.toString() 30 | } 31 | }); 32 | console.log(result.data); 33 | } catch (error) { 34 | console.log(error); 35 | } 36 | 37 | try { 38 | // 酷我br {24, 48, 96, 128, 192, 320, ape, flac, hires} 39 | let result = await axios.get('http://42.192.118.65:5100/kuwo/song?id=213671654&br=hires', { 40 | headers: { 41 | 'imax-music': encrypted.toString() 42 | } 43 | }); 44 | console.log(result.data); 45 | } catch (error) { 46 | console.log(error); 47 | } 48 | })(); -------------------------------------------------------------------------------- /test/t.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios"); 2 | const CryptoJS = require('crypto-js'); 3 | 4 | 5 | (async () => { 6 | 7 | 8 | let a = parseInt((new Date()).getTime() / 1000).toString(); 9 | // a = '12412123213'; 10 | // Encrypt 11 | let ciphertext = CryptoJS.AES.encrypt(a, (new Date()).getMinutes().toString() + 'QWEASDZXC'); 12 | 13 | console.log(ciphertext.iv.toString()); 14 | console.log(ciphertext.salt.toString()); 15 | console.log(ciphertext.key.toString()); 16 | 17 | console.log(CryptoJS.SHA256(a).toString()); 18 | 19 | // try { 20 | // let result = await axios.post('http://43.138.145.42:5001/v1/wy/setcookie', { 21 | // data: 'NTES_P_UTID=ADCXoiSt7mER2Q9bHQUehzzFTds5r3SY|1666877170; nts_mail_user=i153140965@163.com:-1:1; _ntes_nuid=2d881a8b54b2284a6689c2c60449140d; _ntes_nnid=2d881a8b54b2284a6689c2c60449140d,1642125839709; NMTID=00OIWMmS9I0773lpk11uGYZe1GQ0AcAAAF-VlPOfw; WNMCID=qsuewa.1642125840188.01.0; WM_TID=9BYOtDLvlL5BBQAUVUMu%2BHZsMhu3Q3Bs; NTES_CMT_USER_INFO=272331503%7C%E6%9C%89%E6%80%81%E5%BA%A6%E7%BD%91%E5%8F%8B0geTbL%7Chttp%3A%2F%2Fcms-bucket.nosdn.127.net%2F2018%2F08%2F13%2F078ea9f65d954410b62a52ac773875a1.jpeg%7Cfalse%7CaTE1MzE0MDk2NUAxNjMuY29t; ntes_kaola_ad=1; __snaker__id=1Vn5NGpDXStuXuqT; _9755xjdesxxd_=32; YD00000558929251%3AWM_TID=gFt8cdPgYHBBUERFQBfVDZCUS6OxyZWH; __bid_n=183e4c63689cd43abc4207; hb_MA-B701-2FC93ACD9328_source=mail.stu.shmtu.edu.cn; P_INFO=i153140965@163.com|1666877170|0|mail163|00&99|null&null&null#shh&null#10#0#0|&0||i153140965@163.com; WM_NI=grSiwBHZ%2FCe7Hg%2Bq2gKeCjoyhobq2ucSHf%2BWlXpzAwIMNYptYu1AqcXe2R85HChBHADTr4fljr4P3HsI%2BSzZn19uaNBEKJiPqC0gYIJt0FMbcojk5BYQFUCzD0jn2UszWXk%3D; WM_NIKE=9ca17ae2e6ffcda170e2e6eea2b36d81eba990cd4d98968fb6d54f938f8aacc47087ef9697ef7bb6bd8fccd12af0fea7c3b92aa5ae9d8af64a8c86e1a4e83d8cb9e592b16d89bb8d8bee73acb1ae92b17fa18d8887aa5db088c09bc47d8b9382baf3809babffa2f4699cbbfeb3bb3f8c9da8a3cb4b888cbed5e568aaaefcb1b244b197e5b4f952aeeff796e446829b85abec5cb39d0088dc438aac00a7d06aa1b1a68cb84a9b8f8ab6c27288f0a68fb15fb1a699b9ea37e2a3; JSESSIONID-WYYY=SyZIAN%2BluMB7sT9o8hgx12nzgdBj874muxQhQHvbWCFPabJNcXKohh8kJ%2BqjxsChu%5CXlKtSllx01xauM%2BeJTBFKXaX4%5CE5%5CzmqlatxPQcVgVV%2FxG0E49%2FI5Fkzcu2%2FSMEs5s3uzo611c1qg2r4aAXWhVol2QGEDyT%5CH3eZdishv9aH9f%3A1667035526670; _iuqxldmzr_=33; gdxidpyhxdE=rZU24o7i078jcQ9BCPPn3vsbfZGlfL3OKqfUcSWZ8EItZ4p1eIYyMzcymNRuqH7qit5BnwXma68z9mS1cJBolkU0XTPQbLLeD8y%2Fd4xPdu5n67cbSBWSjrfufBtYeTtTyJwgfiCiOyJ0S78JQsVvnPB%2FxT%5CYVmWCPxRUDDLa94o%2BtKSq%3A1667034627197; YD00000558929251%3AWM_NI=dQtq3bi4fMryIvaPsZ1MmTq93vBOstFA9NyvYCaPaiMJ275DLPjUsxhzXZlgxQrRHqyxWUMG0iEBp9Lp8PNFTXAbuAK%2FIzEnv6Fzu2NI%2Fz%2FFENQYid5F%2BQhuPKO2J4EMNlA%3D; YD00000558929251%3AWM_NIKE=9ca17ae2e6ffcda170e2e6ee87bb73b09ae1a7f93da2e78bb7c85f969a8eacc55396a98397ed5a839a8790d12af0fea7c3b92aa88fbc92b26aabecaaaef1738baaafb1f252f393a3b5f433f1aa8eb0f280869ff7affc7e9b9dafb1e4638f96fed8b8509090a6b6f274aca698bbef6dbc8fbeb3cb7bbba899b6d04df4b78ba3f33390b0f8b1cd4e98888f83f84b89f1879bc866b6ea8a9bf83a8e97e19bae4e87929fbaec70f79ae5b2eb7f949581d3c9408793828dc437e2a3; __csrf=65c04c852b653455f5583577b74062e4; WEVNSM=1.0.0' 22 | // }, { 23 | // headers: { 24 | // 'imax-music': ciphertext 25 | // } 26 | // }); 27 | // console.log(result.data); 28 | // } catch (error) { 29 | // console.log(error); 30 | // } 31 | 32 | 33 | try { 34 | let result = await axios.get('http://43.138.145.42:5002/v1/wy/getcookie', { 35 | headers: { 36 | 'imax-music': ciphertext 37 | } 38 | }); 39 | console.log(result.data); 40 | } catch (error) { 41 | console.log(error); 42 | } 43 | })(); -------------------------------------------------------------------------------- /test/testwysong.js: -------------------------------------------------------------------------------- 1 | const wp_musicapi = require('../'); 2 | 3 | 4 | // 网易播放链接 自己传入cookie 5 | (async () => { 6 | let res = await wp_musicapi["/v1/wy/song"]({ 7 | cookie: `_ntes_nuid=a1cb137b406096f92c2a9b9164092baf; mail_psc_fingerprint=5e503c689a2feeb488e31fc33c0a7ac5; vjuids=312e62a27.17b104f939e.0.493b1e5ad0526; vjlast=1628066256.1628066256.30; usertrack=ezq0ZWIDI0ZYIk5JA7DWAg==; NMTID=00OZeaiWVKVtFe_e0nznqw1xGS4coUAAAF_SbIy3A; WEVNSM=1.0.0; WNMCID=nrdxju.1646208889988.01.0; _ntes_nnid=a1cb137b406096f92c2a9b9164092baf,1656465320461; __snaker__id=KPVOWKmE51ityZ8a; _9755xjdesxxd_=32; ntes_kaola_ad=1; NTES_SESS=VaI9Cy3gBjsZO2tZUDEhA6QTkfCaXdGvHPLPpAiFm7v6ceKDcQzbiWZ0xv88HTqS9skCy08NsGzRc_TK3pcVTXLwrMgJdP3dHqBJoNFpNqR_qwM6i4QRBeeRHmiPgb65BA0UZWWEMtlM0d2Jkh6J8WAmqZX9qMmr5KoklCuOJmEKSbLJCUb8gy.JBGDQ6NlqntBmHWtm6s6FW; S_INFO=1664697322|0|3&80##|molu782572516; P_INFO="molu782572516@163.com|1664697322|0|unireg|00&99|null&null&null#hun&null#10#0#0|&0||molu782572516@163.com"; playliststatus=visible; WM_TID=PyPQss6BssBABVAQVEeFS4ZrmxeFanX5; MUSIC_EMAIL_U=8b9ea37d7abdef5d083dcac4097451245ad7bfae54577152f42c19f6d4410eaf90f2302f8ddc39106462a38a3ceb6a28e8ce99300d2b030e792a1b3d7859feae7818932f7339bc8fccbd11045206afdd1b93ac14e0ed86ab; wyy_uid=78cda97f-aa07-4a88-a530-ee6565d1c87a; __root_domain_v=.163.com; _qddaz=QD.994865058316067; locale=zh_CN; urs_t=qVtGMU-1eTCDm4BEOJLgnr4g; urs_u=f16GMaLL50PiwkOL/2Rd8V6tmePzyRBlKDd6iLqodhs1jwh4KGSq9Szm7Cozi0zwoc5-9vLrT-o1TFIL249Fqp7CQ0ubYlQYn2NjphwPd97TqIZ-HKG1arjw7OLnyHaPaai63pc6aoflXD306J7W-H1WFgdLeUf-UDGNo1Y7/KP7/kq3XGbk9MNjH4QiuHCgbsOIBv9zQdTUpZC32nZqHNvFErWHbqV55T5RfoGgTqO-j80fVs6XSImkYQjKqAlhjjIP1X8A6HL0IUc4XGoXNoSXHIB7TqdgB9Vr4-vwRtIUv2XUKs1-iRGBIz0HdSSg; NICE_CSRF=""; playerid=68865577; YD00000558929251%3AWM_TID=k4IaNWHfqXZFUUQUFUPRdRmh6i5Ci%2Fxc; timing_user_id=time_lRfGpSTDiS; JSESSIONID-WYYY=VHTmqM7YjbobD%2FwWThAoCDkMc%5CyQfO5Njz4vfz%5CTERgZDzbU96z%5CsAe14UNiTfE48yArQvaDKkuAZnWCHIXU3vTT3R%2BzYDCH6OjZbi9tJv5w6FUdIfKaAepHQw4ZzpKPe25zfz8mVczNTgXCRFjntl3mhKJECu0NmP3mOXiFVXXeYVp3%3A1668085231068; _iuqxldmzr_=33; WM_NI=2ZIy3utsHzTnEY%2F32wcffyTr6fMAMB4ZB6UOPtdQu5xKhRfZJeNhfsTv9%2F5cMFQXRi%2B74JYen1ga07H3sa58W7%2FgFDl2ZOVxt52jQupNNVu62T0XqRbi9t%2FEHomfefMLUkY%3D; WM_NIKE=9ca17ae2e6ffcda170e2e6ee8fc670b8e7aa98b33b8f868ea6c44b939b8ab1d15281a99d8ddb69a3ecbd9bbb2af0fea7c3b92af286879ab27fb8ad889bd7668787e1d4b17da1baaa9ad77af6ba83a2d04af8868ed7d84594acacb2f83fbc95a8b3e5528abb8289e57998aaf7d0b8698f9a9fb9d163e9e9fcabdc6faeefa982b33d8cedbd82b83495e9e188c550b0ee96b0d574b8ebfe8bd47fb49282b7f83ef28c00b9f45aa2b7f795ae4f8cb1a387e86e8f949b8cdc37e2a3; YD00000558929251%3AWM_NI=q3IvsKRX45r7ivQHOLXEgMIIrjBQjOM5qmWzlmh4XngYhbvrA8XnNP4lB%2BJVAO7IWOF%2FsrFMMQIucafFeHay5sdde8q7B2KiASMj2hxGGUz3q%2FwJhzw2oECEaVMAgjIgUnY%3D; YD00000558929251%3AWM_NIKE=9ca17ae2e6ffcda170e2e6eeb9d35b9aeb8db9b4668ab48fb6d14a939a9b82c15281a889d1db6989aefe8ae62af0fea7c3b92ab3ed8797f2419288fcd6f13dabaa9fa9ce489c9ba9aecc43b2b68eb2e9458febbf8ed672b0b3a8d5db66a587a883f57a83e8988fcd5cf8b48eccc95e8391f9aad64586f1bd93c5478f8f8796e26ba69aaba9e473f7adac85e834b3e98ed3ca63838d838bb164bc9989b6f939b3f1b9abeb72b5eea1a6c24fa38a83a2e6628bb99b8fdc37e2a3; gdxidpyhxdE=Q61pvCC1cpZIsLBLtCRXqjtlbOKTqqZGxqwgh0WxYIsMd10gUI8wg855paj6VSss%2B1aGMkbIUCRDNNzLRjpDMOe%2FoedNuVMJbe9dtnGMXP6BneDcmIQIwBkcbqDEeoylknM1JYfDM7g%2BWsWdXN%5CzC5tm%2F03%2Brfz3%5Cx0%2Ftim%5CwrqIpRET%3A1668084333474; __csrf=5f844f80ac5bb2caf1722091db11651c; __remember_me=true; MUSIC_U=71278ae4829eb7192e53d3b92c838eed0c6f002ede968b8dd8968fe5a7be95ae2440949cf8ac6b9a6d34b39ff2576e41613cf731b735e6611654cf6709263a0d1d645bedcf7aeb9aa0d2166338885bd7`, 8 | br: "hires" 9 | }); 10 | 11 | console.log(JSON.stringify(res)); 12 | })(); -------------------------------------------------------------------------------- /util/MD5.js: -------------------------------------------------------------------------------- 1 | let MD5 = function (a) { 2 | function b(a) { 3 | var b = (a >>> 0).toString(16); 4 | return '00000000'.substr(0, 8 - b.length) + b 5 | } 6 | function c(a) { 7 | for (var b = [ 8 | ], c = 0; c < a.length; c++) b = b.concat(k(a[c])); 9 | return b 10 | } 11 | function d(a) { 12 | for (var b = [ 13 | ], c = 0; 8 > c; c++) b.push(255 & a), 14 | a >>>= 8; 15 | return b 16 | } 17 | function e(a, b) { 18 | return a << b & 4294967295 | a >>> 32 - b 19 | } 20 | function f(a, b, c) { 21 | return a & b | ~a & c 22 | } 23 | function g(a, b, c) { 24 | return c & a | ~c & b 25 | } 26 | function h(a, b, c) { 27 | return a ^ b ^ c 28 | } 29 | function i(a, b, c) { 30 | return b ^ (a | ~c) 31 | } 32 | function j(a, b) { 33 | return a[b + 3] << 24 | a[b + 2] << 16 | a[b + 1] << 8 | a[b] 34 | } 35 | function k(a) { 36 | for (var b = [ 37 | ], c = 0; c < a.length; c++) if (a.charCodeAt(c) <= 127) b.push(a.charCodeAt(c)); 38 | else for (var d = encodeURIComponent(a.charAt(c)).substr(1).split('%'), e = 0; e < d.length; e++) b.push(parseInt(d[e], 16)); 39 | return b 40 | } 41 | function l() { 42 | for (var a = '', c = 0, d = 0, e = 3; e >= 0; e--) d = arguments[e], 43 | c = 255 & d, 44 | d >>>= 8, 45 | c <<= 8, 46 | c |= 255 & d, 47 | d >>>= 8, 48 | c <<= 8, 49 | c |= 255 & d, 50 | d >>>= 8, 51 | c <<= 8, 52 | c |= d, 53 | a += b(c); 54 | return a 55 | } 56 | function m(a) { 57 | for (var b = new Array(a.length), c = 0; c < a.length; c++) b[c] = a[c]; 58 | return b 59 | } 60 | function n(a, b) { 61 | return 4294967295 & a + b 62 | } 63 | function o() { 64 | function a(a, b, c, d) { 65 | var f = v; 66 | v = u, 67 | u = t, 68 | t = n(t, e(n(s, n(a, n(b, c))), d)), 69 | s = f 70 | } 71 | var b = p.length; 72 | p.push(128); 73 | var c = p.length % 64; 74 | if (c > 56) { 75 | for (var k = 0; 64 - c > k; k++) p.push(0); 76 | c = p.length % 64 77 | } 78 | for (k = 0; 56 - c > k; k++) p.push(0); 79 | p = p.concat(d(8 * b)); 80 | var m = 1732584193, 81 | o = 4023233417, 82 | q = 2562383102, 83 | r = 271733878, 84 | s = 0, 85 | t = 0, 86 | u = 0, 87 | v = 0; 88 | for (k = 0; k < p.length / 64; k++) { 89 | s = m, 90 | t = o, 91 | u = q, 92 | v = r; 93 | var w = 64 * k; 94 | a(f(t, u, v), 3614090360, j(p, w), 7), 95 | a(f(t, u, v), 3905402710, j(p, w + 4), 12), 96 | a(f(t, u, v), 606105819, j(p, w + 8), 17), 97 | a(f(t, u, v), 3250441966, j(p, w + 12), 22), 98 | a(f(t, u, v), 4118548399, j(p, w + 16), 7), 99 | a(f(t, u, v), 1200080426, j(p, w + 20), 12), 100 | a(f(t, u, v), 2821735955, j(p, w + 24), 17), 101 | a(f(t, u, v), 4249261313, j(p, w + 28), 22), 102 | a(f(t, u, v), 1770035416, j(p, w + 32), 7), 103 | a(f(t, u, v), 2336552879, j(p, w + 36), 12), 104 | a(f(t, u, v), 4294925233, j(p, w + 40), 17), 105 | a(f(t, u, v), 2304563134, j(p, w + 44), 22), 106 | a(f(t, u, v), 1804603682, j(p, w + 48), 7), 107 | a(f(t, u, v), 4254626195, j(p, w + 52), 12), 108 | a(f(t, u, v), 2792965006, j(p, w + 56), 17), 109 | a(f(t, u, v), 1236535329, j(p, w + 60), 22), 110 | a(g(t, u, v), 4129170786, j(p, w + 4), 5), 111 | a(g(t, u, v), 3225465664, j(p, w + 24), 9), 112 | a(g(t, u, v), 643717713, j(p, w + 44), 14), 113 | a(g(t, u, v), 3921069994, j(p, w), 20), 114 | a(g(t, u, v), 3593408605, j(p, w + 20), 5), 115 | a(g(t, u, v), 38016083, j(p, w + 40), 9), 116 | a(g(t, u, v), 3634488961, j(p, w + 60), 14), 117 | a(g(t, u, v), 3889429448, j(p, w + 16), 20), 118 | a(g(t, u, v), 568446438, j(p, w + 36), 5), 119 | a(g(t, u, v), 3275163606, j(p, w + 56), 9), 120 | a(g(t, u, v), 4107603335, j(p, w + 12), 14), 121 | a(g(t, u, v), 1163531501, j(p, w + 32), 20), 122 | a(g(t, u, v), 2850285829, j(p, w + 52), 5), 123 | a(g(t, u, v), 4243563512, j(p, w + 8), 9), 124 | a(g(t, u, v), 1735328473, j(p, w + 28), 14), 125 | a(g(t, u, v), 2368359562, j(p, w + 48), 20), 126 | a(h(t, u, v), 4294588738, j(p, w + 20), 4), 127 | a(h(t, u, v), 2272392833, j(p, w + 32), 11), 128 | a(h(t, u, v), 1839030562, j(p, w + 44), 16), 129 | a(h(t, u, v), 4259657740, j(p, w + 56), 23), 130 | a(h(t, u, v), 2763975236, j(p, w + 4), 4), 131 | a(h(t, u, v), 1272893353, j(p, w + 16), 11), 132 | a(h(t, u, v), 4139469664, j(p, w + 28), 16), 133 | a(h(t, u, v), 3200236656, j(p, w + 40), 23), 134 | a(h(t, u, v), 681279174, j(p, w + 52), 4), 135 | a(h(t, u, v), 3936430074, j(p, w), 11), 136 | a(h(t, u, v), 3572445317, j(p, w + 12), 16), 137 | a(h(t, u, v), 76029189, j(p, w + 24), 23), 138 | a(h(t, u, v), 3654602809, j(p, w + 36), 4), 139 | a(h(t, u, v), 3873151461, j(p, w + 48), 11), 140 | a(h(t, u, v), 530742520, j(p, w + 60), 16), 141 | a(h(t, u, v), 3299628645, j(p, w + 8), 23), 142 | a(i(t, u, v), 4096336452, j(p, w), 6), 143 | a(i(t, u, v), 1126891415, j(p, w + 28), 10), 144 | a(i(t, u, v), 2878612391, j(p, w + 56), 15), 145 | a(i(t, u, v), 4237533241, j(p, w + 20), 21), 146 | a(i(t, u, v), 1700485571, j(p, w + 48), 6), 147 | a(i(t, u, v), 2399980690, j(p, w + 12), 10), 148 | a(i(t, u, v), 4293915773, j(p, w + 40), 15), 149 | a(i(t, u, v), 2240044497, j(p, w + 4), 21), 150 | a(i(t, u, v), 1873313359, j(p, w + 32), 6), 151 | a(i(t, u, v), 4264355552, j(p, w + 60), 10), 152 | a(i(t, u, v), 2734768916, j(p, w + 24), 15), 153 | a(i(t, u, v), 1309151649, j(p, w + 52), 21), 154 | a(i(t, u, v), 4149444226, j(p, w + 16), 6), 155 | a(i(t, u, v), 3174756917, j(p, w + 44), 10), 156 | a(i(t, u, v), 718787259, j(p, w + 8), 15), 157 | a(i(t, u, v), 3951481745, j(p, w + 36), 21), 158 | m = n(m, s), 159 | o = n(o, t), 160 | q = n(q, u), 161 | r = n(r, v) 162 | } 163 | return l(r, q, o, m).toUpperCase() 164 | } 165 | var p = null, 166 | q = null; 167 | return 'string' == typeof a ? p = k(a) : a.constructor == Array ? 0 === a.length ? p = a : 'string' == typeof a[0] ? p = c(a) : 'number' == typeof a[0] ? p = a : q = typeof a[0] : 'undefined' != typeof ArrayBuffer ? a instanceof ArrayBuffer ? p = m(new Uint8Array(a)) : a instanceof Uint8Array || a instanceof Int8Array ? p = m(a) : a instanceof Uint32Array || a instanceof Int32Array || a instanceof Uint16Array || a instanceof Int16Array || a instanceof Float32Array || a instanceof Float64Array ? p = m(new Uint8Array(a.buffer)) : q = typeof a : q = typeof a, 168 | q && alert('MD5 type mismatch, cannot process ' + q), 169 | o() 170 | } 171 | 172 | 173 | 174 | module.exports = { 175 | MD5 176 | } -------------------------------------------------------------------------------- /util/cache.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | 3 | class Cache { 4 | constructor() { 5 | this.keyMap = {}; 6 | this.timeMap = {}; 7 | } 8 | 9 | clear() { 10 | const nowKey = moment().format('YYYYMMDDHHmmss'); 11 | // if(this.lastClear === nowKey) { 12 | // return; 13 | // } 14 | 15 | const clearTimeArr = Object.keys(this.timeMap).filter((v) => v <= nowKey); 16 | clearTimeArr.forEach((timeKey) => { 17 | this.timeMap[timeKey].forEach((key) => { 18 | delete this.keyMap[key]; 19 | }) 20 | delete this.timeMap[timeKey]; 21 | }) 22 | // this.lastClear = nowKey; 23 | } 24 | 25 | get(key) { 26 | // this.clear(); 27 | return this.keyMap[key]; 28 | } 29 | 30 | set(key, value, time = 300) { 31 | // this.clear(); 32 | const timeKey = moment().add(time, 's').format('YYYYMMDDHHmmss'); 33 | this.keyMap[key] = value; 34 | this.timeMap[timeKey] = this.timeMap[timeKey] || []; 35 | this.timeMap[timeKey].push(key); 36 | } 37 | } 38 | 39 | module.exports = Cache; -------------------------------------------------------------------------------- /util/cookie_util.js: -------------------------------------------------------------------------------- 1 | const APIError = require("../middlewares/rest").APIError; 2 | 3 | module.exports = { 4 | // 解析cookie 5 | parse (cookies) { 6 | // 捕获错误 7 | try { 8 | if(typeof cookies === 'string') { 9 | const cookieObj = {}; 10 | cookies.split('; ').forEach((c) => { 11 | const arr = c.split('='); 12 | let key = arr[0]; 13 | let value = arr[1]; 14 | if(arr.length > 2) { 15 | arr.shift(); 16 | value = arr.join('='); 17 | } 18 | cookieObj[key.toString()] = value; 19 | }); 20 | // console.log(cookieObj); 21 | return cookieObj; 22 | } else if(typeof cookies === 'object') { 23 | return cookies; 24 | } else { 25 | throw new Error("parse error"); 26 | } 27 | } catch (error) { 28 | // throw error; 29 | throw new APIError('Cookie:parse_error', 'cookie parse error'); 30 | } 31 | }, 32 | serialization (data) { 33 | 34 | let cookie = ''; 35 | for (let c in data) { 36 | let s = `${encodeURI(c)}=${encodeURI(data[c])}; `; 37 | cookie += s; 38 | } 39 | if (cookie === '') { 40 | throw new APIError('Cookie:serialization_error', 'serialization error'); 41 | } 42 | // console.log(cookie.substring(0, cookie.length - 4)); 43 | return cookie.substring(0, cookie.length - 2); 44 | } 45 | } -------------------------------------------------------------------------------- /util/crypto.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto') 2 | const iv = Buffer.from('0102030405060708') 3 | const presetKey = Buffer.from('0CoJUm6Qyw8W8jud') 4 | const linuxapiKey = Buffer.from('rFgB&h#%2?^eDg:Q') 5 | const base62 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' 6 | const publicKey = 7 | '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB\n-----END PUBLIC KEY-----' 8 | const eapiKey = 'e82ckenh8dichen8' 9 | 10 | const aesEncrypt = (buffer, mode, key, iv) => { 11 | const cipher = crypto.createCipheriv('aes-128-' + mode, key, iv) 12 | return Buffer.concat([cipher.update(buffer), cipher.final()]) 13 | } 14 | 15 | const rsaEncrypt = (buffer, key) => { 16 | buffer = Buffer.concat([Buffer.alloc(128 - buffer.length), buffer]) 17 | return crypto.publicEncrypt( 18 | { key: key, padding: crypto.constants.RSA_NO_PADDING }, 19 | buffer, 20 | ) 21 | } 22 | 23 | const weapi = (object) => { 24 | const text = JSON.stringify(object) 25 | const secretKey = crypto 26 | .randomBytes(16) 27 | .map((n) => base62.charAt(n % 62).charCodeAt()) 28 | return { 29 | params: aesEncrypt( 30 | Buffer.from( 31 | aesEncrypt(Buffer.from(text), 'cbc', presetKey, iv).toString('base64'), 32 | ), 33 | 'cbc', 34 | secretKey, 35 | iv, 36 | ).toString('base64'), 37 | encSecKey: rsaEncrypt(secretKey.reverse(), publicKey).toString('hex'), 38 | } 39 | } 40 | 41 | const linuxapi = (object) => { 42 | const text = JSON.stringify(object) 43 | return { 44 | eparams: aesEncrypt(Buffer.from(text), 'ecb', linuxapiKey, '') 45 | .toString('hex') 46 | .toUpperCase(), 47 | } 48 | } 49 | 50 | const eapi = (url, object) => { 51 | const text = typeof object === 'object' ? JSON.stringify(object) : object 52 | const message = `nobody${url}use${text}md5forencrypt` 53 | const digest = crypto.createHash('md5').update(message).digest('hex') 54 | const data = `${url}-36cd479b6b5-${text}-36cd479b6b5-${digest}` 55 | return { 56 | params: aesEncrypt(Buffer.from(data), 'ecb', eapiKey, '') 57 | .toString('hex') 58 | .toUpperCase(), 59 | } 60 | } 61 | 62 | const decrypt = (cipherBuffer) => { 63 | const decipher = crypto.createDecipheriv('aes-128-ecb', eapiKey, '') 64 | return Buffer.concat([decipher.update(cipherBuffer), decipher.final()]) 65 | } 66 | 67 | module.exports = { weapi, linuxapi, eapi, decrypt } 68 | -------------------------------------------------------------------------------- /util/decodeLyric.js: -------------------------------------------------------------------------------- 1 | const { inflate } = require('zlib'); 2 | const iconv = require('iconv-lite'); 3 | 4 | const handleInflate = data => new Promise((resolve, reject) => { 5 | inflate(data, (err, result) => { 6 | if (err) return reject(err) 7 | resolve(result) 8 | }) 9 | }) 10 | 11 | const buf_key = Buffer.from('yeelion') 12 | const buf_key_len = buf_key.length 13 | 14 | const decodeLyric = async (buf, isGetLyricx) => { 15 | // const info = buf.slice(0, index).toString() 16 | // if (!info.startsWith('tp=content')) return null 17 | // const isLyric = info.includes('\r\nlrcx=0\r\n') 18 | if (buf.toString('utf8', 0, 10) != 'tp=content') return '' 19 | // const index = buf.indexOf('\r\n\r\n') + 4 20 | const lrcData = await handleInflate(buf.slice(buf.indexOf('\r\n\r\n') + 4)) 21 | 22 | if (!isGetLyricx) return iconv.decode(lrcData, 'gb18030') 23 | 24 | const buf_str = Buffer.from(lrcData.toString(), 'base64') 25 | const buf_str_len = buf_str.length 26 | const output = new Uint16Array(buf_str_len) 27 | let i = 0 28 | while (i < buf_str_len) { 29 | let j = 0 30 | while (j < buf_key_len && i < buf_str_len) { 31 | output[i] = buf_str[i] ^ buf_key[j] 32 | i++ 33 | j++ 34 | } 35 | } 36 | 37 | return iconv.decode(Buffer.from(output), 'gb18030') 38 | } 39 | 40 | 41 | const enc_key = Buffer.from([0x40, 0x47, 0x61, 0x77, 0x5e, 0x32, 0x74, 0x47, 0x51, 0x36, 0x31, 0x2d, 0xce, 0xd2, 0x6e, 0x69], 'binary') 42 | const decodeKgLyric = str => new Promise((resolve, reject) => { 43 | if (!str.length) return 44 | const buf_str = Buffer.from(str, 'base64').slice(4) 45 | for (let i = 0, len = buf_str.length; i < len; i++) { 46 | buf_str[i] = buf_str[i] ^ enc_key[i % 16] 47 | } 48 | inflate(buf_str, (err, result) => { 49 | if (err) return reject(err) 50 | resolve(result.toString()) 51 | }) 52 | }) 53 | 54 | module.exports = { 55 | decodeKwLyric: async ({ lrcBase64, isGetLyricx }) => { 56 | const lrc = await decodeLyric(Buffer.from(lrcBase64, 'base64'), isGetLyricx); 57 | return Buffer.from(lrc).toString('base64'); 58 | }, 59 | decodeKgLyric 60 | } -------------------------------------------------------------------------------- /util/kugou_mid.js: -------------------------------------------------------------------------------- 1 | function guid() { 2 | function e() { 3 | return (65536 * (1 + Math.random()) | 0).toString(16).substring(1) 4 | } 5 | return e() + e() + "-" + e() + "-" + e() + "-" + e() + "-" + e() + e() + e() 6 | }; 7 | function md5(e) { 8 | var t, n = 0, o = 8; 9 | function a(e, t, r, i, n, o) { 10 | return m((o = m(m(t, e), m(i, o))) << (n = n) | o >>> 32 - n, r) 11 | } 12 | function u(e, t, r, i, n, o, s) { 13 | return a(t & r | ~t & i, e, t, n, o, s) 14 | } 15 | function l(e, t, r, i, n, o, s) { 16 | return a(t & i | r & ~i, e, t, n, o, s) 17 | } 18 | function p(e, t, r, i, n, o, s) { 19 | return a(t ^ r ^ i, e, t, n, o, s) 20 | } 21 | function f(e, t, r, i, n, o, s) { 22 | return a(r ^ (t | ~i), e, t, n, o, s) 23 | } 24 | function m(e, t) { 25 | var r = (65535 & e) + (65535 & t); 26 | return (e >> 16) + (t >> 16) + (r >> 16) << 16 | 65535 & r 27 | } 28 | return e = e ? function (e) { 29 | for (var t = n ? "0123456789ABCDEF" : "0123456789abcdef", r = "", i = 0; i < 4 * e.length; i++) 30 | r += t.charAt(e[i >> 2] >> i % 4 * 8 + 4 & 15) + t.charAt(e[i >> 2] >> i % 4 * 8 & 15); 31 | return r 32 | }(function (e, t) { 33 | e[t >> 5] |= 128 << t % 32, 34 | e[14 + (t + 64 >>> 9 << 4)] = t; 35 | for (var r = 1732584193, i = -271733879, n = -1732584194, o = 271733878, s = 0; s < e.length; s += 16) { 36 | var a = r 37 | , g = i 38 | , c = n 39 | , d = o; 40 | r = u(r, i, n, o, e[s + 0], 7, -680876936), 41 | o = u(o, r, i, n, e[s + 1], 12, -389564586), 42 | n = u(n, o, r, i, e[s + 2], 17, 606105819), 43 | i = u(i, n, o, r, e[s + 3], 22, -1044525330), 44 | r = u(r, i, n, o, e[s + 4], 7, -176418897), 45 | o = u(o, r, i, n, e[s + 5], 12, 1200080426), 46 | n = u(n, o, r, i, e[s + 6], 17, -1473231341), 47 | i = u(i, n, o, r, e[s + 7], 22, -45705983), 48 | r = u(r, i, n, o, e[s + 8], 7, 1770035416), 49 | o = u(o, r, i, n, e[s + 9], 12, -1958414417), 50 | n = u(n, o, r, i, e[s + 10], 17, -42063), 51 | i = u(i, n, o, r, e[s + 11], 22, -1990404162), 52 | r = u(r, i, n, o, e[s + 12], 7, 1804603682), 53 | o = u(o, r, i, n, e[s + 13], 12, -40341101), 54 | n = u(n, o, r, i, e[s + 14], 17, -1502002290), 55 | i = u(i, n, o, r, e[s + 15], 22, 1236535329), 56 | r = l(r, i, n, o, e[s + 1], 5, -165796510), 57 | o = l(o, r, i, n, e[s + 6], 9, -1069501632), 58 | n = l(n, o, r, i, e[s + 11], 14, 643717713), 59 | i = l(i, n, o, r, e[s + 0], 20, -373897302), 60 | r = l(r, i, n, o, e[s + 5], 5, -701558691), 61 | o = l(o, r, i, n, e[s + 10], 9, 38016083), 62 | n = l(n, o, r, i, e[s + 15], 14, -660478335), 63 | i = l(i, n, o, r, e[s + 4], 20, -405537848), 64 | r = l(r, i, n, o, e[s + 9], 5, 568446438), 65 | o = l(o, r, i, n, e[s + 14], 9, -1019803690), 66 | n = l(n, o, r, i, e[s + 3], 14, -187363961), 67 | i = l(i, n, o, r, e[s + 8], 20, 1163531501), 68 | r = l(r, i, n, o, e[s + 13], 5, -1444681467), 69 | o = l(o, r, i, n, e[s + 2], 9, -51403784), 70 | n = l(n, o, r, i, e[s + 7], 14, 1735328473), 71 | i = l(i, n, o, r, e[s + 12], 20, -1926607734), 72 | r = p(r, i, n, o, e[s + 5], 4, -378558), 73 | o = p(o, r, i, n, e[s + 8], 11, -2022574463), 74 | n = p(n, o, r, i, e[s + 11], 16, 1839030562), 75 | i = p(i, n, o, r, e[s + 14], 23, -35309556), 76 | r = p(r, i, n, o, e[s + 1], 4, -1530992060), 77 | o = p(o, r, i, n, e[s + 4], 11, 1272893353), 78 | n = p(n, o, r, i, e[s + 7], 16, -155497632), 79 | i = p(i, n, o, r, e[s + 10], 23, -1094730640), 80 | r = p(r, i, n, o, e[s + 13], 4, 681279174), 81 | o = p(o, r, i, n, e[s + 0], 11, -358537222), 82 | n = p(n, o, r, i, e[s + 3], 16, -722521979), 83 | i = p(i, n, o, r, e[s + 6], 23, 76029189), 84 | r = p(r, i, n, o, e[s + 9], 4, -640364487), 85 | o = p(o, r, i, n, e[s + 12], 11, -421815835), 86 | n = p(n, o, r, i, e[s + 15], 16, 530742520), 87 | i = p(i, n, o, r, e[s + 2], 23, -995338651), 88 | r = f(r, i, n, o, e[s + 0], 6, -198630844), 89 | o = f(o, r, i, n, e[s + 7], 10, 1126891415), 90 | n = f(n, o, r, i, e[s + 14], 15, -1416354905), 91 | i = f(i, n, o, r, e[s + 5], 21, -57434055), 92 | r = f(r, i, n, o, e[s + 12], 6, 1700485571), 93 | o = f(o, r, i, n, e[s + 3], 10, -1894986606), 94 | n = f(n, o, r, i, e[s + 10], 15, -1051523), 95 | i = f(i, n, o, r, e[s + 1], 21, -2054922799), 96 | r = f(r, i, n, o, e[s + 8], 6, 1873313359), 97 | o = f(o, r, i, n, e[s + 15], 10, -30611744), 98 | n = f(n, o, r, i, e[s + 6], 15, -1560198380), 99 | i = f(i, n, o, r, e[s + 13], 21, 1309151649), 100 | r = f(r, i, n, o, e[s + 4], 6, -145523070), 101 | o = f(o, r, i, n, e[s + 11], 10, -1120210379), 102 | n = f(n, o, r, i, e[s + 2], 15, 718787259), 103 | i = f(i, n, o, r, e[s + 9], 21, -343485551), 104 | r = m(r, a), 105 | i = m(i, g), 106 | n = m(n, c), 107 | o = m(o, d) 108 | } 109 | return Array(r, i, n, o) 110 | }(function (e) { 111 | for (var t = Array(), r = (1 << o) - 1, i = 0; i < e.length * o; i += o) 112 | t[i >> 5] |= (e.charCodeAt(i / o) & r) << i % 32; 113 | return t 114 | }(t = e), t.length * o)) : "" 115 | }; 116 | 117 | function test() { 118 | (a = new Date).setTime(a.getTime() + 1e3 * 864000000); 119 | var g = ";expires=" + a.toGMTString(); 120 | var i = "; path=/"; 121 | var n = "; domain=kugou.com"; 122 | var o = "; secure"; 123 | var s = "; sameSite=None"; 124 | var tt = md5(guid()); 125 | var t = encodeURIComponent(tt); 126 | var e = "kg_mid"; 127 | var cookie = [e, "=", t, g, i, n, o, s].join(""); 128 | console.log("cookie:", cookie); 129 | console.log("kg_mid:", tt); 130 | return tt; 131 | }; 132 | 133 | module.exports = { 134 | mid: () => { 135 | return md5(guid()); 136 | } 137 | } -------------------------------------------------------------------------------- /util/kugou_request.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const { APIError } = require('../middlewares/rest'); 3 | const __Cookie = require('./cookie_util'); 4 | 5 | 6 | 7 | 8 | userAgent = [ 9 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50', 10 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11', 11 | 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)', 12 | 'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11', 13 | 'MQQBrowser/26 Mozilla/5.0 (Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22; CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1', 14 | 'Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13', 15 | 'NOKIA5700/ UCWEB7.0.2.37/28/999', 16 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;', 17 | 'MQQBrowser/26 Mozilla/5.0 (Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22; CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1', 18 | 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; TencentTraveler 4.0; .NET CLR 2.0.50727)' 19 | ] 20 | 21 | 22 | 23 | let kugou_request = async (url, _params, flag = 1) => { 24 | let result = {}; 25 | try { 26 | if (_params !== undefined) { 27 | result = await axios.get(url, { 28 | params: _params, 29 | headers: flag ? { 30 | // Cookie: 'Hm_lvt_cdb524f42f0ce19b169a8071123a4797=1609307382; _ga=GA1.2.93241344.1609307382; _gid=GA1.2.1348073384.1609307382; Hm_lpvt_cdb524f42f0ce19b169a8071123a4797=1609420898; _gat=1; kw_token=95MWTYC4FP', 31 | 'User-Agent': userAgent[parseInt(Math.random() * 10)], 32 | Referer: 'https://www.kugou.com/', 33 | Cookie: __Cookie.serialization(global.kugou_cookie), 34 | //Host: 'wwwapi.kugou.com' 35 | } : { 36 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0', 37 | Referer: 'https://www.kugou.com/', 38 | } 39 | }); 40 | } else { 41 | result = await axios.get(url, { 42 | headers: { 43 | // Cookie: 'Hm_lvt_cdb524f42f0ce19b169a8071123a4797=1609307382; _ga=GA1.2.93241344.1609307382; _gid=GA1.2.1348073384.1609307382; Hm_lpvt_cdb524f42f0ce19b169a8071123a4797=1609420898; _gat=1; kw_token=95MWTYC4FP', 44 | Cookie: __Cookie.serialization(global.kugou_cookie), 45 | 'User-Agent': userAgent[parseInt(Math.random() * 10)], 46 | Referer: 'https://www.kugou.com/', 47 | dfid: '1ssiv93oVqMp27cirf2CvoF1', 48 | mid: '156798703528610303473757548878786007104', 49 | clienttime: 1584257267, 50 | 'x-router': 'msearch.kugou.com', 51 | 'user-agent': 'Android9-AndroidPhone-10020-130-0-searchrecommendprotocol-wifi', 52 | 'kg-rc': 1 53 | } 54 | }); 55 | } 56 | } catch (error) { 57 | throw new APIError('Request:Request_error', 'Request is error, please recover'); 58 | } 59 | 60 | return result; 61 | } 62 | 63 | module.exports = { 64 | kugou_request 65 | } -------------------------------------------------------------------------------- /util/kuwo_request.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const { APIError } = require('../middlewares/rest'); 3 | 4 | /** 5 | * @author ZC 6 | * @param {string} url 7 | * @param {*} _params 8 | * @param {number} platform 0: 移动端 1: web 9 | * @returns 10 | */ 11 | let kuwo_request = async (url, _params, platform = 1) => { 12 | let result = {}; 13 | try { 14 | if(platform) { 15 | if (_params !== undefined && _params !== null) { 16 | result = await axios.get(url, { 17 | params: _params, 18 | headers: { 19 | Cookie: 'kw_token=8PAW508TG7P; Hm_lvt_cdb524f42f0ce19b169a8071123a4797=1657874945; Hm_lpvt_cdb524f42f0ce19b169a8071123a4797=1657874945; _ga=GA1.2.259677078.1657874945; _gid=GA1.2.2034977564.1657874945; _gat=1', 20 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0', 21 | csrf: '8PAW508TG7P', 22 | Referer: 'https://www.kuwo.cn/' 23 | //Host: 'www.kuwo.cn' 24 | } 25 | }); 26 | // console.log(JSON.stringify(result.data)); 27 | } else { 28 | result = await axios.get(url, { 29 | headers: { 30 | Cookie: 'kw_token=8PAW508TG7P; Hm_lvt_cdb524f42f0ce19b169a8071123a4797=1657874945; Hm_lpvt_cdb524f42f0ce19b169a8071123a4797=1657874945; _ga=GA1.2.259677078.1657874945; _gid=GA1.2.2034977564.1657874945; _gat=1', 31 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0', 32 | csrf: '8PAW508TG7P', 33 | Referer: 'https://www.kuwo.cn/' 34 | //Host: 'www.kuwo.cn' 35 | } 36 | }); 37 | } 38 | } else { 39 | if (_params !== undefined && _params !== null) { 40 | result = await axios.get(url, { 41 | params: _params, 42 | headers: { 43 | Cookie: 'Hm_lvt_cdb524f42f0ce19b169a8071123a4797=1657876915; Hm_lpvt_cdb524f42f0ce19b169a8071123a4797=1657876931; BAIDU_RANDOM=pFKSJDzBnYHtp7YhcR6FYrTymh3TctAr', 44 | 'User-Agent': 'Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36', 45 | Token: '2A6CCCFDBE1879BED52B3EFB1D21330A', 46 | Referer: 'http://m.kuwo.cn/newh5app/' 47 | //Host: 'm.kuwo.cn' 48 | } 49 | }); 50 | // console.log(JSON.stringify(result.data)); 51 | } else { 52 | result = await axios.get(url, { 53 | headers: { 54 | Cookie: 'Hm_lvt_cdb524f42f0ce19b169a8071123a4797=1657876915; Hm_lpvt_cdb524f42f0ce19b169a8071123a4797=1657876931; BAIDU_RANDOM=pFKSJDzBnYHtp7YhcR6FYrTymh3TctAr', 55 | 'User-Agent': 'Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36', 56 | Token: '2A6CCCFDBE1879BED52B3EFB1D21330A', 57 | Referer: 'http://m.kuwo.cn/newh5app/' 58 | //Host: 'm.kuwo.cn' 59 | } 60 | }); 61 | } 62 | } 63 | } catch (error) { 64 | // console.warn(error); 65 | throw new APIError('Request:Request_error', 'Request is error, please recover'); 66 | } 67 | 68 | return result; 69 | } 70 | 71 | module.exports = { 72 | kuwo_request 73 | } -------------------------------------------------------------------------------- /util/migu_request.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const APIError = require('../middlewares/rest').APIError; 3 | const __Cookie = require('./cookie_util'); 4 | 5 | let migu_request = async (url, _params, flag = 1) => { 6 | //console.log(url) 7 | let result = {}; 8 | try { 9 | if (_params !== undefined) { 10 | result = await axios.get(url, { 11 | params: _params, 12 | headers: { 13 | Referer: flag ? 'https://music.migu.cn/' : 'http://m.music.migu.cn', 14 | //Host: 'm.music.migu.cn', 15 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0', 16 | Cookie: __Cookie.serialization(global.migu_cookie) 17 | } 18 | }); 19 | } else { 20 | result = await axios.get(url, { 21 | headers: { 22 | Referer: flag ? 'https://music.migu.cn/' : 'http://m.music.migu.cn', 23 | //Host: 'm.music.migu.cn', 24 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0', 25 | Cookie: __Cookie.serialization(global.migu_cookie) 26 | } 27 | }); 28 | } 29 | } catch (error) { 30 | // console.log(error); 31 | throw new APIError('Request:Request_error', 'Request is error, please recover'); 32 | } 33 | 34 | return result; 35 | } 36 | 37 | module.exports = { 38 | migu_request 39 | } -------------------------------------------------------------------------------- /util/qq_request.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const { APIError } = require('../middlewares/rest'); 3 | const __Cookie = require('./cookie_util'); 4 | 5 | 6 | let qq_request = async (url, _params) => { 7 | let result = {}; 8 | // console.log(__Cookie.serialization(global.qq_cookie), '!!!'); 9 | try { 10 | if (_params !== undefined) { 11 | result = await axios.get(url, { 12 | params: _params, 13 | headers: { 14 | // Cookie: 'Hm_lvt_cdb524f42f0ce19b169a8071123a4797=1609307382; _ga=GA1.2.93241344.1609307382; _gid=GA1.2.1348073384.1609307382; Hm_lpvt_cdb524f42f0ce19b169a8071123a4797=1609420898; _gat=1; kw_token=95MWTYC4FP', 15 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0', 16 | Referer: 'https://y.qq.com/', 17 | Host: 'u.y.qq.com', 18 | TE: 'trailers', 19 | // Origin: 'https://y.qq.com/', 20 | Cookie: __Cookie.serialization(global.qq_cookie) 21 | } 22 | }); 23 | } else { 24 | result = await axios.get(url, { 25 | headers: { 26 | // Cookie: 'Hm_lvt_cdb524f42f0ce19b169a8071123a4797=1609307382; _ga=GA1.2.93241344.1609307382; _gid=GA1.2.1348073384.1609307382; Hm_lpvt_cdb524f42f0ce19b169a8071123a4797=1609420898; _gat=1; kw_token=95MWTYC4FP', 27 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0', 28 | Referer: 'https://y.qq.com/', 29 | Host: 'u.y.qq.com', 30 | TE: 'trailers', 31 | // Origin: 'https://y.qq.com/', 32 | Cookie: __Cookie.serialization(global.qq_cookie) 33 | } 34 | }); 35 | } 36 | } catch (error) { 37 | // console.log(error); 38 | throw new APIError('Request:Request_error', 'Request is error, please recover'); 39 | } 40 | // console.log(__Cookie.serialization(global.qq_cookie)); 41 | return result; 42 | } 43 | 44 | module.exports = { 45 | qq_request 46 | } --------------------------------------------------------------------------------