├── .autod.conf.js ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── .travis.yml ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── app ├── controller │ ├── BaseController.ts │ ├── albumInfo.ts │ ├── banner.ts │ ├── comment.ts │ ├── getTagList.ts │ ├── lrc.ts │ ├── musicInfo.ts │ ├── musicList.ts │ ├── mv.ts │ ├── playList.ts │ ├── playUrl.ts │ ├── radio.ts │ ├── rank.ts │ ├── recGedan.ts │ ├── recSinger.ts │ ├── search.ts │ └── singer.ts ├── public │ └── index.html ├── router.ts ├── schedule │ └── logs.ts ├── service │ ├── BaseService.ts │ ├── albumInfo.ts │ ├── banner.ts │ ├── comment.ts │ ├── getTagList.ts │ ├── lrc.ts │ ├── musicInfo.ts │ ├── musicList.ts │ ├── mv.ts │ ├── playList.ts │ ├── playUrl.ts │ ├── radio.ts │ ├── rank.ts │ ├── recGedan.ts │ ├── recSinger.ts │ ├── search.ts │ └── singer.ts └── utils │ └── secret.js ├── appveyor.yml ├── config ├── config.default.ts ├── config.local.ts ├── config.prod.ts └── plugin.ts ├── docs ├── .nojekyll ├── README.md ├── _coverpage.md ├── favicon.ico └── index.html ├── package-lock.json ├── package.json ├── tsconfig.json └── typings ├── app ├── controller │ └── index.d.ts ├── index.d.ts └── service │ └── index.d.ts ├── config ├── index.d.ts └── plugin.d.ts └── index.d.ts /.autod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | write: true, 5 | plugin: 'autod-egg', 6 | prefix: '^', 7 | devprefix: '^', 8 | exclude: [ 9 | 'test/fixtures', 10 | 'coverage', 11 | ], 12 | dep: [ 13 | 'egg', 14 | 'egg-scripts', 15 | ], 16 | devdep: [ 17 | 'autod', 18 | 'autod-egg', 19 | 'egg-bin', 20 | 'tslib', 21 | 'typescript', 22 | ], 23 | keep: [ 24 | ], 25 | semver: [ 26 | ], 27 | test: 'scripts', 28 | }; 29 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.d.ts 2 | node_modules/ 3 | test/ 4 | typings/ 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint-config-egg/typescript","eslint-config-standard","eslint:recommended","plugin:jsdoc/recommended"], 3 | "plugins": ["jsdoc"], 4 | "rules": { 5 | "comma-dangle":["error","always-multiline"], 6 | "operator-linebreak":["error","before"], 7 | "space-before-function-paren":[ 8 | "error", 9 | { 10 | "anonymous":"always", 11 | "named":"always", 12 | "asyncArrow":"always" 13 | } 14 | ], 15 | "linebreak-style":0, 16 | "no-var-requires":0, 17 | "no-return-assign":"off", 18 | "default-case":"off", 19 | "no-useless-constructor":"off", 20 | "no-unused-vars":0, 21 | "jsdoc/require-param-description":"off", 22 | "jsdoc/check-tag-names": 0, 23 | "jsdoc/no-undefined-types":0, 24 | "jsdoc/valid-types":0, 25 | "jsdoc/tag-lines":0, 26 | "jsdoc/require-returns":0, 27 | "jsdoc/check-param-names":0 28 | }, 29 | "overrides":[ 30 | { 31 | "files":["*.ts"], 32 | "rules":{ 33 | "@typescript-eslint/no-unused-vars":0, 34 | "@typescript-eslint/semi":0, 35 | "@typescript-eslint/no-var-requires":0, 36 | "@typescript-eslint/no-useless-constructor":0 37 | } 38 | } 39 | ], 40 | "globals": { 41 | "_":"readonly", 42 | "app":true 43 | }, 44 | "env": { 45 | "node": true 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | schedule: 12 | - cron: '0 2 * * *' 13 | 14 | jobs: 15 | build: 16 | runs-on: ${{ matrix.os }} 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | node-version: [8] 22 | os: [ubuntu-latest, windows-latest, macos-latest] 23 | 24 | steps: 25 | - name: Checkout Git Source 26 | uses: actions/checkout@v2 27 | 28 | - name: Use Node.js ${{ matrix.node-version }} 29 | uses: actions/setup-node@v1 30 | with: 31 | node-version: ${{ matrix.node-version }} 32 | 33 | - name: Install Dependencies 34 | run: npm i -g npminstall && npminstall 35 | 36 | - name: Continuous Integration 37 | run: npm run ci 38 | 39 | - name: Code Coverage 40 | uses: codecov/codecov-action@v1 41 | with: 42 | token: ${{ secrets.CODECOV_TOKEN }} 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | npm-debug.log 3 | node_modules/ 4 | coverage/ 5 | .idea/ 6 | run/ 7 | logs/ 8 | .DS_Store 9 | *.swp 10 | *.lock 11 | *.js 12 | !.autod.conf.js 13 | 14 | app/**/*.js 15 | test/**/*.js 16 | config/**/*.js 17 | app/**/*.map 18 | test/**/*.map 19 | config/**/*.map 20 | app/**/*.d.ts 21 | config/**/*.d.ts -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: node_js 3 | node_js: 4 | - '8' 5 | before_install: 6 | - npm i npminstall -g 7 | install: 8 | - npminstall 9 | script: 10 | - npm run ci 11 | after_script: 12 | - npminstall codecov && codecov 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll": true 4 | }, 5 | "eslint.validate": ["javascript", "javascriptreact","typescript","typescriptreact"], 6 | 7 | "files.exclude": { 8 | "**/.git": true, 9 | "**/.svn": true, 10 | "**/.hg": true, 11 | "**/CVS": true, 12 | "**/.DS_Store": true, 13 | "**/node_modules":true, 14 | "**/.github":true 15 | }, 16 | "files.watcherExclude": { 17 | "**/.git": true, 18 | "**/.svn": true, 19 | "**/.hg": true, 20 | "**/CVS": true, 21 | "**/.DS_Store": true, 22 | "**/node_modules":true, 23 | "**/.github":true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2021] [name of copyright qyhqiu] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 酷我音樂API 2 | 3 | ### Development 4 | 5 | ## 安装 6 | 7 | ```bash 8 | $ git clone https://github.com/QiuYaohong/kuwoMusicApi.git 9 | $ npm i 10 | $ npm run dev 11 | ``` 12 | 13 | ## 环境要求 14 | 15 | - Node.js 8.x 16 | - Typescript 2.8+ 17 | 18 | ## 使用文档 19 | 20 | - 项目启动后 默认本地服务为 http://127.0.0.1:7002 21 | - 接口完整地址 为 本地服务地址 + 接口地址 22 | ![](https://user-images.githubusercontent.com/51219225/140633174-8e25f1dd-7581-480c-af3b-99f8144c20e9.png) 23 | - 例如歌曲播放链接: http://127.0.0.1:7002/kuwo/url?mid=162457325&type=music 24 | 25 | [接口文档地址](https://qiuyaohong.github.io/kuwoMusicApi/) 26 | 27 | ## 部署 28 | 29 | ```shell 30 | 31 | 1. npm run start # 启动服务、可用于部署服务器 32 | 33 | 2. npm run stop # 停止服务 34 | 35 | 3. npm run dev # 本地运行服务 36 | 37 | ``` -------------------------------------------------------------------------------- /app/controller/BaseController.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from 'egg' 2 | class BaseController extends Controller { 3 | constructor (ctx) { 4 | super(ctx) 5 | } 6 | } 7 | 8 | module.exports = BaseController 9 | -------------------------------------------------------------------------------- /app/controller/albumInfo.ts: -------------------------------------------------------------------------------- 1 | const BaseController = require('./BaseController') 2 | 3 | export default class AlbumInfo extends BaseController { 4 | async index () { 5 | const { ctx, service } = this 6 | const { albumId, pn = 1, rn = 30 } = ctx.query 7 | 8 | if (!albumId) { 9 | ctx.body = { 10 | code: 500, 11 | message: '参数错误', 12 | result: null, 13 | success: false, 14 | } 15 | return false 16 | } 17 | const res = await service.albumInfo.getList({ albumId, pn, rn }) 18 | ctx.body = res 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/controller/banner.ts: -------------------------------------------------------------------------------- 1 | const BaseController = require('./BaseController') 2 | 3 | export default class Banner extends BaseController { 4 | async index () { 5 | const { ctx, service } = this 6 | const res = await service.banner.List() 7 | ctx.body = res 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /app/controller/comment.ts: -------------------------------------------------------------------------------- 1 | const BaseController = require('./BaseController') 2 | 3 | export default class Momment extends BaseController { 4 | /** 5 | * @param {string} type 评论类型 [热门评论 get_rec_comment , 最新评论 get_comment] 6 | * @param {number} digest 15 歌曲 2 排行榜 8 歌单评论 7 mv评论 7 | */ 8 | async index () { 9 | const { ctx, service } = this 10 | 11 | const { sid, page = 1, rows = 30, uid = 0, type = 'get_rec_comment', digest = 15 } = ctx.query 12 | 13 | if (!sid) { 14 | ctx.body = { 15 | code: 500, 16 | message: '参数错误', 17 | result: null, 18 | success: false, 19 | } 20 | return false 21 | } 22 | 23 | const res = await service.comment.List({ sid, page, rows, uid, type, digest }) 24 | ctx.body = res 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/controller/getTagList.ts: -------------------------------------------------------------------------------- 1 | const BaseController = require('./BaseController') 2 | 3 | export default class TagList extends BaseController { 4 | async index () { 5 | const { ctx, service } = this 6 | 7 | const res = await service.getTagList.index() 8 | 9 | ctx.body = res 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/controller/lrc.ts: -------------------------------------------------------------------------------- 1 | const BaseController = require('./BaseController') 2 | 3 | export default class Lrc extends BaseController { 4 | async index () { 5 | const { ctx, service } = this 6 | const { musicId } = ctx.query 7 | 8 | if (!musicId) { 9 | ctx.body = { 10 | code: 500, 11 | message: '参数错误', 12 | result: null, 13 | success: false, 14 | } 15 | return false 16 | } 17 | 18 | const res = await service.lrc.LrcRes(musicId) 19 | 20 | ctx.body = res 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/controller/musicInfo.ts: -------------------------------------------------------------------------------- 1 | const BaseController = require('./BaseController') 2 | 3 | export default class MusicInfo extends BaseController { 4 | async index () { 5 | const { ctx, service } = this 6 | const { mid } = ctx.query 7 | 8 | if (!mid) { 9 | ctx.body = { 10 | code: 500, 11 | message: '参数错误', 12 | result: null, 13 | success: false, 14 | } 15 | return false 16 | } 17 | 18 | const res = await service.musicInfo.getList(mid) 19 | 20 | ctx.body = res 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/controller/musicList.ts: -------------------------------------------------------------------------------- 1 | const BaseController = require('./BaseController') 2 | 3 | export default class MusicList extends BaseController { 4 | async index () { 5 | const { ctx, service } = this 6 | const { pid, rn = 30, pn = 1 } = ctx.query 7 | if (!pid) { 8 | ctx.body = { 9 | code: 500, 10 | message: '参数错误', 11 | result: null, 12 | success: false, 13 | } 14 | return false 15 | } 16 | const res = await service.musicList.getList({ pid, rn, pn }) 17 | 18 | ctx.body = res 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/controller/mv.ts: -------------------------------------------------------------------------------- 1 | const BaseController = require('./BaseController') 2 | 3 | export default class Mv extends BaseController { 4 | async getMvUrl () { 5 | const { ctx, service } = this 6 | 7 | const { rid } = ctx.query 8 | 9 | if (!rid) { 10 | ctx.body = { 11 | code: 500, 12 | message: '参数错误', 13 | result: null, 14 | success: false, 15 | } 16 | return 17 | } 18 | const res = await service.mv.getMvUrl(rid) 19 | ctx.body = res 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/controller/playList.ts: -------------------------------------------------------------------------------- 1 | const BaseController = require('./BaseController') 2 | 3 | export default class PlayList extends BaseController { 4 | async index () { 5 | const { ctx, service } = this 6 | // 默认 new = 最新 hot = 最热 7 | const { order = 'new', rn = 30, pn = 1 } = ctx.query 8 | 9 | const res = await service.playList.index({ order, rn, pn }) 10 | ctx.body = res 11 | } 12 | 13 | // 歌单分类 14 | async getTagPlayList () { 15 | const { ctx, service } = this 16 | const { id, rn = 30, pn = 1 } = ctx.query 17 | if (!id) { 18 | ctx.body = { 19 | code: 500, 20 | message: '参数错误', 21 | result: null, 22 | success: false, 23 | } 24 | return false 25 | } 26 | const res = await service.playList.getTagPlayList({ id, rn, pn }) 27 | 28 | ctx.body = res 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/controller/playUrl.ts: -------------------------------------------------------------------------------- 1 | const BaseController = require('./BaseController') 2 | 3 | export default class PlayUrl extends BaseController { 4 | async index () { 5 | const { ctx, service } = this 6 | const { mid, type, br } = ctx.query 7 | 8 | if (!mid) { 9 | ctx.body = { 10 | code: 500, 11 | message: '参数错误', 12 | result: null, 13 | success: false, 14 | } 15 | return false 16 | } 17 | 18 | const res = await service.playUrl.getPlayUrl(mid, type, br) 19 | 20 | ctx.body = res 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/controller/radio.ts: -------------------------------------------------------------------------------- 1 | const BaseController = require('./BaseController') 2 | 3 | export default class Radio extends BaseController { 4 | async index () { 5 | const { ctx, service } = this 6 | 7 | const res = await service.radio.getRadio() 8 | 9 | ctx.body = res 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/controller/rank.ts: -------------------------------------------------------------------------------- 1 | const BaseController = require('./BaseController') 2 | 3 | export default class Rank extends BaseController { 4 | async index () { 5 | const { ctx, service } = this 6 | const res = await service.rank.index() 7 | ctx.body = res 8 | } 9 | 10 | // 排行榜音乐 11 | async rankMusicList () { 12 | const { ctx, service } = this 13 | const { bangId = 93, pn = 1, rn = 30 } = ctx.query 14 | const res = await service.rank.getRankMusicList({ bangId, pn, rn }) 15 | ctx.body = res 16 | } 17 | 18 | // 推荐榜单 19 | 20 | async rankRecBangList () { 21 | const { ctx, service } = this 22 | const res = await service.rank.getRecBangList() 23 | ctx.body = res 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/controller/recGedan.ts: -------------------------------------------------------------------------------- 1 | const BaseController = require('./BaseController') 2 | 3 | export default class RecGedan extends BaseController { 4 | async index () { 5 | const { ctx, service } = this 6 | const { id = 'rec', pn = 5, rn = 1 } = ctx.query 7 | const res = await service.recGedan.index({ id, pn, rn }) 8 | ctx.body = res 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/controller/recSinger.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsdoc/check-param-names */ 2 | const BaseController = require('./BaseController') 3 | 4 | export default class RecSinger extends BaseController { 5 | /** 6 | * @param {number} category // 11 華語 13 歐美 12 日韓 16 組合 7 | * @param {number} pn 分页 8 | * @param {number} rn 每页数据 9 | */ 10 | async index () { 11 | const { ctx, service } = this 12 | const { category = 1, pn = 5, rn = 1 } = ctx.query 13 | const res = await service.recSinger.index({ category, pn, rn }) 14 | ctx.body = res 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/controller/search.ts: -------------------------------------------------------------------------------- 1 | const BaseController = require('./BaseController') 2 | 3 | export default class Search extends BaseController { 4 | // 关键字搜索 5 | async searchKey () { 6 | const { ctx, service } = this 7 | const { key } = ctx.query 8 | const res = await service.search.searchKey(key) 9 | ctx.body = res 10 | } 11 | 12 | // 单曲搜索 13 | async searchMusicBykeyWord () { 14 | const { ctx, service } = this 15 | const { key, pn = 1, rn = 30 } = ctx.query 16 | if (!key) { 17 | ctx.body = { 18 | code: 500, 19 | message: '参数错误', 20 | result: null, 21 | success: false, 22 | } 23 | return 24 | } 25 | const res = await service.search.searchMusicBykeyWord({ key, pn, rn }) 26 | ctx.body = res 27 | } 28 | 29 | // 专辑搜索 30 | async searchAlbumBykeyWord () { 31 | const { ctx, service } = this 32 | const { key, pn = 1, rn = 30 } = ctx.query 33 | if (!key) { 34 | ctx.body = { 35 | code: 500, 36 | message: '参数错误', 37 | result: null, 38 | success: false, 39 | } 40 | return 41 | } 42 | const res = await service.search.searchAlbumBykeyWord({ key, pn, rn }) 43 | ctx.body = res 44 | } 45 | 46 | // mv 搜索 47 | async searchMvBykeyWord () { 48 | const { ctx, service } = this 49 | const { key, pn = 1, rn = 30 } = ctx.query 50 | if (!key) { 51 | ctx.body = { 52 | code: 500, 53 | message: '参数错误', 54 | result: null, 55 | success: false, 56 | } 57 | return 58 | } 59 | const res = await service.search.searchMvBykeyWord({ key, pn, rn }) 60 | ctx.body = res 61 | } 62 | 63 | // 歌单搜索 64 | async searchPlayListBykeyWord () { 65 | const { ctx, service } = this 66 | const { key, pn = 1, rn = 30 } = ctx.query 67 | if (!key) { 68 | ctx.body = { 69 | code: 500, 70 | message: '参数错误', 71 | result: null, 72 | success: false, 73 | } 74 | return 75 | } 76 | const res = await service.search.searchPlayListBykeyWord({ key, pn, rn }) 77 | ctx.body = res 78 | } 79 | 80 | // 歌手搜索 81 | async searchArtistBykeyWord () { 82 | const { ctx, service } = this 83 | const { key, pn = 1, rn = 30 } = ctx.query 84 | if (!key) { 85 | ctx.body = { 86 | code: 500, 87 | message: '参数错误', 88 | result: null, 89 | success: false, 90 | } 91 | return 92 | } 93 | const res = await service.search.searchArtistBykeyWord({ key, pn, rn }) 94 | ctx.body = res 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/controller/singer.ts: -------------------------------------------------------------------------------- 1 | const BaseController = require('./BaseController') 2 | 3 | export default class Singer extends BaseController { 4 | /** 5 | * @param {number} category 分类 0 = 全部 1 = 华语男 2 = 华语女 3 = 华语组合 4 = 日韩男 5 = 日韩女 6 = 日韩组合 7 = 欧美男 8 = 欧美女 9 = 欧美组合 10 = 其他 6 | * @param {number} rn 每页数据 7 | * @param {number} pn 分页 8 | * @param {string} prefix A~Z 分类 9 | */ 10 | async getArtistInfo () { 11 | const { ctx, service } = this 12 | const { 13 | category = 0, 14 | rn = 50, 15 | pn = 1, 16 | prefix, 17 | } = ctx.query 18 | 19 | const res = await service.singer.getArtistInfo({ category, rn, pn, prefix }) 20 | 21 | ctx.body = res 22 | } 23 | 24 | // 歌手单曲 25 | async getArtistMusic () { 26 | const { ctx, service } = this 27 | const { 28 | artistid, 29 | rn = 30, 30 | pn = 1, 31 | } = ctx.query 32 | 33 | if (!artistid) { 34 | ctx.body = { 35 | code: 500, 36 | message: '参数错误', 37 | result: null, 38 | success: false, 39 | } 40 | return 41 | } 42 | const res = await service.singer.getArtistMusic({ artistid, rn, pn }) 43 | 44 | ctx.body = res 45 | } 46 | 47 | // 获取歌手专辑 48 | async getArtistAlbum () { 49 | const { ctx, service } = this 50 | const { 51 | artistid, 52 | rn = 30, 53 | pn = 1, 54 | } = ctx.query 55 | 56 | if (!artistid) { 57 | ctx.body = { 58 | code: 500, 59 | message: '参数错误', 60 | result: null, 61 | success: false, 62 | } 63 | return 64 | } 65 | 66 | const res = await service.singer.getArtistAlbum({ artistid, rn, pn }) 67 | ctx.body = res 68 | } 69 | 70 | // 获取歌手mv 71 | 72 | async getArtistMv () { 73 | const { ctx, service } = this 74 | const { 75 | artistid, 76 | rn = 30, 77 | pn = 1, 78 | } = ctx.query 79 | 80 | if (!artistid) { 81 | ctx.body = { 82 | code: 500, 83 | message: '参数错误', 84 | result: null, 85 | success: false, 86 | } 87 | return 88 | } 89 | const res = await service.singer.getArtistMv({ artistid, rn, pn }) 90 | ctx.body = res 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 酷我音乐 API 9 | 10 | 11 | 12 |

酷我音乐 API

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

例子:

16 | 21 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/router.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg' 2 | 3 | export default (app: Application) => { 4 | const { controller, router } = app 5 | // 轮播图 6 | router.get('/kuwo/banner', controller.banner.index) 7 | // 评论 8 | router.get('/kuwo/comment', controller.comment.index) 9 | // 歌词 10 | router.get('/kuwo/lrc', controller.lrc.index) 11 | // 音乐信息 12 | router.get('/kuwo/musicInfo', controller.musicInfo.index) 13 | // 歌单音乐 14 | router.get('/kuwo/musicList', controller.musicList.index) 15 | // 音乐播放地址 16 | router.get('/kuwo/url', controller.playUrl.index) 17 | // 获取 mv 播放地址 18 | router.get('/kuwo/mv_url', controller.mv.getMvUrl) 19 | // 获取电台列表 20 | router.get('/kuwo/radio', controller.radio.index) 21 | // 获取歌手信息 22 | router.get('/kuwo/singer', controller.singer.getArtistInfo) 23 | // 获取歌手单曲 24 | router.get('/kuwo/singer/music', controller.singer.getArtistMusic) 25 | // 获取歌手专辑 26 | router.get('/kuwo/singer/album', controller.singer.getArtistAlbum) 27 | // 获取歌手mv 28 | router.get('/kuwo/singer/mv', controller.singer.getArtistMv) 29 | // 排行榜 30 | router.get('/kuwo/rank', controller.rank.index) 31 | // 排行榜音乐 32 | router.get('/kuwo/rank/musicList', controller.rank.rankMusicList) 33 | // 推荐榜单 34 | router.get('/kuwo/rank/rec_bangList', controller.rank.rankRecBangList) 35 | // 推荐歌单 36 | router.get('/kuwo/rec_gedan', controller.recGedan.index) 37 | // 推荐歌手 38 | router.get('/kuwo/rec_singer', controller.recSinger.index) 39 | // 歌单分类 40 | router.get('/kuwo/getTagList', controller.getTagList.index) 41 | // 默认歌单 42 | router.get('/kuwo/playList', controller.playList.index) 43 | // 专辑歌单 44 | router.get('/kuwo/albumInfo', controller.albumInfo.index) 45 | // 歌单分类 46 | router.get('/kuwo/playList/getTagPlayList', controller.playList.getTagPlayList) 47 | // 关键字搜索 48 | router.get('/kuwo/search/searchKey', controller.search.searchKey) 49 | // 单曲搜索 50 | router.get('/kuwo/search/searchMusicBykeyWord', controller.search.searchMusicBykeyWord) 51 | // 专辑搜索 52 | router.get('/kuwo/search/searchAlbumBykeyWord', controller.search.searchAlbumBykeyWord) 53 | // mv 搜索 54 | router.get('/kuwo/search/searchMvBykeyWord', controller.search.searchMvBykeyWord) 55 | // 歌单搜索 56 | router.get('/kuwo/search/searchPlayListBykeyWord', controller.search.searchPlayListBykeyWord) 57 | // 歌手搜索 58 | router.get('/kuwo/search/searchArtistBykeyWord', controller.search.searchArtistBykeyWord) 59 | } 60 | -------------------------------------------------------------------------------- /app/schedule/logs.ts: -------------------------------------------------------------------------------- 1 | const fse = require('fs-extra') 2 | 3 | // https://www.eggjs.org/zh-CN/basics/schedule#%E5%AE%9A%E6%97%B6%E6%96%B9%E5%BC%8F 4 | module.exports = app => { 5 | return { 6 | schedule: { 7 | // 每 24 h 清除 logs 8 | interval: '86440s', 9 | type: 'all', // 指定所有的 worker 都需要执行 10 | }, 11 | async task () { 12 | fse.emptydirSync(app.config?.logger?.dir, err => { 13 | if (!err) { 14 | return console.log('emptydirSync success') 15 | } 16 | }) 17 | }, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/service/BaseService.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsdoc/require-param-type */ 2 | import { Service } from 'egg' 3 | import { v4 as uuidv4 } from 'uuid' 4 | 5 | import { h, v, f, Cookie } from '../utils/secret' 6 | 7 | class BaseService extends Service { 8 | _headers (opts) { 9 | return { 10 | Cookie, 11 | Secret: h(Object(v)(f), f), 12 | Host: 'www.kuwo.cn', 13 | Referer: 'http://www.kuwo.cn/', 14 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36', 15 | ...opts, 16 | } 17 | } 18 | 19 | timeoutCount = 0 20 | 21 | async commonRequest (url, options) { 22 | const opts = { 23 | method: 'GET', 24 | dataType: 'json', 25 | timeout: 60000, 26 | ...options, 27 | headers: this._headers(options?.headers), 28 | } 29 | // eslint-disable-next-line @typescript-eslint/no-this-alias 30 | const _this = this 31 | return handleGetData(_this, url, opts) 32 | } 33 | } 34 | 35 | module.exports = BaseService 36 | /** 37 | * @param _this 38 | * @param url 39 | * @param opts 40 | */ 41 | export function handleGetData (_this, url, opts) { 42 | const reqId = uuidv4() 43 | return _this.ctx.curl(`${url}&reqId=${reqId}`, opts).then(res => { 44 | _this.logger.info({ 45 | req: Object.assign({}, opts, { ctx: undefined }), 46 | url, 47 | reqId, 48 | status: res.status, 49 | res: JSON.stringify(res.data), 50 | }) 51 | _this.timeoutCount = 0 52 | 53 | return res.data 54 | }).catch(e => { 55 | _this.logger.info({ 56 | req: Object.assign({}, opts, { ctx: undefined }), 57 | url, 58 | reqId, 59 | error: e, 60 | res: null, 61 | }) 62 | // 失败自动重试 63 | if (_this.timeoutCount <= 2) { 64 | _this.timeoutCount++ 65 | return handleGetData(_this, url, opts) 66 | } 67 | 68 | _this.timeoutCount = 0 69 | 70 | throw e 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /app/service/albumInfo.ts: -------------------------------------------------------------------------------- 1 | const BaseService = require('./BaseService') 2 | 3 | export default class AlbumInfo extends BaseService { 4 | async getList ({ albumId, pn, rn }) { 5 | return this.commonRequest(`http://www.kuwo.cn/api/www/album/albumInfo?albumId=${albumId}&pn=${pn}&rn=${rn}&httpsStatus=1`) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/service/banner.ts: -------------------------------------------------------------------------------- 1 | const BaseService = require('./BaseService') 2 | 3 | export default class Banner extends BaseService { 4 | async List () { 5 | return this.commonRequest('http://www.kuwo.cn/api/www/banner/index/bannerList?&httpsStatus=1') 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/service/comment.ts: -------------------------------------------------------------------------------- 1 | const BaseService = require('./BaseService') 2 | 3 | export default class Comment extends BaseService { 4 | async List ({ sid, page, rows, uid, type, digest }) { 5 | const targetUrl = `http://www.kuwo.cn/comment?type=${type}&f=web&page=${page}&rows=${rows}&digest=${digest}&sid=${sid}&uid=${uid}&prod=newWeb&httpsStatus=1` 6 | 7 | let Ref = '' 8 | switch (digest) { 9 | case 15: 10 | return Ref = 'http://www.kuwo.cn/play_detail/' + encodeURIComponent(sid) 11 | case 7: 12 | return Ref = 'http://www.kuwo.cn/mvplay/' + encodeURIComponent(sid) 13 | case 8: 14 | return Ref = 'http://www.kuwo.cn/playlist_detail/' + encodeURIComponent(sid) 15 | case 2: 16 | return Ref = 'http://www.kuwo.cn/rankList' 17 | } 18 | 19 | return this.commonRequest(targetUrl, { 20 | headers: { 21 | Referer: Ref, 22 | }, 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/service/getTagList.ts: -------------------------------------------------------------------------------- 1 | const BaseService = require('./BaseService') 2 | 3 | export default class TagList extends BaseService { 4 | async index () { 5 | return this.commonRequest('http://www.kuwo.cn/api/www/playlist/getTagList?&httpsStatus=1') 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/service/lrc.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable node/prefer-global/process */ 2 | const BaseService = require('./BaseService') 3 | const needle = require('needle') 4 | const process = require('process') 5 | const deflateRaw = require('zlib') 6 | const { inflate } = require('zlib') 7 | const iconv = require('iconv-lite') 8 | 9 | const bufkey = Buffer.from('yeelion') 10 | const bufkeylen = bufkey.length 11 | const buildParams = (id, isGetLyricx) => { 12 | let params = `user=12345,web,web,web&requester=localhost&req=1&rid=MUSIC_${id}` 13 | if (isGetLyricx) params += '&lrcx=1' 14 | const bufstr = Buffer.from(params) 15 | const bufstrlen = bufstr.length 16 | const output = new Uint16Array(bufstrlen) 17 | let i = 0 18 | while (i < bufstrlen) { 19 | let j = 0 20 | while (j < bufkeylen && i < bufstrlen) { 21 | // eslint-disable-next-line no-bitwise 22 | output[i] = bufkey[j] ^ bufstr[i] 23 | i++ 24 | j++ 25 | } 26 | } 27 | return Buffer.from(output).toString('base64') 28 | } 29 | 30 | const cancelHttp = requestObj => { 31 | if (!requestObj) return 32 | if (!requestObj.abort) return 33 | requestObj.abort() 34 | } 35 | 36 | const requestMsg = { 37 | fail: '请求异常,可以多试几次,若还是不行就换一首吧', 38 | unachievable: '哦No...接口无法访问了!', 39 | timeout: '请求超时', 40 | // unachievable: '哦No...接口无法访问了!已帮你切换到临时接口,重试下看能不能播放吧~', 41 | notConnectNetwork: '无法连接到服务器', 42 | cancelRequest: '取消http请求', 43 | } 44 | 45 | const request = (url, options, callback) => { 46 | let data 47 | if (options.body) { 48 | data = options.body 49 | } else if (options.form) { 50 | data = options.form 51 | // data.content_type = 'application/x-www-form-urlencoded' 52 | options.json = false 53 | } else if (options.formData) { 54 | data = options.formData 55 | // data.content_type = 'multipart/form-data' 56 | options.json = false 57 | } 58 | options.response_timeout = options.timeout 59 | 60 | return needle.request(options.method || 'get', url, data, options, (err, resp, body) => { 61 | if (!err) { 62 | body = resp.body = resp.raw.toString() 63 | try { 64 | resp.body = JSON.parse(resp.body) 65 | } catch (_) { } 66 | body = resp.body 67 | } 68 | callback(err, resp, body) 69 | }).request 70 | } 71 | 72 | const defaultHeaders = { 73 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36', 74 | } 75 | 76 | const handleDeflateRaw: any = data => new Promise((resolve, reject) => { 77 | deflateRaw(data, (err, buf) => { 78 | if (err) return reject(err) 79 | resolve(buf) 80 | }) 81 | }) 82 | 83 | const regx = /(?:\d\w)+/g 84 | 85 | const fetchData = async (url, method, { 86 | headers = {}, 87 | format = 'json', 88 | timeout = 15000, 89 | ...options 90 | }, callback) => { 91 | headers = Object.assign({}, headers) 92 | const bHh = '624868746c' 93 | if (headers[bHh]) { 94 | const path = url.replace(/^https?:\/\/[\w.:]+\//, '/') 95 | let s = Buffer.from(bHh, 'hex').toString() 96 | s = s.replace(s.substr(-1), '') 97 | s = Buffer.from(s, 'base64').toString() 98 | const v = process.versions.app.split('-')[0].split('.').map(n => (n.length < 3 ? n.padStart(3, '0') : n)).join('') 99 | const v2 = process.versions.app.split('-')[1] || '' 100 | headers[s] = !s || `${(await handleDeflateRaw(Buffer.from(JSON.stringify(`${path}${v}`.match(regx), null, 1).concat(v)).toString('base64'))).toString('hex')}&${parseInt(v)}${v2}` 101 | delete headers[bHh] 102 | } 103 | return request(url, { 104 | ...options, 105 | method, 106 | headers: Object.assign({}, defaultHeaders, headers), 107 | timeout, 108 | json: format === 'json', 109 | }, (err, resp, body) => { 110 | if (err) return callback(err, null) 111 | callback(null, resp, body) 112 | }) 113 | } 114 | 115 | const buildHttpPromose = (url, options) => { 116 | const obj: any = { 117 | isCancelled: false, 118 | } 119 | obj.promise = new Promise((resolve, reject) => { 120 | obj.cancelFn = reject 121 | // console.log(`\nsend request---${url}`) 122 | fetchData(url, options.method, options, (err, resp) => { 123 | obj.requestObj = null 124 | obj.cancelFn = null 125 | if (err) return reject(err) 126 | resolve(resp) 127 | }).then(ro => { 128 | obj.requestObj = ro 129 | if (obj.isCancelled) obj.cancelHttp() 130 | }) 131 | }) 132 | obj.cancelHttp = () => { 133 | if (!obj.requestObj) return obj.isCancelled = true 134 | cancelHttp(obj.requestObj) 135 | obj.requestObj = null 136 | obj.promise = obj.cancelHttp = null 137 | obj.cancelFn(new Error(requestMsg.cancelRequest)) 138 | obj.cancelFn = null 139 | } 140 | return obj 141 | } 142 | const httpFetch = (url, options = { method: 'get' }) => { 143 | const requestObj = buildHttpPromose(url, options) 144 | requestObj.promise = requestObj.promise.catch(err => { 145 | if (err.message === 'socket hang up') { 146 | return Promise.reject(new Error(requestMsg.unachievable)) 147 | } 148 | switch (err.code) { 149 | case 'ETIMEDOUT': 150 | case 'ESOCKETTIMEDOUT': 151 | return Promise.reject(new Error(requestMsg.timeout)) 152 | case 'ENOTFOUND': 153 | return Promise.reject(new Error(requestMsg.notConnectNetwork)) 154 | default: 155 | return Promise.reject(err) 156 | } 157 | }) 158 | return requestObj 159 | } 160 | const lrcTools: any = { 161 | rxps: { 162 | wordLine: /^(\[\d{1,2}:.*\d{1,4}\])\s*(\S+(?:\s+\S+)*)?\s*/, 163 | tagLine: /\[(ver|ti|ar|al|offset|by|kuwo):\s*(\S+(?:\s+\S+)*)\s*\]/, 164 | wordTimeAll: /<(-?\d+),(-?\d+)(?:,-?\d+)?>/g, 165 | wordTime: /<(-?\d+),(-?\d+)(?:,-?\d+)?>/, 166 | }, 167 | offset: 1, 168 | offset2: 1, 169 | isOK: false, 170 | lines: [], 171 | tags: [], 172 | getWordInfo (str, str2, prevWord) { 173 | const offset = parseInt(str) 174 | const offset2 = parseInt(str2) 175 | const startTime = Math.abs((offset + offset2) / (this.offset * 2)) 176 | const endTime = Math.abs((offset - offset2) / (this.offset2 * 2)) + startTime 177 | if (prevWord) { 178 | if (startTime < prevWord.endTime) { 179 | prevWord.endTime = startTime 180 | if (prevWord.startTime > prevWord.endTime) { 181 | prevWord.startTime = prevWord.endTime 182 | } 183 | prevWord.newTimeStr = '' 184 | } 185 | } 186 | return { 187 | startTime, 188 | endTime, 189 | timeStr: '', 190 | } 191 | }, 192 | parseLine (line) { 193 | if (line.length < 6) return 194 | let result = this.rxps.wordLine.exec(line) 195 | if (result) { 196 | const time = result[1] 197 | let words = result[2] 198 | if (words == null) { 199 | words = '' 200 | } 201 | const wordTimes = words.match(this.rxps.wordTimeAll) 202 | if (!wordTimes) return 203 | // console.log(wordTimes) 204 | let preTimeInfo 205 | for (const timeStr of wordTimes) { 206 | const result = this.rxps.wordTime.exec(timeStr) 207 | const wordInfo = this.getWordInfo(result[1], result[2], preTimeInfo) 208 | words = words.replace(timeStr, wordInfo.timeStr) 209 | if (preTimeInfo?.newTimeStr) words = words.replace(preTimeInfo.timeStr, preTimeInfo.newTimeStr) 210 | preTimeInfo = wordInfo 211 | } 212 | this.lines.push(time + words) 213 | return 214 | } 215 | result = this.rxps.tagLine.exec(line) 216 | if (!result) return 217 | if (result[1] === 'kuwo') { 218 | let content = result[2] 219 | if (content !== null && content.includes('][')) { 220 | content = content.substring(0, content.indexOf('][')) 221 | } 222 | const valueOf = parseInt(content, 8) 223 | this.offset = Math.trunc(valueOf / 10) 224 | this.offset2 = Math.trunc(valueOf % 10) 225 | if (this.offset === 0 || Number.isNaN(this.offset) || this.offset2 === 0 || Number.isNaN(this.offset2)) { 226 | this.isOK = false 227 | } 228 | } else { 229 | this.tags.push(line) 230 | } 231 | }, 232 | parse (lrc) { 233 | // console.log(lrc) 234 | const lines = lrc.split(/\r\n|\r|\n/) 235 | const tools = Object.create(this) 236 | tools.isOK = true 237 | tools.offset = 1 238 | tools.offset2 = 1 239 | tools.lines = [] 240 | tools.tags = [] 241 | for (const line of lines) { 242 | if (!tools.isOK) throw new Error('failed') 243 | tools.parseLine(line) 244 | } 245 | if (!tools.lines.length) return '' 246 | let lrcs = tools.lines.join('\n') 247 | if (tools.tags.length) lrcs = `${tools.tags.join('\n')}\n${lrcs}` 248 | // console.log(lrcs) 249 | return lrcs 250 | }, 251 | } 252 | const isGetLyricx = true 253 | const handleInflate = data => new Promise((resolve, reject) => { 254 | inflate(data, (err, result) => { 255 | if (err) return reject(err) 256 | resolve(result) 257 | }) 258 | }) 259 | const bufKey = Buffer.from('yeelion') 260 | const bufKeyLen = bufKey.length 261 | const decodeLyrics = async (buf, isGetLyricx) => { 262 | if (buf.toString('utf8', 0, 10) !== 'tp=content') return '' 263 | const lrcData: any = await handleInflate(buf.slice(buf.indexOf('\r\n\r\n') + 4)) 264 | if (!isGetLyricx) return iconv.decode(lrcData, 'gb18030') 265 | const bufStr = Buffer.from(lrcData.toString(), 'base64') 266 | const bufStrLen = bufStr.length 267 | const output = new Uint16Array(bufStrLen) 268 | let i = 0 269 | while (i < bufStrLen) { 270 | let j = 0 271 | while (j < bufKeyLen && i < bufStrLen) { 272 | // eslint-disable-next-line no-bitwise 273 | output[i] = bufStr[i] ^ bufKey[j] 274 | i++ 275 | j++ 276 | } 277 | } 278 | return iconv.decode(Buffer.from(output), 'gb18030') 279 | } 280 | const timeExp = /^\[([\d:.]*)\]{1}/g 281 | const sortLrcArr = arr => { 282 | const lrcSet = new Set() 283 | const lrc: any = [] 284 | const lrcT: any = [] 285 | for (const item of arr) { 286 | if (lrcSet.has(item.time)) { 287 | if (lrc.length < 2) continue 288 | const tItem: any = lrc.pop() 289 | tItem.time = lrc[lrc.length - 1].time 290 | lrcT.push(tItem) 291 | lrc.push(item) 292 | } else { 293 | lrc.push(item) 294 | lrcSet.add(item.time) 295 | } 296 | } 297 | return { 298 | lrc, 299 | lrcT, 300 | } 301 | } 302 | const parseLrc = lrc => { 303 | const lines = lrc.split(/\r\n|\r|\n/) 304 | const tags: any = [] 305 | const lrcArr: any = [] 306 | for (let i = 0; i < lines.length; i++) { 307 | const line = lines[i].trim() 308 | const result = timeExp.exec(line) 309 | if (result) { 310 | let text = line.replace(timeExp, '').trim() 311 | let time = RegExp.$1 312 | if (/\.\d\d$/.test(time)) time += '0' 313 | const regexp = /<.*?>/g 314 | text = text.replace(regexp, '').replace(/\[by:.*?\](\n|$)/g, '').replace(/\[kuwo:.*?\](\n|$)/g, '') 315 | const times = time.split(':') 316 | time = (parseFloat(times[0]) * 60 + parseFloat(times[1])).toFixed(2) 317 | lrcArr.push({ 318 | time, 319 | lineLyric: text, 320 | }) 321 | } else if (lrcTools.rxps.tagLine.test(line)) { 322 | tags.push(line) 323 | } 324 | } 325 | const lrcInfo = sortLrcArr(lrcArr) 326 | return lrcInfo 327 | } 328 | 329 | const rendererInvoke = async params => { 330 | const lrc = await decodeLyrics(Buffer.from(params.lrcBase64, 'base64'), isGetLyricx) 331 | return Buffer.from(lrc).toString('base64') 332 | } 333 | const decodeLyric = base64Data => rendererInvoke(base64Data) 334 | 335 | export default class Lrc extends BaseService { 336 | async LrcRes (musicId) { 337 | const url = `http://newlyric.kuwo.cn/newlyric.lrc?${buildParams(musicId, isGetLyricx)}` 338 | 339 | const requestObj = httpFetch(url) 340 | requestObj.promise = requestObj.promise.then(({ statusCode, body, raw }) => { 341 | if (statusCode !== 200) { 342 | console.log(body) 343 | // 兼容 web 端请求 344 | return this.commonRequest(`http://m.kuwo.cn/newh5/singles/songinfoandlrc?musicId=${musicId}&httpsStatus=1`) 345 | // return Promise.reject(new Error(JSON.stringify(body))) 346 | } 347 | return decodeLyric({ lrcBase64: raw.toString('base64'), isGetLyricx }).then(base64Data => { 348 | let lrcInfo 349 | lrcInfo = parseLrc(Buffer.from(base64Data, 'base64').toString()) 350 | try { 351 | lrcInfo = parseLrc(Buffer.from(base64Data, 'base64').toString()) 352 | } catch (err) { 353 | return Promise.reject(new Error('Get lyric failed')) 354 | } 355 | const msg = { 356 | data: { 357 | lrclist: lrcInfo.lrc, 358 | }, 359 | msg: '成功', 360 | status: 200, 361 | } 362 | return msg 363 | }) 364 | }) 365 | const asd = async () => { 366 | return await new Promise(resolve => { 367 | requestObj.promise.then(re => { 368 | resolve(re) 369 | }) 370 | }) 371 | } 372 | 373 | return await asd() 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /app/service/musicInfo.ts: -------------------------------------------------------------------------------- 1 | const BaseService = require('./BaseService') 2 | 3 | export default class MusicInfo extends BaseService { 4 | async getList (mid) { 5 | return this.commonRequest(`http://www.kuwo.cn/api/www/music/musicInfo?mid=${mid}&httpsStatus=1`) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/service/musicList.ts: -------------------------------------------------------------------------------- 1 | const BaseService = require('./BaseService') 2 | 3 | export default class MusicList extends BaseService { 4 | async getList ({ pid, pn, rn }) { 5 | return this.commonRequest(`http://www.kuwo.cn/api/www/playlist/playListInfo?pid=${pid}&pn=${pn}&rn=${rn}&httpsStatus=1`) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/service/mv.ts: -------------------------------------------------------------------------------- 1 | const BaseService = require('./BaseService') 2 | 3 | export default class Mv extends BaseService { 4 | async getMvUrl (rid) { 5 | const res = await this.commonRequest(`http://www.kuwo.cn/url?rid=${rid}&response=url&format=mp4%7Cmkv&type=convert_url&t=${Date.now()}&httpsStatus=1`, { 6 | headers: { 7 | Referer: 'http://www.kuwo.cn/mvs', 8 | }, 9 | dataType: 'text', 10 | }) 11 | return { 12 | code: 200, 13 | msg: 'success', 14 | url: res, 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/service/playList.ts: -------------------------------------------------------------------------------- 1 | const BaseService = require('./BaseService') 2 | 3 | export default class PlayList extends BaseService { 4 | async index ({ order, rn, pn }) { 5 | return await this.commonRequest(`http://www.kuwo.cn/api/www/classify/playlist/getRcmPlayList?pn=${pn}&rn=${rn}&order${order}&httpsStatus=1`) 6 | } 7 | 8 | // 歌单分类 9 | async getTagPlayList ({ id, rn, pn }) { 10 | return await this.commonRequest(`http://www.kuwo.cn/api/www/classify/playlist/getTagPlayList?pn=${pn}&rn=${rn}&id=${id}&httpsStatus=1`, { 11 | headers: { 12 | Referer: 'http://www.kuwo.cn/playlists', 13 | }, 14 | }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/service/playUrl.ts: -------------------------------------------------------------------------------- 1 | const BaseService = require('./BaseService') 2 | 3 | export default class PlayUrl extends BaseService { 4 | async getPlayUrl (mid, type = 'music', br = '128kmp3') { 5 | return this.commonRequest(`http://www.kuwo.cn/api/v1/www/music/playUrl?mid=${mid}&type=${type}&httpsStatus=1&plat=web_www&from=&br=${br}`) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/service/radio.ts: -------------------------------------------------------------------------------- 1 | const BaseService = require('./BaseService') 2 | 3 | export default class Radio extends BaseService { 4 | async getRadio () { 5 | return this.commonRequest('http://www.kuwo.cn/api/www/radio/index/radioList?&httpsStatus=1', { 6 | headers: { 7 | Referer: 'http://www.kuwo.cn/rankList', 8 | }, 9 | }) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/service/rank.ts: -------------------------------------------------------------------------------- 1 | const BaseService = require('./BaseService') 2 | 3 | export default class Rank extends BaseService { 4 | // 榜单 5 | async index () { 6 | return this.commonRequest('http://www.kuwo.cn/api/www/bang/bang/bangMenu?&httpsStatus=1') 7 | } 8 | 9 | // 排行榜音乐 10 | async getRankMusicList ({ bangId, pn, rn }) { 11 | return this.commonRequest(`http://www.kuwo.cn/api/www/bang/bang/musicList?bangId=${bangId}&pn=${pn}&rn=${rn}&httpsStatus=1`, { 12 | headers: { 13 | Referer: 'http://www.kuwo.cn/rankList', 14 | }, 15 | }) 16 | } 17 | 18 | // 推荐榜单 --首页 19 | async getRecBangList () { 20 | return this.commonRequest('http://www.kuwo.cn/api/www/bang/index/bangList?&httpsStatus=1', { 21 | headers: { 22 | Referer: 'http://www.kuwo.cn/rankList', 23 | }, 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/service/recGedan.ts: -------------------------------------------------------------------------------- 1 | const BaseService = require('./BaseService') 2 | 3 | export default class RecGedan extends BaseService { 4 | async index ({ id, pn, rn }) { 5 | return this.commonRequest(`http://www.kuwo.cn/api/www/rcm/index/playlist?id=${id}&pn=${pn}&rn=${rn}&httpsStatus=1`) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/service/recSinger.ts: -------------------------------------------------------------------------------- 1 | const BaseService = require('./BaseService') 2 | 3 | export default class RecSinger extends BaseService { 4 | async index ({ category, pn, rn }) { 5 | return this.commonRequest(`http://www.kuwo.cn/api/www/artist/artistInfo?category=${category}&pn=${pn}&rn=${rn}&httpsStatus=1`) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/service/search.ts: -------------------------------------------------------------------------------- 1 | const BaseService = require('./BaseService') 2 | 3 | export default class Search extends BaseService { 4 | // 关键字搜索 5 | async searchKey (key) { 6 | return this.commonRequest(`http://www.kuwo.cn/api/www/search/searchKey?key=${key}&httpsStatus=1`) 7 | } 8 | 9 | // 单曲搜索 10 | async searchMusicBykeyWord ({ key, pn, rn }) { 11 | return this.commonRequest(`http://www.kuwo.cn/api/www/search/searchMusicBykeyWord?key=${key}&pn=${pn}&rn=${rn}&httpsStatus=1`) 12 | } 13 | 14 | // 专辑搜索 15 | async searchAlbumBykeyWord ({ key, pn, rn }) { 16 | return this.commonRequest(`http://www.kuwo.cn/api/www/search/searchAlbumBykeyWord?key=${key}&pn=${pn}&rn=${rn}&httpsStatus=1`) 17 | } 18 | 19 | // mv 搜索 20 | async searchMvBykeyWord ({ key, pn, rn }) { 21 | return this.commonRequest(`http://www.kuwo.cn/api/www/search/searchMvBykeyWord?key=${key}&pn=${pn}&rn=${rn}&httpsStatus=1`) 22 | } 23 | 24 | // 歌单搜索 25 | async searchPlayListBykeyWord ({ key, pn, rn }) { 26 | return this.commonRequest(`http://www.kuwo.cn/api/www/search/searchPlayListBykeyWord?key=${key}&pn=${pn}&rn=${rn}&httpsStatus=1`) 27 | } 28 | 29 | // 歌手搜索 30 | async searchArtistBykeyWord ({ key, pn, rn }) { 31 | return this.commonRequest(`http://www.kuwo.cn/api/www/search/searchArtistBykeyWord?key=${key}&pn=${pn}&rn=${rn}&httpsStatus=1`) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/service/singer.ts: -------------------------------------------------------------------------------- 1 | const BaseService = require('./BaseService') 2 | 3 | export default class Artist extends BaseService { 4 | // 获取歌手 5 | async getArtistInfo ({ category, rn, pn, prefix }) { 6 | return this.commonRequest( 7 | prefix 8 | ? `http://www.kuwo.cn/api/www/artist/artistInfo?category=${category}&pn=${pn}&rn=${rn}&prefix=${prefix}&httpsStatus=1` 9 | : `http://www.kuwo.cn/api/www/artist/artistInfo?category=${category}&pn=${pn}&rn=${rn}&httpsStatus=1`, { 10 | headers: { 11 | Referer: prefix ? 'http://www.kuwo.cn/singers' : undefined, 12 | }, 13 | }) 14 | } 15 | 16 | // 获取歌手单曲 17 | async getArtistMusic ({ artistid, rn, pn }) { 18 | return this.commonRequest(`http://www.kuwo.cn/api/www/artist/artistMusic?artistid=${artistid}&rn=${rn}&pn=${pn}&httpsStatus=1`, { 19 | headers: { 20 | Referer: 'http://www.kuwo.cn/singer_detail/' + encodeURIComponent(artistid), 21 | }, 22 | }) 23 | } 24 | 25 | // 获取歌手专辑 26 | 27 | async getArtistAlbum ({ artistid, rn, pn }) { 28 | return this.commonRequest(`http://www.kuwo.cn/api/www/artist/artistAlbum?artistid=${artistid}&rn=${rn}&pn=${pn}&httpsStatus=1`, { 29 | headers: { 30 | Referer: 'http://www.kuwo.cn/singer_detail/' + encodeURIComponent(artistid) + '/album', 31 | }, 32 | }) 33 | } 34 | 35 | // 获取歌手mv 36 | async getArtistMv ({ artistid, rn, pn }) { 37 | return this.commonRequest(`http://www.kuwo.cn/api/www/artist/artistMv?artistid=${artistid}&rn=${rn}&pn=${pn}&httpsStatus=1`, { 38 | headers: { 39 | Referer: 'http://www.kuwo.cn/singer_detail/' + encodeURIComponent(artistid) + '/mv', 40 | }, 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/utils/secret.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | /** 3 | * @param t 4 | * @param e 5 | */ 6 | function h (t, e) { 7 | if (e == null || e.length <= 0) { 8 | return console.log('Please enter a password with which to encrypt the message.'), 9 | null; 10 | } 11 | for (var n = '', i = 0; i < e.length; i++) { n += e.charCodeAt(i).toString(); } 12 | const r = Math.floor(n.length / 5); 13 | const o = parseInt(n.charAt(r) + n.charAt(2 * r) + n.charAt(3 * r) + n.charAt(4 * r) + n.charAt(5 * r)); 14 | const l = Math.ceil(e.length / 2); 15 | const c = Math.pow(2, 31) - 1; 16 | if (o < 2) { 17 | return console.log('Algorithm cannot find a suitable hash. Please choose a different password. \nPossible considerations are to choose a more complex or longer password.'), 18 | null; 19 | } 20 | let d = Math.round(1e9 * Math.random()) % 1e8; 21 | for (n += d; n.length > 10;) { n = (parseInt(n.substring(0, 10)) + parseInt(n.substring(10, n.length))).toString(); } 22 | n = (o * n + l) % c; 23 | let h = ''; 24 | let f = ''; 25 | for (i = 0; i < t.length; i++) { 26 | f += (h = parseInt(t.charCodeAt(i) ^ Math.floor(n / c * 255))) < 16 ? '0' + h.toString(16) : h.toString(16), 27 | n = (o * n + l) % c; 28 | } 29 | for (d = d.toString(16); d.length < 8;) { d = '0' + d; } 30 | return f += d; 31 | } 32 | 33 | const f = 'Hm_Iuvt_cdb524f42f0ce19b169b8072123a4727'; 34 | const Cookie = 'Hm_lvt_cdb524f42f0ce19b169a8071123a4797=1689780885000; _ga=GA1.2.259721034.1689780885000; _gid=GA1.2.1715768254.1689780885000; Hm_lpvt_cdb524f42f0ce19b169a8071123a4797=1689780885000; _ga_ETPBRPM9ML=GS1.2.1689780885000.2.0.1689780885000.60.0.0; Hm_Iuvt_cdb524f42f0ce19b169b8072123a4727=3MiWHX6n8Zr8sN48sF3dccyTWjZ54Hxy'; 35 | const v = function (t = f) { 36 | // 使用 cookie 37 | const e = Cookie; 38 | let n = e.indexOf(t + '='); 39 | if (n !== -1) { 40 | n = n + t.length + 1; 41 | let r = e.indexOf(';', n); 42 | return r === -1 && (r = e.length), 43 | unescape(e.substring(n, r)); 44 | } 45 | return null; 46 | } 47 | 48 | let l = ''; 49 | l = Object(v)(f); 50 | 51 | module.exports = { 52 | h, 53 | v, 54 | f, 55 | Cookie, 56 | }; 57 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '8' 4 | 5 | install: 6 | - ps: Install-Product node $env:nodejs_version 7 | - npm i npminstall && node_modules\.bin\npminstall 8 | 9 | test_script: 10 | - node --version 11 | - npm --version 12 | - npm run test 13 | 14 | build: off 15 | -------------------------------------------------------------------------------- /config/config.default.ts: -------------------------------------------------------------------------------- 1 | import { EggAppConfig, EggAppInfo, PowerPartial } from 'egg' 2 | const path = require('path') 3 | const fs = require('fs') 4 | 5 | export default (appInfo: EggAppInfo) => { 6 | const config = {} as PowerPartial 7 | 8 | // override config from framework / plugin 9 | // use for cookie sign key, should change to your own and keep security 10 | config.keys = appInfo.name + '_1618050800795_6113' 11 | 12 | // add your egg config in here 13 | config.middleware = [] 14 | 15 | // add your special config in here 16 | const bizConfig = { 17 | sourceUrl: `https://github.com/eggjs/examples/tree/master/${appInfo.name}`, 18 | headers: { 19 | /** 20 | * _ga=GA1.2.675600123.1604474404; gid=1c56e740-1027-41a2-bbd5-40c0372f1af8; JSESSIONID=1wc56uuw4qocl1jef6lxbdm7gq; uname3=%u90B1%u8000%u6D2A; t3kwid=485455771; userid=485455771; websid=155282790; 21 | * pic3="http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eqNeken3rBYSfXTn2Nq6uOm8aumOqIqsj7ibvM3bx9ibUmTp644d0hO4qftyS3iabA48AUzMdqauqaHQ/132"; 22 | * t3=weixin; Hm_lvt_cdb524f42f0ce19b169a8071123a4797=1622942674,1623162158,1623249078,1623338422; Hm_lpvt_cdb524f42f0ce19b169a8071123a4797=1623338422; 23 | * _gid=GA1.2.85752388.1623338422; kw_token=9ACQW0P7QIP; _gat=1 24 | */ 25 | Cookie: 'Hm_lvt_cdb524f42f0ce19b169a8071123a4797=1623339177,1623339183; _ga=GA1.2.1195980605.1579367081; Hm_lpvt_cdb524f42f0ce19b169a8071123a4797=1623339982; kw_token=3E7JFQ7MRPL; _gid=GA1.2.747985028.1623339179; _gat=1', 26 | csrf: '3E7JFQ7MRPL', 27 | Host: 'www.kuwo.cn', 28 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36', 29 | }, 30 | } 31 | 32 | config.security = { 33 | csrf: { 34 | enable: false, 35 | }, 36 | domainWhiteList: ['*'], 37 | } 38 | 39 | // 跨域支持 40 | config.cors = { 41 | origin: '*', 42 | allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS', 43 | } 44 | // 端口 45 | config.cluster = { 46 | listen: { 47 | path: '', 48 | port: 7002, 49 | hostname: '127.0.0.1', 50 | }, 51 | } 52 | 53 | config.siteFile = { 54 | '/': fs.readFileSync(path.join(appInfo.baseDir, 'app/public/index.html')), 55 | } 56 | 57 | // the return config will combines to EggAppConfig 58 | return { 59 | ...config, 60 | ...bizConfig, 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /config/config.local.ts: -------------------------------------------------------------------------------- 1 | import { EggAppConfig, PowerPartial } from 'egg' 2 | 3 | export default () => { 4 | const config: PowerPartial = {} 5 | return config 6 | } 7 | -------------------------------------------------------------------------------- /config/config.prod.ts: -------------------------------------------------------------------------------- 1 | import { EggAppConfig, PowerPartial } from 'egg' 2 | 3 | export default () => { 4 | const config: PowerPartial = { 5 | proxy: true, 6 | } 7 | return config 8 | } 9 | -------------------------------------------------------------------------------- /config/plugin.ts: -------------------------------------------------------------------------------- 1 | import { EggPlugin } from 'egg' 2 | 3 | const plugin: EggPlugin = { 4 | // static: true, 5 | // nunjucks: { 6 | // enable: true, 7 | // package: 'egg-view-nunjucks', 8 | // }, 9 | // assets :{ 10 | // enable: true, 11 | // package: 'egg-view-assets', 12 | // }, 13 | cors: { 14 | enable: true, 15 | package: 'egg-cors', 16 | }, 17 | 18 | } 19 | 20 | export default plugin 21 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qyhqiu/kuwoMusicApi/e8e720b90b4d7e3052078a3380906f2b3349e388/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # 酷我音乐API 2 | 3 | 4 | ## 接口文档 5 | 6 | #### 播放链接( `music`| `mv`) 统一接口 7 | 8 | 接口: `/kuwo/url?mid=162457325&type=music&br=128kmp3` | `/kuwo/url?mid=162457325&type=mv` 9 | 10 | | 参数 | 说明 | 是否必须 | 示例 | 11 | | ----- | ------- | -------- | ---------------- | 12 | | `mid` | 歌曲 id | 是 | `mid` =162457325 | 13 | | `type` | 播放类型 | 默认 music | `type`= music / mv | 14 | | `br` | 播放音质:可选 128kmp3、192kmp3、320kmp3 | 默认 128kmp3 | `br`= 128kmp3 | 15 | 16 | 17 | 18 | #### 歌词 19 | 20 | 示例: `/kuwo/lrc?musicId=162457325` 21 | 22 | | 参数 | 说明 | 是否必须 | 示例 | 23 | | --------- | ------ | -------- | ------------------- | 24 | | `musicId` | 音乐id | 是 | `musicId`=162457325 | 25 | | | | | | 26 | 27 | 28 | 29 | #### 搜索 30 | 31 | 接口: `/kuwo/search/searchMusicBykeyWord?key=等你归来` 32 | 33 | > 1. `/kuwo/search/searchKey?key=xxx` 34 | > 2. `/kuwo/search/searchMusicBykeyWord?key=xxx` 35 | > 3. `/kuwo/search/searchAlbumBykeyWord?key=xxx` 36 | > 4. `/kuwo/search/searchMvBykeyWord?key=xxx` 37 | > 5. `/kuwo/search/searchPlayListBykeyWord?key=xxx` 38 | > 6. `/kuwo/search/searchArtistBykeyWord?key=xxx` 39 | 40 | 参数 : `searchKey` 搜索提示 `searchMusicBykeyWord` 单曲`searchAlbumBykeyWord` 专辑`searchMvBykeyWord` mv `searchPlayListBykeyWord` 歌单 `searchArtistBykeyWord` 歌手 41 | 42 | | 参数 | 说明 | 是否必须 | 示例 | 43 | | ----- | ---------- | ---------- | -------------- | 44 | | `key` | 搜索关键字 | 是 | key = 等你归来 | 45 | | `pn` | 页数 | 否 默认 1 | `pn `= 2 | 46 | | `rn` | 每页数量 | 否 默认 30 | `rn `= 15 | 47 | 48 | 49 | 50 | #### 轮播图 51 | 52 | 接口: `/kuwo/banner` 53 | 54 | ```json 55 | { 56 | "code": 200, 57 | "curTime": 1581397195179, 58 | "data": [ 59 | { 60 | "id": 1, 61 | "pic": "http://kwimg4.kuwo.cn/star/upload/47/52/1557311466098_.png", 62 | "priority": 1, 63 | "url": "http://down.kuwo.cn/mbox/kwmusic_web_1.exe" 64 | }, 65 | { 66 | "id": 18, 67 | "pic": "http://kwimg2.kuwo.cn/star/upload/12/15/1581389006583_.jpg", 68 | "priority": 2, 69 | "url": "http://www.kuwo.cn/album_detail/12554981" 70 | }, 71 | { 72 | "id": 10, 73 | "pic": "http://kwimg4.kuwo.cn/star/upload/76/42/1581305352340_.jpg", 74 | "priority": 3, 75 | "url": "http://www.kuwo.cn/album_detail/12720213" 76 | }, 77 | { 78 | "id": 9, 79 | "pic": "http://kwimg3.kuwo.cn/star/upload/72/82/1581305211162_.jpg", 80 | "priority": 4, 81 | "url": "http://www.kuwo.cn/album_detail/7662111" 82 | }, 83 | { 84 | "id": 11, 85 | "pic": "http://kwimg2.kuwo.cn/star/upload/2/94/1581092599880_.jpg", 86 | "priority": 5, 87 | "url": "http://www.kuwo.cn/playlist_detail/2952464073" 88 | }, 89 | { 90 | "id": 19, 91 | "pic": "http://kwimg4.kuwo.cn/star/upload/1/51/1581220186723_.jpg", 92 | "priority": 6, 93 | "url": "http://www.kuwo.cn/playlist_detail/2950090991" 94 | }, 95 | { 96 | "id": 20, 97 | "pic": "http://kwimg3.kuwo.cn/star/upload/34/71/1580525755117_.jpg", 98 | "priority": 7, 99 | "url": "http://www.kuwo.cn/playlist_detail/2948964151" 100 | }, 101 | { 102 | "id": 7, 103 | "pic": "http://kwimg1.kuwo.cn/star/upload/6/66/1579069807332_.jpg", 104 | "priority": 8, 105 | "url": "http://jx.kuwo.cn/KuwoLive/OpenLiveRoomLinkForKw?from=1001004053" 106 | } 107 | ], 108 | "msg": "success", 109 | "profileId": "site", 110 | "reqId": "be53e268b062aa76eca763beb8a7f071" 111 | } 112 | ``` 113 | 114 | 115 | 116 | #### 评论 117 | 118 | 接口: `/kuwo/comment?sid=80958029&type=get_rec_comment&page=1&rows=30&digest=15` 119 | 120 | | 参数 | 说明 | 是否必须 | 示例 | 121 | | -------- | --------------------------------------------------- | -------- | ---------------------- | 122 | | `sid` | 评论类型 id | 是 | `sid`=80958029 | 123 | | `page` | 页数 | | 默认1 | 124 | | `rows` | 每页条数 | | 默认 30 | 125 | | `type` | `get_rec_comment` 热门评论 `get_comment` 最新评论 | | 默认 `get_rec_comment` | 126 | | `digest` | 15 歌曲 2 排行榜 7 mv评论 8 歌单评论 | | 默认 `15` | 127 | 128 | ### 歌单 129 | 130 | #### - 推荐歌单 131 | 132 | 接口:`/kuwo/rec_gedan` 133 | 134 | | 參數 | 説明 | 是否必須 | 示例 | 135 | | ---- | -------- | -------- | ----- | 136 | | `rn` | 分页 | | 默认1 | 137 | | `pn` | 每页数量 | | 默认5 | 138 | 139 | ```json 140 | list: [ 141 | 0: {img: "http://img1.kwcdn.kuwo.cn/star/userpl2015/10/13/1582042149394_132026710_150.jpg", uname: "",…} 142 | img: "http://img1.kwcdn.kuwo.cn/star/userpl2015/10/13/1582042149394_132026710_150.jpg" 143 | uname: "" 144 | img700: "http://img1.kwcdn.kuwo.cn/star/userpl2015/10/13/1582042149394_132026710_700.jpg" 145 | img300: "http://img1.kwcdn.kuwo.cn/star/userpl2015/10/13/1582042149394_132026710b.jpg" 146 | userName: "" 147 | img500: "http://img1.kwcdn.kuwo.cn/star/userpl2015/10/13/1582042149394_132026710_500.jpg" 148 | total: 548 149 | name: "每日最新单曲推荐" 150 | listencnt: 197482809 151 | id: 1082685104 152 | tag: "" 153 | musicList: [] 154 | desc: "" 155 | info: "该专辑先后邀请孟文豪、董楠、良朋、崔恕、赵佳霖等国内知名音乐人,分别围绕致敬生命、致敬逆行者、致敬祖国等角度进行创作,并将邀请韩磊、佟丽娅等国内著名歌手及青年演员倾情演唱" 156 | 1: {img: "http://img1.kwcdn.kuwo.cn/star/userpl2015/15/53/1482267851912_54513215_500.jpg", uname: "熙姊。",…} 157 | img: "http://img1.kwcdn.kuwo.cn/star/userpl2015/15/53/1482267851912_54513215_500.jpg" 158 | uname: "熙姊。" 159 | img700: "http://img1.kwcdn.kuwo.cn/star/userpl2015/15/53/1482267851912_54513215_700.jpg" 160 | img300: "http://img1.kwcdn.kuwo.cn/star/userpl2015/15/53/1482267851912_54513215b.jpg" 161 | userName: "熙姊。" 162 | img500: "http://img1.kwcdn.kuwo.cn/star/userpl2015/15/53/1482267851912_54513215_500.jpg" 163 | total: 51 164 | name: "<周杰伦>饶舌Rap快歌最全合集" 165 | listencnt: 636642 166 | id: 2073771443 167 | musicList: [] 168 | desc: "" 169 | info: "觉得自己舌头不错的勇士,快来挑战吧!" 170 | } 171 | ,…] 172 | ``` 173 | 174 | 175 | 176 | #### - 歌单音乐 177 | 178 | 接口:`/kuwo/musicList` 179 | 180 | 示例:`/kuwo/musicList?pid=1082685104` 181 | 182 | | 参数 | 说明 | 是否必须 | 示例 | 183 | | ----- | -------- | -------- | ------------- | 184 | | `pid` | 歌单id | 是 | id=1082685104 | 185 | | `rn` | 分页 | | 默认1 | 186 | | `pn` | 每页数量 | | 默认30 | 187 | 188 | 189 | 190 | #### - 默认歌单 191 | 192 | 接口: `/kuwo/playList` 193 | 194 | 示例:`/kuwo/playList?order=new&rn=30&pn=1` 195 | 196 | | 参数 | 说明 | 是否必须 | 示例 | 197 | | ----- | ------------------ | -------- | -------- | 198 | | order | new 最新 hot 最热 | | 默认 new | 199 | | `rn` | 每页条数 | | 默认 30 | 200 | | `pn` | 页数 | | 默认 1 | 201 | 202 | ### - 歌手专辑 歌单 203 | 204 | 接口: `/kuwo/albumInfo` 205 | 206 | 示例: `/kuwo/albumInfo?albumId=49449&rn=30&pn=1` 207 | 208 | | 参数 | 说明 | 是否必须 | 示例 | 209 | | --------- | -------- | -------- | --------------- | 210 | | `albumId` | 专辑 id | 是 | `albumId`=49449 | 211 | | `rn` | 每页条数 | | 默认 30 | 212 | | `pn` | 页数 | | 默认 1 | 213 | 214 | ```json 215 | { 216 | "code": 200, 217 | "curTime": 1636723164284, 218 | "data": { 219 | "playCnt": 126806, 220 | "artist": "周杰伦", 221 | "releaseDate": "2010-05-18", 222 | "album": "跨时代", 223 | "albumid": 49449, 224 | "pay": 0, 225 | "artistid": 336, 226 | "pic": "https://img2.kuwo.cn/star/albumcover/300/98/24/205164915.jpg", 227 | "isstar": 0, 228 | "total": 12, 229 | "content_type": "0", 230 | "albuminfo": "跨越自己 创新时代 周杰伦 2010第十辑【跨时代】 跨乐古今.飞越想象.创意绝伦.超时感动 华语乐坛天王周杰伦每次带给大家的全新音乐每每引领话题,而专辑封面更是备受瞩目!第10张个人专辑「跨时代」在5月18日全亚洲同步发行,随着封面的曝光,彷佛预告了一张经典专辑即将诞生!周杰伦穿着中古世纪的服装,化身吸血鬼王子,穿梭时空来到现代,在古堡的天台上,他忧郁凝视远方,只要音乐存在,吸血鬼王子就能跨越时代闻乐苏醒,这也是这张专辑的音乐精神所在,当初创作音乐的初衷与坚持依然不变,就像周杰伦在他第一张同名专辑内页里写的:「希望我现在认为很屌的杰伦专辑,以后听也能一样佩服自己!」事实上周董对自己的音乐依然充满自信,希望音乐可以跨时代继续感动所有人。 封面里周杰伦一身中古世纪的服装令人惊艳!尤其是黑色蕾丝斗篷披风有别于一般人对吸血鬼的印象,造型师表示今年流行的蕾丝大多运用在女生上,但我们大胆的用在杰伦的身上却有很自然又突出的效果!而这次拍摄封面与内页的场景就是在「跨时代」MV的场景里拍摄,片场租了两个大棚搭建了三个场景,光封面拍照的场景动辄就花费300万!为达到视觉上的三维效果,「跨时代」专辑铁盒精装版的封面将制作成3D影像,让歌迷有身历其境的感受! 「超人不会飞」道出十年心路历程 各方意见让人疲惫,压力大但不能流泪 超人周董还要继续飞 「超人不会飞」 自嘲好人好事代表 需接受众人检视 伟大无奈的杰伦就算压力大 仍坚持硬汉形象 因为「超人不能流眼泪」 天王周杰伦一举一动均引起华语乐坛骚动,2008年发行【魔杰座】专辑,顺利获得金曲奖八项入围肯定,并拿下叁项大奖,成绩裴然。在广大乐迷引颈期盼将近两年后,周杰伦终于要在今年5月推出个人第十张全新音乐大碟, 4月26日早上10点新专辑首波主打歌「超人不会飞」完整首播,第一时间与听友分享周杰伦的最新创作。 主打歌「超人不会飞」 自嘲好人好事代表 需接受众人检视 就算压力大 仍坚持硬汉形象 因为「超人不能流眼泪」 新歌「超人不会飞」,由周杰伦填词谱曲兼制作,以抒情曲风缓缓唱出出道十年心路历程与感慨。歌词中谈到自己带来的社会以及音乐现象,更自嘲自己成为好人好事代表,需接受众人检视,自然散发出独特的周式幽默风格,令人会心一笑,周杰伦表示「一般带有批判意味的歌词都会用重节奏或饶舌曲风包装,但我就是要用抒情曲风带给大家全新感受!」 歌名「超人不会飞」,自嘲没有特异功能的周杰伦,只有梦想与坚持去做音乐!没想到他的歌曲可以变成学校教材,他的一举一动都变成社会榜样,每个人都在帮他计算收视率、票房与奖项,他的形象、表情、言行举止、甚至开的车、住的楼,还是专辑有没有夺冠?电影有没有创下高票房?通通被挖出来仔细检验,如同歌词所说,妈妈说很多事别太计较,只是使命感找到了我,我睡不着。他也不禁大唱,我到底是一个创作歌手,还是好人好事代表。周杰伦表示这样的生活难免觉得累,也需要呼吸空间,但歌词同时也透露「不要问我哭过了没,因为超人不能流眼泪!」依然展现十足铁汉个性。", 231 | "lang": "普通话", 232 | "musicList": [ 233 | { 234 | "musicrid": "MUSIC_728673", 235 | "barrage": "0", 236 | "artist": "周杰伦", 237 | "mvpayinfo": { 238 | "play": 0, 239 | "vid": 10307548, 240 | "down": 0 241 | }, 242 | "pic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 243 | "isstar": 0, 244 | "rid": 728673, 245 | "duration": 194, 246 | "score100": "57", 247 | "content_type": "0", 248 | "track": 1, 249 | "hasLossless": true, 250 | "hasmv": 1, 251 | "releaseDate": "2010-05-18", 252 | "album": "跨时代", 253 | "albumid": 49449, 254 | "pay": "16711935", 255 | "artistid": 336, 256 | "albumpic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 257 | "originalsongtype": 1, 258 | "songTimeMinutes": "03:14", 259 | "isListenFee": true, 260 | "pic120": "https://img2.kuwo.cn/star/albumcover/120/98/24/205164915.jpg", 261 | "name": "跨时代", 262 | "online": 1, 263 | "payInfo": { 264 | "play": "1111", 265 | "download": "1111", 266 | "local_encrypt": "1", 267 | "limitfree": 0, 268 | "cannotDownload": 0, 269 | "refrain_start": 48031, 270 | "listen_fragment": "1", 271 | "refrain_end": 69528, 272 | "cannotOnlinePlay": 0, 273 | "feeType": { 274 | "song": "1", 275 | "vip": "1" 276 | }, 277 | "down": "1111" 278 | } 279 | }, 280 | { 281 | "musicrid": "MUSIC_728675", 282 | "barrage": "0", 283 | "artist": "周杰伦", 284 | "mvpayinfo": { 285 | "play": 0, 286 | "vid": 10307415, 287 | "down": 0 288 | }, 289 | "pic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 290 | "isstar": 0, 291 | "rid": 728675, 292 | "duration": 282, 293 | "score100": "67", 294 | "content_type": "0", 295 | "track": 2, 296 | "hasLossless": true, 297 | "hasmv": 1, 298 | "releaseDate": "2010-05-18", 299 | "album": "跨时代", 300 | "albumid": 49449, 301 | "pay": "16711935", 302 | "artistid": 336, 303 | "albumpic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 304 | "originalsongtype": 1, 305 | "songTimeMinutes": "04:42", 306 | "isListenFee": true, 307 | "pic120": "https://img2.kuwo.cn/star/albumcover/120/98/24/205164915.jpg", 308 | "name": "说了再见", 309 | "online": 1, 310 | "payInfo": { 311 | "play": "1111", 312 | "download": "1111", 313 | "local_encrypt": "1", 314 | "limitfree": 0, 315 | "cannotDownload": 0, 316 | "refrain_start": 93238, 317 | "listen_fragment": "1", 318 | "refrain_end": 124785, 319 | "cannotOnlinePlay": 0, 320 | "feeType": { 321 | "song": "1", 322 | "vip": "1" 323 | }, 324 | "down": "1111" 325 | } 326 | }, 327 | { 328 | "musicrid": "MUSIC_728677", 329 | "barrage": "0", 330 | "artist": "周杰伦", 331 | "mvpayinfo": { 332 | "play": 0, 333 | "vid": 10307419, 334 | "down": 0 335 | }, 336 | "pic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 337 | "isstar": 0, 338 | "rid": 728677, 339 | "duration": 262, 340 | "score100": "77", 341 | "content_type": "0", 342 | "track": 3, 343 | "hasLossless": true, 344 | "hasmv": 1, 345 | "releaseDate": "2010-05-18", 346 | "album": "跨时代", 347 | "albumid": 49449, 348 | "pay": "16711935", 349 | "artistid": 336, 350 | "albumpic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 351 | "originalsongtype": 1, 352 | "songTimeMinutes": "04:22", 353 | "isListenFee": true, 354 | "pic120": "https://img2.kuwo.cn/star/albumcover/120/98/24/205164915.jpg", 355 | "name": "烟花易冷", 356 | "online": 1, 357 | "payInfo": { 358 | "play": "1111", 359 | "download": "1111", 360 | "local_encrypt": "1", 361 | "limitfree": 0, 362 | "cannotDownload": 0, 363 | "refrain_start": 61374, 364 | "listen_fragment": "1", 365 | "refrain_end": 112162, 366 | "cannotOnlinePlay": 0, 367 | "feeType": { 368 | "song": "1", 369 | "vip": "1" 370 | }, 371 | "down": "1111" 372 | } 373 | }, 374 | { 375 | "musicrid": "MUSIC_728674", 376 | "barrage": "0", 377 | "artist": "周杰伦", 378 | "mvpayinfo": { 379 | "play": 0, 380 | "vid": 10307549, 381 | "down": 0 382 | }, 383 | "nationid": "0", 384 | "pic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 385 | "isstar": 0, 386 | "rid": 728674, 387 | "duration": 238, 388 | "score100": "50", 389 | "content_type": "0", 390 | "track": 4, 391 | "hasLossless": true, 392 | "hasmv": 1, 393 | "releaseDate": "2010-05-18", 394 | "album": "跨时代", 395 | "albumid": 49449, 396 | "pay": "16711935", 397 | "artistid": 336, 398 | "albumpic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 399 | "originalsongtype": 1, 400 | "songTimeMinutes": "03:58", 401 | "isListenFee": true, 402 | "pic120": "https://img2.kuwo.cn/star/albumcover/120/98/24/205164915.jpg", 403 | "name": "免费教学录影带", 404 | "online": 1, 405 | "payInfo": { 406 | "play": "1111", 407 | "download": "1111", 408 | "local_encrypt": "1", 409 | "limitfree": 0, 410 | "cannotDownload": 0, 411 | "refrain_start": 61695, 412 | "listen_fragment": "1", 413 | "refrain_end": 83273, 414 | "cannotOnlinePlay": 0, 415 | "feeType": { 416 | "song": "1", 417 | "vip": "1" 418 | }, 419 | "down": "1111" 420 | } 421 | }, 422 | { 423 | "musicrid": "MUSIC_728669", 424 | "barrage": "0", 425 | "artist": "周杰伦", 426 | "mvpayinfo": { 427 | "play": 0, 428 | "vid": 10307545, 429 | "down": 0 430 | }, 431 | "pic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 432 | "isstar": 0, 433 | "rid": 728669, 434 | "duration": 252, 435 | "score100": "59", 436 | "content_type": "0", 437 | "track": 5, 438 | "hasLossless": true, 439 | "hasmv": 1, 440 | "releaseDate": "2010-05-18", 441 | "album": "跨时代", 442 | "albumid": 49449, 443 | "pay": "16711935", 444 | "artistid": 336, 445 | "albumpic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 446 | "originalsongtype": 1, 447 | "songTimeMinutes": "04:12", 448 | "isListenFee": true, 449 | "pic120": "https://img2.kuwo.cn/star/albumcover/120/98/24/205164915.jpg", 450 | "name": "好久不见", 451 | "online": 1, 452 | "payInfo": { 453 | "play": "1111", 454 | "download": "1111", 455 | "local_encrypt": "1", 456 | "limitfree": 0, 457 | "cannotDownload": 0, 458 | "refrain_start": 89054, 459 | "listen_fragment": "1", 460 | "refrain_end": 129689, 461 | "cannotOnlinePlay": 0, 462 | "feeType": { 463 | "song": "1", 464 | "vip": "1" 465 | }, 466 | "down": "1111" 467 | } 468 | }, 469 | { 470 | "musicrid": "MUSIC_726836", 471 | "barrage": "0", 472 | "artist": "周杰伦", 473 | "mvpayinfo": { 474 | "play": 0, 475 | "vid": 10307544, 476 | "down": 0 477 | }, 478 | "pic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 479 | "isstar": 0, 480 | "rid": 726836, 481 | "duration": 256, 482 | "score100": "65", 483 | "content_type": "0", 484 | "track": 6, 485 | "hasLossless": true, 486 | "hasmv": 1, 487 | "releaseDate": "2010-05-18", 488 | "album": "跨时代", 489 | "albumid": 49449, 490 | "pay": "16711935", 491 | "artistid": 336, 492 | "albumpic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 493 | "originalsongtype": 1, 494 | "songTimeMinutes": "04:16", 495 | "isListenFee": true, 496 | "pic120": "https://img2.kuwo.cn/star/albumcover/120/98/24/205164915.jpg", 497 | "name": "雨下一整晚", 498 | "online": 1, 499 | "payInfo": { 500 | "play": "1111", 501 | "download": "1111", 502 | "local_encrypt": "1", 503 | "limitfree": 0, 504 | "cannotDownload": 0, 505 | "refrain_start": 59084, 506 | "listen_fragment": "1", 507 | "refrain_end": 105386, 508 | "cannotOnlinePlay": 0, 509 | "feeType": { 510 | "song": "1", 511 | "vip": "1" 512 | }, 513 | "down": "1111" 514 | } 515 | }, 516 | { 517 | "musicrid": "MUSIC_735136", 518 | "barrage": "0", 519 | "artist": "周杰伦", 520 | "mvpayinfo": { 521 | "play": 0, 522 | "vid": 10307550, 523 | "down": 0 524 | }, 525 | "pic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 526 | "isstar": 0, 527 | "rid": 735136, 528 | "duration": 170, 529 | "score100": "50", 530 | "content_type": "0", 531 | "track": 7, 532 | "hasLossless": true, 533 | "hasmv": 1, 534 | "releaseDate": "2010-05-18", 535 | "album": "跨时代", 536 | "albumid": 49449, 537 | "pay": "16711935", 538 | "artistid": 336, 539 | "albumpic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 540 | "originalsongtype": 1, 541 | "songTimeMinutes": "02:50", 542 | "isListenFee": true, 543 | "pic120": "https://img2.kuwo.cn/star/albumcover/120/98/24/205164915.jpg", 544 | "name": "嘻哈空姐", 545 | "online": 1, 546 | "payInfo": { 547 | "play": "1111", 548 | "download": "1111", 549 | "local_encrypt": "1", 550 | "limitfree": 0, 551 | "cannotDownload": 0, 552 | "refrain_start": 56367, 553 | "listen_fragment": "1", 554 | "refrain_end": 73699, 555 | "cannotOnlinePlay": 0, 556 | "feeType": { 557 | "song": "1", 558 | "vip": "1" 559 | }, 560 | "down": "1111" 561 | } 562 | }, 563 | { 564 | "musicrid": "MUSIC_728676", 565 | "barrage": "0", 566 | "artist": "周杰伦", 567 | "mvpayinfo": { 568 | "play": 0, 569 | "vid": 286155, 570 | "down": 0 571 | }, 572 | "pic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 573 | "isstar": 0, 574 | "rid": 728676, 575 | "duration": 258, 576 | "score100": "69", 577 | "content_type": "0", 578 | "track": 8, 579 | "hasLossless": true, 580 | "hasmv": 1, 581 | "releaseDate": "2010-05-18", 582 | "album": "跨时代", 583 | "albumid": 49449, 584 | "pay": "16711935", 585 | "artistid": 336, 586 | "albumpic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 587 | "originalsongtype": 1, 588 | "songTimeMinutes": "04:18", 589 | "isListenFee": true, 590 | "pic120": "https://img2.kuwo.cn/star/albumcover/120/98/24/205164915.jpg", 591 | "name": "我落泪情绪零碎", 592 | "online": 1, 593 | "payInfo": { 594 | "play": "1111", 595 | "download": "1111", 596 | "local_encrypt": "1", 597 | "limitfree": 0, 598 | "cannotDownload": 0, 599 | "refrain_start": 72065, 600 | "listen_fragment": "1", 601 | "refrain_end": 134852, 602 | "cannotOnlinePlay": 0, 603 | "feeType": { 604 | "song": "1", 605 | "vip": "1" 606 | }, 607 | "down": "1111" 608 | } 609 | }, 610 | { 611 | "musicrid": "MUSIC_728668", 612 | "barrage": "0", 613 | "artist": "周杰伦&杨瑞代", 614 | "mvpayinfo": { 615 | "play": 0, 616 | "vid": 7975651, 617 | "down": 0 618 | }, 619 | "pic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 620 | "isstar": 0, 621 | "rid": 728668, 622 | "duration": 254, 623 | "score100": "70", 624 | "content_type": "0", 625 | "track": 9, 626 | "hasLossless": true, 627 | "hasmv": 1, 628 | "releaseDate": "2010-05-18", 629 | "album": "跨时代", 630 | "albumid": 49449, 631 | "pay": "16711935", 632 | "artistid": 336, 633 | "albumpic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 634 | "originalsongtype": 1, 635 | "songTimeMinutes": "04:14", 636 | "isListenFee": true, 637 | "pic120": "https://img2.kuwo.cn/star/albumcover/120/98/24/205164915.jpg", 638 | "name": "爱的飞行日记", 639 | "online": 1, 640 | "payInfo": { 641 | "play": "1111", 642 | "download": "1111", 643 | "local_encrypt": "1", 644 | "limitfree": 0, 645 | "cannotDownload": 0, 646 | "refrain_start": 85425, 647 | "listen_fragment": "1", 648 | "refrain_end": 120859, 649 | "cannotOnlinePlay": 0, 650 | "feeType": { 651 | "song": "1", 652 | "vip": "1" 653 | }, 654 | "down": "1111" 655 | } 656 | }, 657 | { 658 | "musicrid": "MUSIC_728671", 659 | "barrage": "0", 660 | "artist": "周杰伦", 661 | "mvpayinfo": { 662 | "play": 0, 663 | "vid": 10307546, 664 | "down": 0 665 | }, 666 | "pic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 667 | "isstar": 0, 668 | "rid": 728671, 669 | "duration": 255, 670 | "score100": "55", 671 | "content_type": "0", 672 | "track": 10, 673 | "hasLossless": true, 674 | "hasmv": 1, 675 | "releaseDate": "2010-05-18", 676 | "album": "跨时代", 677 | "albumid": 49449, 678 | "pay": "16711935", 679 | "artistid": 336, 680 | "albumpic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 681 | "originalsongtype": 1, 682 | "songTimeMinutes": "04:15", 683 | "isListenFee": true, 684 | "pic120": "https://img2.kuwo.cn/star/albumcover/120/98/24/205164915.jpg", 685 | "name": "自导自演", 686 | "online": 1, 687 | "payInfo": { 688 | "play": "1111", 689 | "download": "1111", 690 | "local_encrypt": "1", 691 | "limitfree": 0, 692 | "cannotDownload": 0, 693 | "refrain_start": 69953, 694 | "listen_fragment": "1", 695 | "refrain_end": 99535, 696 | "cannotOnlinePlay": 0, 697 | "feeType": { 698 | "song": "1", 699 | "vip": "1" 700 | }, 701 | "down": "1111" 702 | } 703 | }, 704 | { 705 | "musicrid": "MUSIC_728672", 706 | "barrage": "0", 707 | "artist": "周杰伦", 708 | "mvpayinfo": { 709 | "play": 0, 710 | "vid": 10307547, 711 | "down": 0 712 | }, 713 | "pic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 714 | "isstar": 0, 715 | "rid": 728672, 716 | "duration": 299, 717 | "score100": "62", 718 | "content_type": "0", 719 | "track": 11, 720 | "hasLossless": true, 721 | "hasmv": 1, 722 | "releaseDate": "2010-05-18", 723 | "album": "跨时代", 724 | "albumid": 49449, 725 | "pay": "16711935", 726 | "artistid": 336, 727 | "albumpic": "https://img2.kuwo.cn/star/albumcover/500/98/24/205164915.jpg", 728 | "originalsongtype": 1, 729 | "songTimeMinutes": "04:59", 730 | "isListenFee": true, 731 | "pic120": "https://img2.kuwo.cn/star/albumcover/120/98/24/205164915.jpg", 732 | "name": "超人不会飞", 733 | "online": 1, 734 | "payInfo": { 735 | "play": "1111", 736 | "download": "1111", 737 | "local_encrypt": "1", 738 | "limitfree": 0, 739 | "cannotDownload": 0, 740 | "refrain_start": 99677, 741 | "listen_fragment": "1", 742 | "refrain_end": 146960, 743 | "cannotOnlinePlay": 0, 744 | "feeType": { 745 | "song": "1", 746 | "vip": "1" 747 | }, 748 | "down": "1111" 749 | } 750 | } 751 | ] 752 | }, 753 | "msg": "success", 754 | "profileId": "site", 755 | "reqId": "f89856d21f50d0b0047c1ae01fb7e86c", 756 | "tId": "" 757 | } 758 | ``` 759 | 760 | 761 | 762 | #### - 歌单分类 763 | 764 | 示例:`/kuwo/playList/getTagPlayList?id=2190` 765 | 766 | | 参数 | 说明 | 是否必须 | 示例 | 767 | | ---- | ----------- | -------- | --------- | 768 | | id | 歌单 tag id | 是 | id = 2190 | 769 | | `rn` | 每页条数 | | 默认 30 | 770 | | `pn` | 页数 | | 默认1 | 771 | 772 | 773 | 774 | #### - 歌单分类Tag 775 | 776 | 接口:`/kuwo/getTagList` 777 | 778 | ```json 779 | { 780 | "img": "http://img2.kwcdn.kuwo.cn/star/upload/11/11/1531190823851_.png", 781 | "mdigest": "5", 782 | "data": [ 783 | { 784 | "extend": "", 785 | "img": "http://img2.kwcdn.kuwo.cn/star/upload/15/15/1536566688335_.jpg", 786 | "digest": "10000", 787 | "name": "翻唱", 788 | "isnew": "0", 789 | "id": "1848" 790 | }, 791 | { 792 | "extend": "", 793 | "img": "http://img4.kwcdn.kuwo.cn/star/upload/13/13/1507883183437_.png", 794 | "digest": "10000", 795 | "name": "网络", 796 | "isnew": "0", 797 | "id": "621" 798 | }, 799 | { 800 | "extend": "", 801 | "img": "http://img1.kwcdn.kuwo.cn/star/upload/6/6/1507883159190_.png", 802 | "digest": "10000", 803 | "name": "经典", 804 | "isnew": "0", 805 | "id": "1265" 806 | }, 807 | { 808 | "extend": "", 809 | "img": "http://img1.kwcdn.kuwo.cn/star/upload/0/0/1507781397648_.png", 810 | "digest": "10000", 811 | "name": "轻音乐", 812 | "isnew": "0", 813 | "id": "173" 814 | }, 815 | { 816 | "extend": "", 817 | "img": "http://img3.kwcdn.kuwo.cn/star/upload/7/7/1517380561751_.png", 818 | "digest": "10000", 819 | "name": "怀旧", 820 | "isnew": "0", 821 | "id": "155" 822 | }, 823 | { 824 | "extend": "", 825 | "img": "http://img4.kwcdn.kuwo.cn/star/upload/11/11/1517380562443_.png", 826 | "digest": "10000", 827 | "name": "古风", 828 | "isnew": "0", 829 | "id": "127" 830 | }, 831 | { 832 | "extend": "", 833 | "img": "http://img3.kwcdn.kuwo.cn/star/upload/7/7/1517380562103_.png", 834 | "digest": "10000", 835 | "name": "网红", 836 | "isnew": "0", 837 | "id": "1879" 838 | }, 839 | { 840 | "extend": "", 841 | "img": "http://img3.kwcdn.kuwo.cn/star/upload/8/8/1507781342248_.png", 842 | "digest": "10000", 843 | "name": "佛乐", 844 | "isnew": "0", 845 | "id": "220" 846 | }, 847 | { 848 | "extend": "", 849 | "img": "http://img3.kwcdn.kuwo.cn/star/upload/7/7/1517380562103_.png", 850 | "digest": "10000", 851 | "name": "影视", 852 | "isnew": "0", 853 | "id": "180" 854 | }, 855 | { 856 | "extend": "", 857 | "img": "http://img4.kwcdn.kuwo.cn/star/upload/15/15/1507883255871_.png", 858 | "digest": "10000", 859 | "name": "器乐", 860 | "isnew": "0", 861 | "id": "578" 862 | }, 863 | { 864 | "extend": "", 865 | "img": "http://img3.kwcdn.kuwo.cn/star/upload/9/9/1517380562105_.png", 866 | "digest": "10000", 867 | "name": "游戏", 868 | "isnew": "0", 869 | "id": "1877" 870 | }, 871 | { 872 | "extend": "", 873 | "img": "http://img1.kwcdn.kuwo.cn/star/upload/10/10/1517468038218_.png", 874 | "digest": "10000", 875 | "name": "国漫游戏", 876 | "isnew": "0", 877 | "id": "181" 878 | }, 879 | { 880 | "extend": "", 881 | "img": "http://img3.kwcdn.kuwo.cn/star/upload/10/10/1507883200970_.png", 882 | "digest": "10000", 883 | "name": "KTV", 884 | "isnew": "0", 885 | "id": "361" 886 | }, 887 | { 888 | "extend": "", 889 | "img": "http://img1.kwcdn.kuwo.cn/star/upload/5/5/1517380561685_.png", 890 | "digest": "10000", 891 | "name": "喊麦", 892 | "isnew": "0", 893 | "id": "216" 894 | }, 895 | { 896 | "extend": "|HOT", 897 | "img": "http://img4.kwcdn.kuwo.cn/star/upload/3/3/1536566652003_.jpg", 898 | "digest": "10000", 899 | "name": "抖音", 900 | "isnew": "0", 901 | "id": "2189" 902 | }, 903 | { 904 | "extend": "", 905 | "img": "http://kwimg4.kuwo.cn/star/upload/16/73/1551434397397_.jpg", 906 | "digest": "10000", 907 | "name": "3D", 908 | "isnew": "0", 909 | "id": "1366" 910 | }, 911 | { 912 | "extend": "", 913 | "img": "http://img2.kwcdn.kuwo.cn/star/upload/15/15/1517380562303_.png", 914 | "digest": "10000", 915 | "name": "店铺专用", 916 | "isnew": "0", 917 | "id": "263" 918 | }, 919 | { 920 | "extend": "", 921 | "img": "http://img3.kwcdn.kuwo.cn/star/upload/14/14/1524130384286_.png", 922 | "digest": "10000", 923 | "name": "纯音乐", 924 | "isnew": "0", 925 | "id": "577" 926 | }, 927 | { 928 | "extend": "|NEW", 929 | "img": "http://kwimg2.kuwo.cn/star/upload/40/75/1579227646729_.jpg", 930 | "digest": "10000", 931 | "name": "春节", 932 | "isnew": "0", 933 | "id": "2190" 934 | } 935 | ], 936 | "name": "主题", 937 | "id": "5", 938 | "type": "list", 939 | "img1": "http://kwimg2.kuwo.cn/star/upload/46/19/1548987670837_.png" 940 | }, 941 | ``` 942 | 943 | 944 | 945 | 946 | 947 | ### 歌手 948 | 949 | #### - 全部歌手 950 | 951 | 接口: 952 | 953 | + `/kuwo/singer?category=0&rn=100&pn=1` 954 | + `/kuwo/singer?category=0&rn=100&pn=1&prefix=A` 955 | 956 | 參数: 分类: 0 = 全部 1 = 华语男 2 = 华语女 3 = 华语组合 4 = 日韩男 5 = 日韩女 6 = 日韩组合 7 = 欧美男 8 = 欧美女 9 = 欧美组合 10 = 其他 957 | 958 | | 参数 | 説明 | 是否必須 | 示例 | 959 | | -------- | -------- | -------- | ------- | 960 | | category | 分类 | 是 | 默认 0 | 961 | | `rn` | 每页数量 | | 默认100 | 962 | | `pn` | 分页 | | 默认1 | 963 | | prefix | A~Z 分类 | | | 964 | 965 | #### - 歌手单曲 966 | 967 | 接口:`/kuwo/singer/music?artistid=5371&rn=30&pn=1` 968 | 969 | | 參數 | 説明 | 是否必須 | 示例 | 970 | | ---------- | -------- | -------- | --------------- | 971 | | `artistid` | 歌手id | 是 | `artistid`=5371 | 972 | | `rn` | 每页数量 | | 默认30 | 973 | | `pn` | 分页 | | 默认1 | 974 | 975 | #### - 歌手專輯 976 | 977 | 接口:`/kuwo/singer/album?artistid=5371&rn=30&pn=1` 978 | 979 | | 參數 | 説明 | 是否必須 | 示例 | 980 | | ---------- | -------- | -------- | --------------- | 981 | | `artistid` | 歌手id | 是 | `artistid`=5371 | 982 | | `rn` | 每页数量 | | 默认30 | 983 | | `pn` | 分页 | | 默认1 | 984 | 985 | #### - 歌手mv 986 | 987 | 接口:`/kuwo/singer/mv?artistid=5371&rn=30&pn=1` 988 | 989 | | 參數 | 説明 | 是否必須 | 示例 | 990 | | ---------- | -------- | -------- | --------------- | 991 | | `artistid` | 歌手id | 是 | `artistid`=5371 | 992 | | `rn` | 每页数量 | | 默认30 | 993 | | `pn` | 分页 | | 默认 1 | 994 | 995 | #### - 歌手推荐 996 | 997 | 接口: `/kuwo/rec_singer` 998 | 999 | 示例: `/kuwo/rec_singer?category=11&rn=6&pn=1` 1000 | 1001 | `categroy`: 11 华语 13 欧美 12 日韩 16 组合 1002 | 1003 | | 參數 | 説明 | 是否必須 | 示例 | 1004 | | -------- | -------- | -------- | ------ | 1005 | | category | 分类 | | 默认11 | 1006 | | `rn` | 每页数量 | | 默认6 | 1007 | | `pn` | 页数 | | 默认1 | 1008 | 1009 | 1010 | 1011 | ### 音樂信息 1012 | 1013 | 接口:`/kuwo/musicInfo?mid=162457325` 1014 | 1015 | | 參數 | 説明 | 是否必須 | 示例 | 1016 | | ---- | ------ | -------- | ----------- | 1017 | | mid | 音樂id | 是 | mid=6691107 | 1018 | 1019 | 1020 | 1021 | ### 排行榜 1022 | 1023 | #### - 排行榜單 1024 | 1025 | 接口:`/kuwo/rank` 1026 | 1027 | ```json 1028 | { 1029 | "name": "官方榜", 1030 | "list": [ 1031 | { 1032 | "sourceid": "93", 1033 | "intro": "酷我用户每天播放线上歌曲的飙升指数TOP排行榜,为你展示流行趋势、蹿红歌曲,每天更新", 1034 | "name": "酷我飙升榜", 1035 | "id": "489929", 1036 | "source": "2", 1037 | "pic": "http://img3.kwcdn.kuwo.cn/star/upload/4/9/1581375962.png", 1038 | "pub": "今日更新" 1039 | }, 1040 | { 1041 | "sourceid": "17", 1042 | "intro": "酷我用户每天播放新歌(一个月内发行)TOP排行榜,为你展示当下潮流新歌,每天更新", 1043 | "name": "酷我新歌榜", 1044 | "id": "489928", 1045 | "source": "2", 1046 | "pic": "http://img3.kwcdn.kuwo.cn/star/upload/6/9/1581203155.png", 1047 | "pub": "今日更新" 1048 | }, 1049 | { 1050 | "sourceid": "16", 1051 | "intro": "酷我用户每天播放线上歌曲TOP排行榜,为你展示当下最人气最热门歌曲,每天更新", 1052 | "name": "酷我热歌榜", 1053 | "id": "489927", 1054 | "source": "2", 1055 | "pic": "http://img3.kwcdn.kuwo.cn/star/upload/2/4/1581030357.png", 1056 | "pub": "今日更新" 1057 | }, 1058 | { 1059 | "sourceid": "158", 1060 | "intro": "抖音官方热歌TOP排行榜,为你展示最火最洗脑的抖音神曲,每周二更新", 1061 | "name": "抖音热歌榜", 1062 | "id": "490022", 1063 | "source": "2", 1064 | "pic": "http://img3.kwcdn.kuwo.cn/star/upload/4/2/1581375964.png", 1065 | "pub": "今日更新" 1066 | }, 1067 | { 1068 | "sourceid": "145", 1069 | "intro": "酷我音乐包歌曲TOP排行榜,为你展示最卖座的高品质无损音乐,每天更新", 1070 | "name": "会员畅听榜", 1071 | "id": "507077", 1072 | "source": "2", 1073 | "pic": "http://img3.kwcdn.kuwo.cn/star/upload/1/6/1580598361.png", 1074 | "pub": "今日更新" 1075 | } 1076 | ] 1077 | } 1078 | ``` 1079 | 1080 | #### - 排行榜音樂 1081 | 1082 | 接口:`/kuwo/rank/musicList` 1083 | 1084 | 示例: `/kuwo/rank/musicList?bangId=93&pn=1&rn=30` 1085 | 1086 | | 參數 | 説明 | 是否必須 | 示例 | 1087 | | -------- | -------- | -------- | ----------- | 1088 | | `bangId` | 榜單id | 是 | `bangId`=93 | 1089 | | `pn` | 分頁 | | 默認1 | 1090 | | `rn` | 每頁數量 | | 默認30 | 1091 | 1092 | #### - 推荐榜单 1093 | 1094 | 接口:`/kuwo/rank/rec_bangList` 1095 | 1096 | ```json 1097 | { 1098 | "leader": "酷我热歌榜", 1099 | "num": "350", 1100 | "name": "酷我热歌榜", 1101 | "pic": "http://img1.kwcdn.kuwo.cn/star/upload/12/12/1481783559612_.png", 1102 | "id": "16", 1103 | "pub": "2020-02-11", 1104 | "musicList": [8 items] 1105 | } 1106 | ``` 1107 | 1108 | 1109 | 1110 | ### 主播电台 1111 | 1112 | 接口:`/kuwo/radio` 1113 | 1114 | ```json 1115 | Tree 1116 | Chart 1117 | JSON Input 1118 | { 1119 | "code": 200, 1120 | "curTime": 1581407319418, 1121 | "data": { 1122 | "albumList": [ 1123 | { 1124 | "artist": "蕊希Erin", 1125 | "album": "蕊希电台", 1126 | "listencnt": "30933", 1127 | "pic": "http://img3.kuwo.cn/star/albumcover/300/52/43/2233596567.jpg", 1128 | "rid": "38721812" 1129 | }, 1130 | { 1131 | "artist": "由小藜", 1132 | "album": "非藜不可", 1133 | "listencnt": "145635", 1134 | "pic": "http://img3.kuwo.cn/star/albumcover/300/40/46/3747600081.jpg", 1135 | "rid": "51859818" 1136 | }, 1137 | { 1138 | "artist": "睡前一起夜听", 1139 | "album": "睡前一起夜听 ", 1140 | "listencnt": "5537", 1141 | "pic": "http://img3.kuwo.cn/star/albumcover/500/8/74/538223138.jpg", 1142 | "rid": "63807800" 1143 | }, 1144 | { 1145 | "artist": "紫云纱", 1146 | "album": "职场情报局", 1147 | "listencnt": "154056", 1148 | "pic": "http://img3.kuwo.cn/star/albumcover/300/56/78/4005148811.jpg", 1149 | "rid": "39277769" 1150 | }, 1151 | { 1152 | "artist": "北城[主播]", 1153 | "album": "南城故事", 1154 | "listencnt": "210292", 1155 | "pic": "http://img4.kuwo.cn/star/albumcover/300/47/92/186765640.jpg", 1156 | "rid": "40887125" 1157 | }, 1158 | { 1159 | "artist": "中国大百科全书出版社", 1160 | "album": "迷你百科脱口秀第二季", 1161 | "listencnt": "1741", 1162 | "pic": "http://img4.kuwo.cn/star/albumcover/300/79/11/1935561670.jpg", 1163 | "rid": "63722836" 1164 | }, 1165 | { 1166 | "artist": "喜剧听我的", 1167 | "album": "小沈阳爆笑作品汇", 1168 | "listencnt": "69499", 1169 | "pic": "http://img3.kuwo.cn/star/albumcover/300/6/0/1039562571.jpg", 1170 | "rid": "68390546" 1171 | }, 1172 | { 1173 | "artist": "凯紫", 1174 | "album": "诗词之美", 1175 | "listencnt": "27520", 1176 | "pic": "http://img4.kuwo.cn/star/albumcover/300/2/94/1939976936.jpg", 1177 | "rid": "61002763" 1178 | }, 1179 | { 1180 | "artist": "九小如", 1181 | "album": "【美文】听说-你的心情有人懂", 1182 | "listencnt": "376042", 1183 | "pic": "http://img1.kuwo.cn/star/albumcover/300/29/84/1778172532.jpg", 1184 | "rid": "41327034" 1185 | } 1186 | ] 1187 | }, 1188 | "msg": "success", 1189 | "profileId": "site", 1190 | "reqId": "cd681719285c24cb5db83494e7fbf0eb" 1191 | } 1192 | ``` 1193 | 1194 | -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | # 酷我 音乐 Api 2 | 3 | > 酷我音乐 NodeJS 版 API 4 | 5 | [GitHub](https://github.com/QiuYaohong/kuwoMusicApi.git) 6 | [Get Started](#酷我音乐API) -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qyhqiu/kuwoMusicApi/e8e720b90b4d7e3052078a3380906f2b3349e388/docs/favicon.ico -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 | 酷我音乐 NodeJS 版 API 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kuwomusicapi", 3 | "version": "1.0.0", 4 | "description": "Egg for 酷我音乐API;酷我音乐API Node.js版", 5 | "private": true, 6 | "keywords": [ 7 | "酷我音乐API Node.JS版", 8 | "音乐API", 9 | "酷我音乐API" 10 | ], 11 | "egg": { 12 | "typescript": true, 13 | "declarations": true 14 | }, 15 | "scripts": { 16 | "start": "npm run ci && egg-scripts start --port=7002 --daemon --title=egg-server-kuwoMusicApi", 17 | "stop": "egg-scripts stop --title=egg-server-kuwoMusicApi", 18 | "docs": "docsify serve ./docs", 19 | "dev": "npm run clean && egg-bin dev --port 7002", 20 | "debug": "egg-bin debug", 21 | "test-local": "egg-bin test", 22 | "test": "npm run lint -- --fix && npm run test-local", 23 | "cov": "egg-bin cov", 24 | "tsc": "ets && tsc -p tsconfig.json", 25 | "ci": "npm run lint && npm run cov && npm run tsc", 26 | "autod": "autod", 27 | "lint": "eslint . --ext .ts", 28 | "clean": "tsc -b --clean" 29 | }, 30 | "dependencies": { 31 | "docsify": "^4.13.1", 32 | "egg": "^3.17.3", 33 | "egg-cors": "^2.2.3", 34 | "egg-scripts": "^2.17.0", 35 | "fs-extra": "^11.1.1", 36 | "iconv-lite": "^0.6.3", 37 | "needle": "^3.2.0", 38 | "uuid": "^9.0.0", 39 | "zlib": "^1.0.5" 40 | }, 41 | "devDependencies": { 42 | "@eggjs/tsconfig": "^1.3.3", 43 | "@types/mocha": "^10.0.1", 44 | "@types/node": "^20.4.4", 45 | "@types/supertest": "^2.0.12", 46 | "@typescript-eslint/eslint-plugin": "^6.1.0", 47 | "autod": "^3.1.2", 48 | "autod-egg": "^1.1.0", 49 | "docsify-cli": "^4.4.4", 50 | "egg-bin": "^6.4.1", 51 | "egg-ci": "^2.2.0", 52 | "egg-mock": "^5.10.8", 53 | "eslint": "^8.45.0", 54 | "eslint-config-egg": "^12.2.1", 55 | "eslint-config-standard": "^17.1.0", 56 | "eslint-plugin-html": "^7.1.0", 57 | "eslint-plugin-jsdoc": "^46.4.4", 58 | "eslint-plugin-node": "^11.1.0", 59 | "eslint-plugin-promise": "^6.1.1", 60 | "tslib": "^2.6.0", 61 | "typescript": "^5.1.6" 62 | }, 63 | "engines": { 64 | "node": ">=8.9.0" 65 | }, 66 | "ci": { 67 | "version": "8" 68 | }, 69 | "repository": { 70 | "type": "git", 71 | "url": "https://github.com/QiuYaohong/kuwoMusicApi.git" 72 | }, 73 | "eslintIgnore": [ 74 | "coverage" 75 | ], 76 | "author": "qyhqiu", 77 | "license": "ISC" 78 | } 79 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "target": "es2017", 5 | "module": "commonjs", 6 | "strict": true, 7 | "noImplicitAny": false, 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "allowJs": false, 11 | "pretty": true, 12 | "noEmitOnError": false, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "allowUnreachableCode": false, 16 | "allowUnusedLabels": true, 17 | "strictPropertyInitialization": false, 18 | "noFallthroughCasesInSwitch": true, 19 | "skipLibCheck": true, 20 | "skipDefaultLibCheck": true, 21 | "inlineSourceMap": true, 22 | "importHelpers": true 23 | }, 24 | "extends": "@eggjs/tsconfig", 25 | "exclude": [ 26 | "app/public", 27 | "app/views", 28 | "node_modules*" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /typings/app/controller/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.34.7 2 | // Do not modify this file!!!!!!!!! 3 | /* eslint-disable */ 4 | 5 | import 'egg'; 6 | import ExportAlbumInfo from '../../../app/controller/albumInfo'; 7 | import ExportBanner from '../../../app/controller/banner'; 8 | import ExportBaseController from '../../../app/controller/BaseController'; 9 | import ExportComment from '../../../app/controller/comment'; 10 | import ExportGetTagList from '../../../app/controller/getTagList'; 11 | import ExportLrc from '../../../app/controller/lrc'; 12 | import ExportMusicInfo from '../../../app/controller/musicInfo'; 13 | import ExportMusicList from '../../../app/controller/musicList'; 14 | import ExportMv from '../../../app/controller/mv'; 15 | import ExportPlayList from '../../../app/controller/playList'; 16 | import ExportPlayUrl from '../../../app/controller/playUrl'; 17 | import ExportRadio from '../../../app/controller/radio'; 18 | import ExportRank from '../../../app/controller/rank'; 19 | import ExportRecGedan from '../../../app/controller/recGedan'; 20 | import ExportRecSinger from '../../../app/controller/recSinger'; 21 | import ExportSearch from '../../../app/controller/search'; 22 | import ExportSinger from '../../../app/controller/singer'; 23 | 24 | declare module 'egg' { 25 | interface IController { 26 | albumInfo: ExportAlbumInfo; 27 | banner: ExportBanner; 28 | baseController: ExportBaseController; 29 | comment: ExportComment; 30 | getTagList: ExportGetTagList; 31 | lrc: ExportLrc; 32 | musicInfo: ExportMusicInfo; 33 | musicList: ExportMusicList; 34 | mv: ExportMv; 35 | playList: ExportPlayList; 36 | playUrl: ExportPlayUrl; 37 | radio: ExportRadio; 38 | rank: ExportRank; 39 | recGedan: ExportRecGedan; 40 | recSinger: ExportRecSinger; 41 | search: ExportSearch; 42 | singer: ExportSinger; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /typings/app/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.34.7 2 | // Do not modify this file!!!!!!!!! 3 | /* eslint-disable */ 4 | 5 | import 'egg'; 6 | export * from 'egg'; 7 | export as namespace Egg; 8 | -------------------------------------------------------------------------------- /typings/app/service/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.34.7 2 | // Do not modify this file!!!!!!!!! 3 | /* eslint-disable */ 4 | 5 | import 'egg'; 6 | type AnyClass = new (...args: any[]) => any; 7 | type AnyFunc = (...args: any[]) => T; 8 | type CanExportFunc = AnyFunc> | AnyFunc>; 9 | type AutoInstanceType : T> = U extends AnyClass ? InstanceType : U; 10 | import ExportAlbumInfo from '../../../app/service/albumInfo'; 11 | import ExportBanner from '../../../app/service/banner'; 12 | import ExportBaseService from '../../../app/service/BaseService'; 13 | import ExportComment from '../../../app/service/comment'; 14 | import ExportGetTagList from '../../../app/service/getTagList'; 15 | import ExportLrc from '../../../app/service/lrc'; 16 | import ExportMusicInfo from '../../../app/service/musicInfo'; 17 | import ExportMusicList from '../../../app/service/musicList'; 18 | import ExportMv from '../../../app/service/mv'; 19 | import ExportPlayList from '../../../app/service/playList'; 20 | import ExportPlayUrl from '../../../app/service/playUrl'; 21 | import ExportRadio from '../../../app/service/radio'; 22 | import ExportRank from '../../../app/service/rank'; 23 | import ExportRecGedan from '../../../app/service/recGedan'; 24 | import ExportRecSinger from '../../../app/service/recSinger'; 25 | import ExportSearch from '../../../app/service/search'; 26 | import ExportSinger from '../../../app/service/singer'; 27 | 28 | declare module 'egg' { 29 | interface IService { 30 | albumInfo: AutoInstanceType; 31 | banner: AutoInstanceType; 32 | baseService: AutoInstanceType; 33 | comment: AutoInstanceType; 34 | getTagList: AutoInstanceType; 35 | lrc: AutoInstanceType; 36 | musicInfo: AutoInstanceType; 37 | musicList: AutoInstanceType; 38 | mv: AutoInstanceType; 39 | playList: AutoInstanceType; 40 | playUrl: AutoInstanceType; 41 | radio: AutoInstanceType; 42 | rank: AutoInstanceType; 43 | recGedan: AutoInstanceType; 44 | recSinger: AutoInstanceType; 45 | search: AutoInstanceType; 46 | singer: AutoInstanceType; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /typings/config/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.34.7 2 | // Do not modify this file!!!!!!!!! 3 | /* eslint-disable */ 4 | 5 | import 'egg'; 6 | import { EggAppConfig } from 'egg'; 7 | import ExportConfigDefault from '../../config/config.default'; 8 | type ConfigDefault = ReturnType; 9 | type NewEggAppConfig = ConfigDefault; 10 | declare module 'egg' { 11 | interface EggAppConfig extends NewEggAppConfig { } 12 | } -------------------------------------------------------------------------------- /typings/config/plugin.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.34.7 2 | // Do not modify this file!!!!!!!!! 3 | /* eslint-disable */ 4 | 5 | import 'egg'; 6 | import 'egg-onerror'; 7 | import 'egg-session'; 8 | import 'egg-i18n'; 9 | import 'egg-watcher'; 10 | import 'egg-multipart'; 11 | import 'egg-security'; 12 | import 'egg-development'; 13 | import 'egg-logrotator'; 14 | import 'egg-schedule'; 15 | import 'egg-static'; 16 | import 'egg-jsonp'; 17 | import 'egg-view'; 18 | import 'egg-cors'; 19 | import { EggPluginItem } from 'egg'; 20 | declare module 'egg' { 21 | interface EggPlugin { 22 | onerror?: EggPluginItem; 23 | session?: EggPluginItem; 24 | i18n?: EggPluginItem; 25 | watcher?: EggPluginItem; 26 | multipart?: EggPluginItem; 27 | security?: EggPluginItem; 28 | development?: EggPluginItem; 29 | logrotator?: EggPluginItem; 30 | schedule?: EggPluginItem; 31 | static?: EggPluginItem; 32 | jsonp?: EggPluginItem; 33 | view?: EggPluginItem; 34 | cors?: EggPluginItem; 35 | } 36 | } -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | import 'egg'; 2 | 3 | declare module 'egg' { 4 | 5 | } --------------------------------------------------------------------------------