├── .dockerignore ├── .env.example ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .prettierrc.json ├── Dockerfile ├── LICENSE ├── README.md ├── app.js ├── docs ├── .nojekyll ├── README.md ├── _coverpage.md └── index.html ├── index.js ├── interface.d.ts ├── main.js ├── module ├── .gitignore ├── ai_recommend.js ├── album.js ├── album_detail.js ├── album_shop.js ├── album_songs.js ├── artist_albums.js ├── artist_audios.js ├── artist_detail.js ├── artist_follow.js ├── artist_follow_newsongs.js ├── artist_honour.js ├── artist_lists.js ├── artist_unfollow.js ├── artist_videos.js ├── audio.js ├── audio_accompany_matching.js ├── audio_ktv_total.js ├── audio_related.js ├── brush.js ├── captcha_sent.js ├── comment_album.js ├── comment_count.js ├── comment_floor.js ├── comment_music.js ├── comment_music_classify.js ├── comment_music_hotword.js ├── comment_playlist.js ├── everyday_friend.js ├── everyday_history.js ├── everyday_recommend.js ├── everyday_style_recommend.js ├── favorite_count.js ├── fm_class.js ├── fm_image.js ├── fm_recommend.js ├── fm_songs.js ├── images.js ├── images_audio.js ├── ip.js ├── ip_dateil.js ├── ip_playlist.js ├── ip_zone.js ├── ip_zone_home.js ├── kmr_audio_mv.js ├── krm_audio.js ├── lastest_songs_listen.js ├── login.js ├── login_cellphone.js ├── login_openplat.js ├── login_qr_check.js ├── login_qr_create.js ├── login_qr_key.js ├── login_token.js ├── login_wx_check.js ├── login_wx_create.js ├── longaudio_album_audios.js ├── longaudio_album_detail.js ├── longaudio_daily_recommend.js ├── longaudio_rank_recommend.js ├── longaudio_vip_recommend.js ├── longaudio_week_recommend.js ├── lyric.js ├── pc_diantai.js ├── personal_fm.js ├── playhistory_upload.js ├── playlist_add.js ├── playlist_del.js ├── playlist_detail.js ├── playlist_effect.js ├── playlist_similar.js ├── playlist_tags.js ├── playlist_track_all.js ├── playlist_track_all_new.js ├── playlist_tracks_add.js ├── playlist_tracks_del.js ├── privilege_lite.js ├── rank_audio.js ├── rank_info.js ├── rank_list.js ├── rank_top.js ├── rank_vol.js ├── recommend_songs.js ├── register_dev.js ├── scene_audio_list.js ├── scene_collection_list.js ├── scene_lists.js ├── scene_lists_v2.js ├── scene_module.js ├── scene_module_info.js ├── scene_music.js ├── scene_video_list.js ├── search.js ├── search_complex.js ├── search_default.js ├── search_hot.js ├── search_lyric.js ├── search_mixed.js ├── search_suggest.js ├── server_now.js ├── sheet_collection.js ├── sheet_collection_detail.js ├── sheet_detail.js ├── sheet_hot.js ├── sheet_list.js ├── singer_list.js ├── song_climax.js ├── song_ranking.js ├── song_ranking_filter.js ├── song_url.js ├── song_url_new.js ├── theme_music.js ├── theme_music_detail.js ├── theme_playlist.js ├── theme_playlist_track.js ├── top_album.js ├── top_card.js ├── top_ip.js ├── top_playlist.js ├── top_song.js ├── user_cloud.js ├── user_cloud_url.js ├── user_detail.js ├── user_follow.js ├── user_history.js ├── user_listen.js ├── user_playlist.js ├── user_video_collect.js ├── user_video_love.js ├── user_vip_detail.js ├── video_detail.js ├── video_privilege.js ├── video_url.js ├── youth_channel_all.js ├── youth_channel_amway.js ├── youth_channel_detail.js ├── youth_channel_similar.js ├── youth_channel_song.js ├── youth_channel_song_detail.js ├── youth_channel_sub.js ├── youth_day_vip.js ├── youth_day_vip_upgrade.js ├── youth_dynamic.js ├── youth_dynamic_recent.js ├── youth_listen_song.js ├── youth_month_vip_record.js ├── youth_union_vip.js ├── youth_user_song.js ├── youth_vip.js ├── yueku.js ├── yueku_banner.js └── yueku_fm.js ├── nodemon.json ├── package.json ├── pnpm-lock.yaml ├── public ├── .gitignore └── index.html ├── server.js ├── tsconfig.json ├── util ├── apicache.js ├── config.json ├── crypto.js ├── helper.js ├── index.js ├── memory-cache.js ├── request.js └── util.js └── vercel.json /.dockerignore: -------------------------------------------------------------------------------- 1 | /** 2 | !/module 3 | !/plugins 4 | !/public 5 | !/static 6 | !/util 7 | !/app.js 8 | !/server.js 9 | !/package.json 10 | !/package-lock.json 11 | !/index.js 12 | !/main.js -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | ## 配置设备平台 2 | ### lite: 酷狗概念版, 默认为手机版 3 | platform='' 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # 发行版本部署 2 | ## 多端部署 3 | name: Build Release 4 | 5 | ## 权限配置 6 | permissions: 7 | contents: write 8 | 9 | on: 10 | push: 11 | tags: 12 | - v* 13 | 14 | jobs: 15 | # window 端打包 16 | build-windows: 17 | name: Build for Windows 18 | runs-on: windows-latest 19 | timeout-minutes: 30 20 | steps: 21 | # 检出 Git 仓库 22 | - name: Check out Git repository 23 | uses: actions/checkout@v4 24 | # 安装 Node.js 25 | - name: Install Node.js 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: '18.x' 29 | # 安装项目依赖 30 | - name: Install Dependencies 31 | run: npm install 32 | # 构建 33 | - name: Build App for Windows 34 | run: npm run pkgwin 35 | shell: bash 36 | # 上传构建产物 37 | - name: Upload Windows artifact 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: app-win 41 | if-no-files-found: ignore 42 | path: | 43 | bin/*.exe 44 | #创建 GitHub Release 45 | - name: Create Release 46 | uses: softprops/action-gh-release@v2 47 | if: startsWith(github.ref, 'refs/tags/v') 48 | with: 49 | token: ${{ secrets.GITHUB_TOKEN}} 50 | draft: false 51 | prerelease: false 52 | files: bin/*.exe 53 | env: 54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 55 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN}} 56 | 57 | # mac 端打包 58 | build-masos: 59 | name: Build for MacOS 60 | runs-on: macos-latest 61 | timeout-minutes: 30 62 | steps: 63 | # 检出 Git 仓库 64 | - name: Check out Git repository 65 | uses: actions/checkout@v4 66 | # 安装 Node.js 67 | - name: Install Node.js 68 | uses: actions/setup-node@v4 69 | with: 70 | node-version: '18.x' 71 | # 安装项目依赖 72 | - name: Install Dependencies 73 | run: npm install 74 | # 构建 75 | - name: Build App for Macos 76 | run: npm run pkgmacos 77 | shell: bash 78 | # 上传构建产物 79 | - name: Upload Macos artifact 80 | uses: actions/upload-artifact@v4 81 | with: 82 | name: app-macos 83 | if-no-files-found: ignore 84 | path: | 85 | bin/* 86 | #创建 GitHub Release 87 | - name: Create Release 88 | uses: softprops/action-gh-release@v2 89 | if: startsWith(github.ref, 'refs/tags/v') 90 | with: 91 | token: ${{ secrets.GITHUB_TOKEN}} 92 | draft: false 93 | prerelease: false 94 | files: bin/* 95 | env: 96 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 97 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN}} 98 | 99 | # Linux 端打包 100 | build-linux: 101 | name: Build for Linux 102 | runs-on: ubuntu-22.04 103 | timeout-minutes: 30 104 | steps: 105 | # 检出 Git 仓库 106 | - name: Check out Git repository 107 | uses: actions/checkout@v4 108 | # 安装 Node.js 109 | - name: Install Node.js 110 | uses: actions/setup-node@v4 111 | with: 112 | node-version: '18.x' 113 | # 更新 Ubuntu 软件源 114 | - name: Ubuntu Update with sudo 115 | run: sudo apt-get update 116 | # 安装 RPM 和 Pacman 插件 117 | - name: Install RPM & Pacman (on Ubuntu) 118 | run: | 119 | sudo apt-get install --no-install-recommends -y rpm && 120 | sudo apt-get install --no-install-recommends -y libarchive-tools && 121 | sudo apt-get install --no-install-recommends -y libopenjp2-tools 122 | # 安装项目依赖 123 | - name: Install Dependencies 124 | run: npm install 125 | # 构建 126 | - name: Build App for Linux 127 | run: npm run pkglinux 128 | shell: bash 129 | # 上传构建产物 130 | - name: Upload Linux artifact 131 | uses: actions/upload-artifact@v4 132 | with: 133 | name: app-linux 134 | if-no-files-found: ignore 135 | path: | 136 | bin/* 137 | #创建 GitHub Release 138 | - name: Create Release 139 | uses: softprops/action-gh-release@v2 140 | if: startsWith(github.ref, 'refs/tags/v') 141 | with: 142 | token: ${{ secrets.GITHUB_TOKEN}} 143 | draft: false 144 | prerelease: false 145 | files: bin/* 146 | env: 147 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 148 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN}} 149 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | test 15 | bin/* 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | .env -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSameLine": false, 4 | "bracketSpacing": true, 5 | "embeddedLanguageFormatting": "auto", 6 | "htmlWhitespaceSensitivity": "css", 7 | "insertPragma": false, 8 | "jsxSingleQuote": false, 9 | "printWidth": 150, 10 | "proseWrap": "always", 11 | "quoteProps": "preserve", 12 | "requirePragma": false, 13 | "semi": true, 14 | "singleQuote": true, 15 | "tabWidth": 2, 16 | "trailingComma": "es5", 17 | "useTabs": false, 18 | "vueIndentScriptAndStyle": false 19 | } 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine 2 | 3 | RUN apk add --no-cache tini 4 | 5 | ENV NODE_ENV production 6 | 7 | USER node 8 | 9 | WORKDIR /app 10 | 11 | COPY --chown=node:node . ./ 12 | 13 | RUN yarn --network-timeout=100000 14 | 15 | EXPOSE 3000 16 | 17 | CMD [ "/sbin/tini", "--", "node", "app.js" ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 MakcRe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to 6 | deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 12 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 13 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 14 | IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KuGouMusic API 2 | 3 | 酷狗音乐 NodeJS 版 API 4 | 5 | [![](https://img.shields.io/badge/Author-MakcRe-blueviolet?style=for-the-badge '作者')](https://github.com/MakcRe) 6 | ![](https://img.shields.io/badge/dynamic/json?label=GitHub%20Followers&style=for-the-badge&query=%24.data.totalSubs&url=https%3A%2F%2Fapi.spencerwoo.com%2Fsubstats%2F%3Fsource%3Dgithub%26queryKey%3DMakcRe&labelColor=282c34&color=181717&logo=github&longCache=true '关注数量') 7 | ![](https://img.shields.io/github/stars/MakcRe/KuGouMusicApi.svg?style=for-the-badge&label=Star 'Star数量') 8 | ![](https://img.shields.io/github/forks/MakcRe/KuGouMusicApi.svg?style=for-the-badge&label=Fork 'Fork数量') 9 | ![](https://img.shields.io/github/issues/MakcRe/KuGouMusicApi.svg?style=for-the-badge&label=Issues 'Issues数量') 10 | ![](https://img.shields.io/github/contributors/MakcRe/KuGouMusicApi?style=for-the-badge '贡献者') 11 | ![](https://img.shields.io/github/repo-size/MakcRe/KuGouMusicApi?style=for-the-badge&label=files&color=cf8ef4&labelColor=373e4dl '文件大小') 12 | ![](https://img.shields.io/github/languages/code-size/MakcRe/KuGouMusicApi?color=blueviolet&style=for-the-badge '代码大小') 13 | 14 | [//]: # '
' 15 | 16 | ![](https://img.shields.io/github/package-json/v/MakcRe/KuGouMusicApi?longCache=true&style=for-the-badge) 17 | ![](https://img.shields.io/badge/Node-12+-green.svg?longCache=true&style=for-the-badge) 18 | ![](https://img.shields.io/badge/License-mit-blue.svg?longCache=true&style=for-the-badge) 19 | 20 | ## 灵感来自 21 | 22 | [Binaryify/NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi) 23 | 24 | ## 环境要求 25 | 26 | 需要 NodeJS 12+ 环境 27 | 28 | ## 工作原理 29 | 30 | 跨站请求伪造 (CSRF), 伪造请求头 , 调用官方 API 31 | 32 | ## 免责声明 33 | 34 | > 1. 本项目仅供学习使用,请尊重版权,请勿利用此项目从事商业行为及非法用途! 35 | > 2. 使用本项目的过程中可能会产生版权数据。对于这些版权数据,本项目不拥有它们的所有权。为了避免侵权,使用者务必在 24 小时内清除使用本项目的过程中所产 36 | > 生的版权数据。 37 | > 3. 由于使用本项目产生的包括由于本协议或由于使用或无法使用本项目而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停 38 | > 工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。 39 | > 4. **禁止在违反当地法律法规的情况下使用本项目。** 对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本 40 | > 项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。 41 | > 5. 音乐平台不易,请尊重版权,支持正版。 42 | > 6. 本项目仅用于对技术可行性的探索及研究,不接受任何商业(包括但不限于广告等)合作及捐赠。 43 | > 7. 如果官方音乐平台觉得本项目不妥,可联系本项目更改或移除。 44 | 45 | ### 安装 46 | 47 | ```shell 48 | $ git clone git@github.com:MakcRe/KuGouMusicApi.git 49 | $ cd KuGouMusicApi 50 | $ npm install 51 | ``` 52 | 53 | ### 使用接口为概念版 54 | 55 | ``` 56 | $ 复制 .env.example 为 .env,并且把里面的 `platform=''` 改为 `platform=lite` 57 | $ 注意不同版本的平台的 token 是不通用的。 58 | ``` 59 | 60 | ### 运行 61 | 62 | ```shell 63 | $ npm run dev 64 | ``` 65 | 66 | 服务器启动默认端口为 3000, 若不想使用 3000 端口 , 可使用以下命令 : Mac/Linux 67 | 68 | ```shell 69 | $ PORT=4000 npm run dev 70 | ``` 71 | 72 | windows 下使用 git-bash 或者 cmder 等终端执行以下命令 : 73 | 74 | ```shell 75 | $ set PORT=4000 && npm run dev 76 | ``` 77 | 78 | windows 下使用 PowerShell 等终端执行以下命令 : 79 | 80 | ```shell 81 | $ $Env:PORT=4000; npm run dev 82 | ``` 83 | 84 | 服务器启动默认 host 为 localhost,如果需要更改, 可使用以下命令 : Mac/Linux 85 | 86 | ```shell 87 | $ HOST=127.0.0.1 npm run dev 88 | ``` 89 | 90 | windows 下使用 git-bash 或者 cmder 等终端执行以下命令 : 91 | 92 | ```shell 93 | $ set HOST=127.0.0.1 && npm run dev 94 | ``` 95 | 96 | windows 下使用 PowerShell 等终端执行以下命令 : 97 | 98 | ```shell 99 | $ $Env:HOST=127.0.0.1; npm run dev 100 | ``` 101 | 102 | ## Vercel 部署 103 | 104 | ### 操作方法 105 | 106 | 1. fork 此项目 107 | 2. 在 Vercel 官网点击 `New Project` 108 | 3. 点击 `Import Git Repository` 并选择你 fork 的此项目并点击 `import` 109 | 4. 点击 `PERSONAL ACCOUNT` 的 `select` 110 | 5. 直接点 `Continue` 111 | 6. 若需要部署版本为概念版(不需要该步骤可以跳过),在 `Environment Variables` 添加 `key` 为 `platform`,`Value (Will Be Encrypted)` 为 `lite` 然后点击 112 | `Add` 113 | 7. `PROJECT NAME`自己填,`FRAMEWORK PRESET` 选 `Other` 然后直接点 `Deploy` 接着等部署完成即可 114 | 115 | ## 功能特性 116 | 117 | 1. 登录 118 | 2. 刷新登录 119 | 3. 发送验证码 120 | 4. dfid 获取 121 | 5. 获取用户额外信息 122 | 6. 获取用户 vip 信息 123 | 7. 获取用户歌单 124 | 8. 获取用户关注歌手 125 | 9. 获取用户听歌历史排行 126 | 10. 获取用户最近听歌历史 127 | 11. 获取继续播放信息(对应手机版首页显示继续播放入口) 128 | 12. 收藏歌单/新建歌单 129 | 13. 取消收藏歌单/删除歌单 130 | 14. 对歌单添加歌曲 131 | 15. 对歌单删除歌曲 132 | 16. 新碟上架 133 | 17. 专辑信息 134 | 18. 专辑详情 135 | 19. 专辑音乐列表 136 | 20. 获取音乐 URL 137 | 21. 获取歌曲高潮部分 138 | 22. 搜索 139 | 23. 默认搜索关键词 140 | 24. 综合搜索 141 | 25. 热搜列表 142 | 26. 搜索建议 143 | 27. 歌词搜索 144 | 28. 获取歌词 145 | 29. 歌单分类 146 | 30. 歌单 147 | 31. 主题歌单 148 | 32. 音效歌单 149 | 33. 获取歌单详情 150 | 34. 获取歌单所有歌曲 151 | 35. 获取歌单所有歌曲(新版) 152 | 36. 相似歌单 153 | 37. 获取主题歌单所有歌曲 154 | 38. 获取主题音乐 155 | 39. 获取主题音乐详情 156 | 40. 歌曲推荐 157 | 41. 获取歌手和专辑图片 158 | 42. 获取歌手图片 159 | 43. 获取音乐相关信息 160 | 44. 获取更多音乐版本 161 | 45. 获取音乐伴奏信息 162 | 46. 获取音乐 k 歌数量 163 | 47. 获取音乐详情 164 | 48. 获取音乐专辑/歌手信息 165 | 49. 私人 FM(对应手机和 pc 端的猜你喜欢) 166 | 50. banner 167 | 51. 乐库 banner 168 | 52. 乐库电台 169 | 53. 乐库 170 | 54. 电台 - 推荐 171 | 55. 电台 172 | 56. 电台 - 图片 173 | 57. 电台 - 音乐列表 174 | 58. 编辑精选 175 | 59. 编辑精选数据 176 | 60. 编辑精选歌单 177 | 61. 编辑精选专区 178 | 62. 编辑精选专区详情 179 | 63. 领取 VIP(需要登陆,该接口为测试接口) 180 | 64. 获取歌手列表 181 | 65. 获取歌手详情 182 | 66. 获取歌手专辑 183 | 67. 获取歌手单曲 184 | 68. 获取歌手 MV 185 | 69. 关注歌手 186 | 70. 取消关注歌手 187 | 71. 获取关注歌手新歌 188 | 72. 获取视频 url 189 | 73. 获取歌曲 MV 190 | 74. 获取视频相关信息 191 | 75. 获取视频详情 192 | 76. 新歌速递 193 | 77. 场景音乐列表 194 | 78. 场景音乐详情 195 | 79. 获取场景音乐讨论区 196 | 80. 获取场景音乐模块 Tag 197 | 81. 获取场景音乐歌单列表 198 | 82. 获取场景音乐视频列表 199 | 83. 获取场景音乐音乐列表 200 | 84. 每日推荐 201 | 85. 历史推荐 202 | 86. 风格推荐 203 | 87. 排行列表 204 | 88. 排行榜推荐列表 205 | 89. 排行榜往期列表 206 | 90. 排行榜信息 207 | 91. 排行榜歌曲列表 208 | 92. 歌曲评论 209 | 93. 歌曲评论-根据分类返回 210 | 94. 歌曲评论-根据热词返回 211 | 95. 楼层评论 212 | 96. 歌单评论 213 | 97. 专辑评论 214 | 98. 歌曲曲谱 215 | 99. 曲谱详情 216 | 100. 推荐曲谱 217 | 101. 曲谱合集 218 | 102. 曲谱合集详情 219 | 103. 提交听歌历史 220 | 104. 获取服务器时间 221 | 105. 刷刷 222 | 106. AI 推荐 223 | 107. 频道 - 获取用户所有频道 224 | 108. 频道 - 详情 225 | 109. 频道 - 频道安利 226 | 110. 频道 - 相似频道 227 | 111. 频道 - 订阅 228 | 112. 频道 - 音乐故事 229 | 113. 频道 - 音乐故事详情 230 | 114. 动态 - 最常访问 231 | 115. 获取用户公开的音乐 232 | 116. 听书 - 每日推荐 233 | 117. 听书 - 排行榜推荐 234 | 118. 听书 - VIP 推荐 235 | 119. 听书 - 每周推荐 236 | 120. 听书 - 专辑详情 237 | 121. 听书 - 专辑音乐列表 238 | 122. 歌曲详情 - 歌曲成绩单 239 | 123. 歌曲详情 - 歌曲成绩单详情 240 | 241 | ## License 242 | 243 | [The MIT License (MIT)](https://github.com/MakcRe/KuGouMusicApi/blob/main/LICENSE) 244 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // import { startService } from './server'; 3 | async function start() { 4 | // await startService(); 5 | require('./server').startService(); 6 | } 7 | 8 | start(); 9 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakcRe/KuGouMusicApi/fa335a13877c9ad6ed219fd3a1feb203f8b6bf70/docs/.nojekyll -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | # 酷狗音乐 API 2 | 3 | > 酷狗音乐 NodeJS 版 API 4 | 5 | - 全部接口已升级到最新 6 | - 具备登录接口 7 | - 更完善的文档 8 | 9 | [GitHub](https://github.com/MakcRe/KuGouMusicApi) 10 | [Get Started](#kugoumusic-api) 11 | 12 | ![color](#fff) -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 酷狗音乐 NodeJS 版 API 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./app'); 2 | -------------------------------------------------------------------------------- /interface.d.ts: -------------------------------------------------------------------------------- 1 | export type BufferLike = Buffer | string | Object | Record; 2 | 3 | export type ExpressExtension = { server?: import('http').Server }; 4 | 5 | export type UseAxios = (config: UseAxiosRequestConfig) => Promise; 6 | export type UseModuleParams = Record & { cookie?: Record }; 7 | export type UseModule = (req: UseModuleParams, useAxios: UseAxios) => Promise; 8 | 9 | export type ModuleDefinition = { identifier?: string; route: string; module: UseModule }; 10 | export type EncryptType = 'android' | 'web' | 'register'; 11 | export interface UseAxiosRequestConfig { 12 | method: 'get' | 'GET' | 'post' | 'POST'; 13 | url: string; 14 | baseURL?: string; 15 | params?: T; 16 | data?: T; 17 | headers?: Record; 18 | cookie?: { [key: string]: string | number }; 19 | encryptType: EncryptType; 20 | encryptKey?: boolean; 21 | clearDefaultParams?: boolean; 22 | ip?: string; 23 | realIP?: string; 24 | } 25 | 26 | export type APIBaseResponse = { data: any; errcode: number; status: number; error: string; [index: string]: unknown }; 27 | 28 | export interface UseAxiosResponse { 29 | status: number; 30 | body: T; 31 | cookie: string[]; 32 | headers?: Record; 33 | } 34 | 35 | export type RequestBaseConfig = { cookie?: { [key: string]: string | number }; realIP?: string; proxy?: string }; 36 | 37 | export type MultiPageConfig = { page?: number | string; pagesize?: number | string }; 38 | 39 | export function startService(): Promise; 40 | 41 | export function getModulesDefinitions(modulesPath: string, specificRoute: Record, doRequire?: boolean): Promise; 42 | 43 | export function createRequest(params: UseAxiosRequestConfig): Promise; 44 | 45 | // API 46 | export function login_cellphone(params: { mobile: number; code: number | string } & RequestBaseConfig): Promise; 47 | 48 | export function login(params: { username: string; password: string } & RequestBaseConfig): Promise; 49 | 50 | export function login_openplat(params: { code: string } & RequestBaseConfig): Promise; 51 | 52 | export function login_qr_key(params?: RequestBaseConfig): Promise; 53 | 54 | export function login_qr_create(parmas: { key: string; qrimg?: boolean } & RequestBaseConfig): Promise; 55 | 56 | export function login_qr_check(params: { key: string } & RequestBaseConfig): Promise; 57 | 58 | export function login_wx_create(params?: RequestBaseConfig): Promise; 59 | 60 | export function login_wx_check(params: { uuid: string; timestamp?: string | number } & RequestBaseConfig): Promise; 61 | 62 | export function login_token(params: { token: string; userid: string | number } & RequestBaseConfig): Promise; 63 | 64 | export function captcha_sent(params: { mobile: string | number } & RequestBaseConfig): Promise; 65 | 66 | export function register_dev(params?: RequestBaseConfig): Promise; 67 | 68 | export function user_detail(params?: RequestBaseConfig): Promise; 69 | 70 | export function user_playlist(params?: RequestBaseConfig & MultiPageConfig): Promise; 71 | 72 | export function user_follow(params?: RequestBaseConfig): Promise; 73 | 74 | export function user_listen(params?: { type?: 0 | 1 } & RequestBaseConfig): Promise; 75 | 76 | export function user_history(params?: { bp?: string } & RequestBaseConfig): Promise; 77 | 78 | export enum PlaylistAdd { 79 | Add = 0, 80 | Collect = 1, 81 | } 82 | 83 | export function playlist_add( 84 | params: { 85 | name: string; 86 | list_create_userid: string | number; 87 | list_create_listid: number | string; 88 | type: PlaylistAdd.Collect; 89 | list_create_gid: string; 90 | } & RequestBaseConfig 91 | ): Promise; 92 | 93 | export function playlist_add( 94 | params: { 95 | name: string; 96 | list_create_userid: string | number; 97 | list_create_listid: number | string; 98 | type: PlaylistAdd.Add; 99 | is_pri?: 1 | 0; 100 | } & RequestBaseConfig 101 | ): Promise; 102 | 103 | export function playlist_del(params: { listid: number | string } & RequestBaseConfig): Promise; 104 | 105 | export function playlist_tracks_add(params: { listid: number | string; data: string } & RequestBaseConfig): Promise; 106 | 107 | export function playlist_tracks_del(params: { listid: number | string; fileids: string } & RequestBaseConfig): Promise; 108 | 109 | export enum TopAlbum { 110 | ZH = 1, 111 | EA = 2, 112 | JP = 3, 113 | KR = 4, 114 | } 115 | 116 | export function top_album(params?: { type?: TopAlbum } & MultiPageConfig & RequestBaseConfig): Promise; 117 | 118 | export function album(params: { album_id: string; fields?: string } & RequestBaseConfig): Promise; 119 | 120 | export function album_detail(params: { id: string | number } & RequestBaseConfig): Promise; 121 | 122 | export function album_songs(params: { id: string | number } & MultiPageConfig & RequestBaseConfig): Promise; 123 | 124 | export enum SongURLQuality { 125 | Piano = 'piano', 126 | Acappella = 'acappella', 127 | Subwoofer = 'subwoofer', 128 | Ancient = 'ancient', 129 | Surnay = 'surnay', 130 | DJ = 'dj', 131 | 128 = '128', 132 | 320 = '320', 133 | Flac = 'flac', 134 | HiRes = 'high', 135 | ViperAtmos = 'viper_atmos', 136 | ViperClear = 'viper_clear', 137 | ViperTape = 'viper_tape', 138 | } 139 | 140 | export function song_url( 141 | params: { 142 | hash: string; 143 | album_id?: number | string; 144 | free_part?: boolean; 145 | album_audio_id?: number | string; 146 | quality?: SongURLQuality; 147 | } & RequestBaseConfig 148 | ): Promise; 149 | 150 | export function song_climax(params: { hash: string } & RequestBaseConfig): Promise; 151 | 152 | export enum SearchType { 153 | Special = 'special', 154 | Lyric = 'lyric', 155 | Song = 'song', 156 | Album = 'album', 157 | Author = 'author', 158 | MV = 'mv', 159 | } 160 | 161 | export function search(params: { keyword: string; type?: SearchType } & MultiPageConfig & RequestBaseConfig): Promise; 162 | 163 | export function search_default(params?: RequestBaseConfig): Promise; 164 | 165 | export function search_complex(params: { keyword: string } & MultiPageConfig & RequestBaseConfig): Promise; 166 | 167 | export function search_hot(params?: RequestBaseConfig): Promise; 168 | 169 | export function search_lyric(params: { keyword: string; album_audio_id?: string | number } & RequestBaseConfig): Promise; 170 | 171 | export function search_lyric(params: { hash: string; album_audio_id?: string | number } & RequestBaseConfig): Promise; 172 | 173 | export function lyric( 174 | params: { id: string | number; accesskey: string; fmt?: 'lrc' | 'krc'; decode?: boolean } & RequestBaseConfig 175 | ): Promise; 176 | 177 | export function playlist_tags(params?: RequestBaseConfig): Promise; 178 | 179 | export enum TopPlaylistCategory { 180 | Recommend = 0, 181 | HIRES = 11292, 182 | } 183 | 184 | export function top_playlist( 185 | params: { category_id: TopPlaylistCategory | number | string; withsong?: 0 | 1; withtag?: 0 | 1 } & RequestBaseConfig 186 | ): Promise; 187 | 188 | export function theme_playlist(params?: RequestBaseConfig): Promise; 189 | 190 | export function playlist_effect(params?: RequestBaseConfig): Promise; 191 | 192 | export function playlist_detail(params: { ids: string } & RequestBaseConfig): Promise; 193 | 194 | export function playlist_track_all(params: { id: string } & MultiPageConfig & RequestBaseConfig): Promise; 195 | 196 | export function playlist_track_all_new(params: { lisdid: string | number } & MultiPageConfig & RequestBaseConfig): Promise; 197 | 198 | export function playlist_similar(params: { ids: string } & RequestBaseConfig): Promise; 199 | 200 | export function theme_playlist_track(params: { theme_id: string | number } & RequestBaseConfig): Promise; 201 | 202 | export function theme_music(params?: RequestBaseConfig): Promise; -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs'); 2 | const path = require('path'); 3 | const { cookieToJson } = require('./util'); 4 | 5 | /** @type {Record} */ 6 | let obj = {}; 7 | 8 | fs.readdirSync(path.join(__dirname, 'module')) 9 | .reverse() 10 | .forEach((file) => { 11 | if (!file.endsWith('.js')) return; 12 | let fileModule = require(path.join(__dirname, 'module', file)); 13 | let fn = file.split('.').shift() || ''; 14 | obj[fn] = (data = {}) => { 15 | if (typeof data.cookie === 'string') data.cookie = cookieToJson(data.cookie); 16 | return fileModule({ ...data, cookie: data.cookie ? data.cookie : {} }, (...args) => { 17 | const { createRequest } = require('./util/request'); 18 | return createRequest(...args); 19 | }); 20 | }; 21 | }); 22 | 23 | /** 24 | * @type {Record & import("./server")} 25 | */ 26 | 27 | module.exports = { ...require('./server'), ...require('./util/request'), ...obj }; 28 | -------------------------------------------------------------------------------- /module/.gitignore: -------------------------------------------------------------------------------- 1 | audio_match.js -------------------------------------------------------------------------------- /module/ai_recommend.js: -------------------------------------------------------------------------------- 1 | const { appid, clientver, signParamsKey, cryptoMd5 } = require('../util'); 2 | 3 | module.exports = (params, useAxios) => { 4 | const userid = params?.userid || params?.cookie?.userid || 0; 5 | const dfid = params?.dfid || params?.cookie?.dfid || '-'; // 自定义 6 | const mid = cryptoMd5(dfid); // 可以自定义 7 | const clienttime = Date.now(); 8 | 9 | const recommend_source = (params?.album_audio_id || '').split(',').map((s) => ({ ID: Number(s) })); 10 | 11 | const dataMap = { 12 | platform: 'ios', 13 | clientver, 14 | clienttime: clienttime, 15 | userid, 16 | client_playlist: [], 17 | source_type: 2, 18 | playlist_ver: 2, 19 | area_code: 1, 20 | appid, 21 | key: signParamsKey(clienttime.toString()), 22 | mid, 23 | recommend_source, 24 | }; 25 | 26 | return useAxios({ 27 | url: '/recommend', 28 | data: dataMap, 29 | method: 'post', 30 | encryptType: 'android', 31 | cookie: params?.cookie || {}, 32 | headers: { 'x-router': 'songlistairec.kugou.com' }, 33 | clearDefaultParams: true, 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /module/album.js: -------------------------------------------------------------------------------- 1 | const { clientver, appid, cryptoMd5, signParamsKey } = require('../util'); 2 | 3 | module.exports = (params, useAxios) => { 4 | const dateTime = Date.now(); 5 | const data = (params?.album_id || '').split(',').map((s) => ({ album_id: s, album_name: '', author_name: '' })); 6 | const dfid = params?.cookie?.dfid || params?.dfid || '-'; 7 | const userid = params?.cookie?.userid || params?.userid || 0; 8 | const token = params?.cookie?.token || params?.token || 0; 9 | 10 | const dataMap = { 11 | appid, 12 | clienttime: dateTime, 13 | clientver, 14 | data, 15 | dfid, 16 | fields: params?.fields || '', 17 | key: signParamsKey(dateTime), 18 | mid: cryptoMd5(dfid), 19 | }; 20 | 21 | if (token) dataMap['token'] = token; 22 | if (userid) dataMap['userid'] = userid; 23 | 24 | return useAxios({ 25 | baseURL: 'http://kmr.service.kugou.com', 26 | url: '/v1/album', 27 | method: 'POST', 28 | data: dataMap, 29 | encryptType: 'android', 30 | cookie: params?.cookie || {}, 31 | headers: { 'x-router': 'kmr.service.kugou.com', 'Content-Type': 'application/json' }, 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /module/album_detail.js: -------------------------------------------------------------------------------- 1 | // 专辑详情 2 | module.exports = (params, useAxios) => { 3 | const data = { data: [{ album_id: params.id }], is_buy: params?.is_buy || 0 }; 4 | return useAxios({ 5 | url: '/kmr/v2/albums', 6 | method: 'POST', 7 | data, 8 | encryptType: 'android', 9 | cookie: params?.cookie || {}, 10 | headers: { 'x-router': 'openapi.kugou.com', 'kg-tid': '255' }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /module/album_shop.js: -------------------------------------------------------------------------------- 1 | // 唱片店 2 | module.exports = (params, useAxios) => { 3 | return useAxios({ 4 | url: '/zhuanjidata/v3/album_shop_v2/get_classify_data', 5 | method: 'GET', 6 | encryptType: 'android', 7 | cookie: params?.cookie || {}, 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /module/album_songs.js: -------------------------------------------------------------------------------- 1 | // 专辑音乐列表 2 | module.exports = (params, useAxios) => { 3 | const dataMap = { 4 | album_id: params.id, 5 | is_buy: params?.is_buy || '', 6 | page: params?.page || 1, 7 | pagesize: params?.pagesize || 30, 8 | }; 9 | 10 | return useAxios({ 11 | url: '/v1/album_audio/lite', 12 | method: 'POST', 13 | data: dataMap, 14 | encryptType: 'android', 15 | cookie: params?.cookie || {}, 16 | headers: { 'x-router': 'openapi.kugou.com', 'kg-tid': '255' }, 17 | }); 18 | } -------------------------------------------------------------------------------- /module/artist_albums.js: -------------------------------------------------------------------------------- 1 | // 获取歌手专辑 2 | 3 | module.exports = (params, useAxios) => { 4 | const dataMap = { 5 | author_id: params.id, 6 | pagesize: params?.pagesize || 30, 7 | page: params?.page || 1, 8 | sort: params?.sort === 'hot' ? 3 : 1, // 3:最热,1:最新 9 | category: 1, 10 | area_code: 'all', 11 | }; 12 | 13 | return useAxios({ 14 | url: '/kmr/v1/author/albums', 15 | method: 'POST', 16 | data: dataMap, 17 | encryptType: 'android', 18 | cookie: params?.cookie || {}, 19 | headers: { 'x-router': 'openapi.kugou.com', 'kg-tid': 36 }, 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /module/artist_audios.js: -------------------------------------------------------------------------------- 1 | const { cryptoMd5, signParamsKey, clientver, appid } = require('../util'); 2 | // 获取歌手单曲 3 | 4 | module.exports = (params, useAxios) => { 5 | const clienttime = Math.floor(new Date().getTime() / 1000); 6 | const mid = cryptoMd5(params?.cookie?.dfid || '-'); 7 | const dataMap = { 8 | appid, 9 | clientver, 10 | mid, 11 | clienttime, 12 | key: signParamsKey(clienttime), 13 | author_id: params.id, 14 | pagesize: params?.pagesize || 30, 15 | page: params?.page || 1, 16 | sort: params?.sort === 'hot' ? 1 : 2, // 1:最热,2:最新 17 | area_code: 'all', 18 | }; 19 | 20 | return useAxios({ 21 | baseURL: 'https://openapi.kugou.com', 22 | url: '/kmr/v1/audio_group/author', 23 | method: 'POST', 24 | data: dataMap, 25 | encryptType: 'android', 26 | cookie: params?.cookie || {}, 27 | headers: { 'x-router': 'openapi.kugou.com', 'kg-tid': 220 }, 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /module/artist_detail.js: -------------------------------------------------------------------------------- 1 | // 歌手详情 2 | module.exports = (params, useAxios) => { 3 | return useAxios({ 4 | url: '/kmr/v3/author', 5 | method: 'POST', 6 | data: { author_id: params.id }, 7 | encryptType: 'android', 8 | cookie: params?.cookie || {}, 9 | headers: { 'x-router': 'openapi.kugou.com', 'kg-tid': 36 }, 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /module/artist_follow.js: -------------------------------------------------------------------------------- 1 | const { cryptoAesEncrypt, rsaEncrypt2 } = require('../util'); 2 | // 关注歌手 3 | module.exports = (params, useAxios) => { 4 | const singerid = Number(params.id); 5 | const token = params?.token || params.cookie?.token || ''; 6 | const userid = Number(params?.userid || params?.cookie?.userid || 0); 7 | const clienttime = Math.floor(Date.now() / 1000); 8 | 9 | const encrypt = cryptoAesEncrypt({ singerid, token }); 10 | 11 | 12 | const paramsMap = { 13 | plat: 0, 14 | userid, 15 | singerid, 16 | source: 7, 17 | p: rsaEncrypt2({ clienttime, key: encrypt.key }), 18 | params: encrypt.str 19 | } 20 | 21 | 22 | 23 | return useAxios({ 24 | url: '/followservice/v3/follow_singer', 25 | method: 'post', 26 | data: paramsMap, 27 | params: {clienttime}, 28 | encryptType: 'android', 29 | cookie: params?.cookie || {}, 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /module/artist_follow_newsongs.js: -------------------------------------------------------------------------------- 1 | // 获取关注歌手新歌 2 | module.exports = (params, useAxios) => { 3 | const last_album_id = params.last_album_id || 0; 4 | const page_size = params.pagesize || 30; 5 | const opt_sort = params.opt_sort === 2 ? 2 : 1; 6 | 7 | const paramsMap = { 8 | last_album_id, 9 | page_size, 10 | opt_sort, 11 | }; 12 | 13 | return useAxios({ 14 | url: '/feed/v1/follow/newsong_album_list', 15 | method: 'post', 16 | data: { last_album_id }, 17 | params: paramsMap, 18 | encryptType: 'android', 19 | cookie: params?.cookie || {}, 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /module/artist_honour.js: -------------------------------------------------------------------------------- 1 | // 歌手荣誉详情 2 | module.exports = (params, useAxios) => { 3 | return useAxios({ 4 | baseURL: 'http://h5activity.kugou.com', 5 | url: '/v1/query_singer_honour_detail', 6 | method: 'POST', 7 | params: { singer_id: params.id, pagesize: params?.pagesize || 30, page: params?.page || 1 }, 8 | encryptType: 'android', 9 | cookie: params?.cookie || {}, 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /module/artist_lists.js: -------------------------------------------------------------------------------- 1 | // 歌手列表 2 | module.exports = (params, useAxios) => { 3 | const paramsMap = { 4 | musician: Number(params.musician || 0), 5 | sextype: params.sextypes || 0, 6 | showtype: 2, 7 | type: params.type || 0, 8 | hotsize: Number(params.hotsize || 30) 9 | }; 10 | 11 | return useAxios({ 12 | url: '/ocean/v6/singer/list', 13 | method: 'GET', 14 | params: paramsMap, 15 | encryptType: 'android', 16 | cookie: params?.cookie || {}, 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /module/artist_unfollow.js: -------------------------------------------------------------------------------- 1 | const { cryptoAesEncrypt, rsaEncrypt2 } = require('../util'); 2 | // 取消关注歌手 3 | module.exports = (params, useAxios) => { 4 | const singerid = params.id; 5 | const token = params?.token || params.cookie?.token || ''; 6 | const userid = params?.userid || params?.cookie?.userid || 0; 7 | const clienttime = Math.floor(Date.now() / 1000); 8 | 9 | const encrypt = cryptoAesEncrypt({ singerid, token }); 10 | 11 | const paramsMap = { 12 | plat: 0, 13 | userid, 14 | singerid, 15 | source: 7, 16 | p: rsaEncrypt2({ clienttime, key: encrypt.key }), 17 | params: encrypt.str 18 | } 19 | 20 | 21 | return useAxios({ 22 | url: '/followservice/v3/unfollow_singer', 23 | method: 'post', 24 | data: paramsMap, 25 | encryptType: 'android', 26 | cookie: params?.cookie || {}, 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /module/artist_videos.js: -------------------------------------------------------------------------------- 1 | // 获取歌手mv 2 | 3 | const tag_idx = { official: 18, live: 20, fan: 23, artist: 42419, all: '' }; 4 | 5 | module.exports = (params, useAxios) => { 6 | const paramsMap = { 7 | author_id: params.id, 8 | is_fanmade: '', 9 | tag_idx: tag_idx[params?.tag || 'all'] || '', // 18:官方版本,20:现场版本,23:饭制版本,42419:歌手发布 10 | pagesize: params.pagesize || 30, 11 | page: params.page || 1, 12 | }; 13 | 14 | return useAxios({ 15 | baseURL: 'https://openapicdn.kugou.com', 16 | url: '/kmr/v1/author/videos', 17 | method: 'GET', 18 | params: paramsMap, 19 | encryptType: 'android', 20 | cookie: params?.cookie || {}, 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /module/audio.js: -------------------------------------------------------------------------------- 1 | const { cryptoMd5, signParamsKey, appid, clientver } = require('../util'); 2 | 3 | module.exports = (params, useAxios) => { 4 | const dateTime = Date.now(); 5 | const data = (params?.hash || '').split(',').map((s) => ({ hash: s, audio_id: 0 })); 6 | const dfid = params?.cookie?.dfid || params?.dfid || '-'; 7 | const userid = params?.cookie?.userid || params?.userid || 0; 8 | const token = params?.cookie?.token || params?.token || 0; 9 | 10 | const dataMap = { 11 | appid, 12 | clienttime: dateTime, 13 | clientver, 14 | data, 15 | dfid, 16 | key: signParamsKey(dateTime), 17 | mid: cryptoMd5(dfid), 18 | }; 19 | 20 | if (token) dataMap['token'] = token; 21 | if (userid) dataMap['userid'] = userid; 22 | 23 | return useAxios({ 24 | baseURL: 'http://kmr.service.kugou.com', 25 | url: '/v1/audio/audio', 26 | method: 'POST', 27 | data: dataMap, 28 | encryptType: 'android', 29 | cookie: params?.cookie || {}, 30 | headers: { 'x-router': 'kmr.service.kugou.com', 'Content-Type': 'application/json' }, 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /module/audio_accompany_matching.js: -------------------------------------------------------------------------------- 1 | const { cryptoMd5, appid } = require('../util'); 2 | 3 | module.exports = (params, useAxios) => { 4 | 5 | const dataMap = { 6 | isteen: 0, 7 | mixId: Number(params.mixId) || 0, 8 | usemkv: 1, 9 | platform: 2, 10 | fileName: params.fileName || '', 11 | hash: params.hash, 12 | version: 12375, 13 | appid, 14 | } 15 | 16 | 17 | const str = '*s&iN#G70*'; 18 | const paramsString = Object.keys(dataMap) 19 | .sort() 20 | .map((key) => `${key}=${typeof dataMap[key] === 'object' ? JSON.stringify(dataMap[key]) : dataMap[key]}`) 21 | .join('&'); 22 | dataMap['sign'] = cryptoMd5(`${paramsString}${str}`).substring(8, 24); 23 | 24 | return useAxios({ 25 | baseURL: 'https://nsongacsing.kugou.com', 26 | url: '/sing7/accompanywan/json/v2/cdn/optimal_matching_accompany_2_listen.do', 27 | params: dataMap, 28 | method: 'get', 29 | encryptType: 'android', 30 | cookie: params?.cookie || {}, 31 | clearDefaultParams: true, 32 | notSignature: true, 33 | }); 34 | }; 35 | -------------------------------------------------------------------------------- /module/audio_ktv_total.js: -------------------------------------------------------------------------------- 1 | const { cryptoMd5, appid } = require('../util'); 2 | 3 | module.exports = (params, useAxios) => { 4 | 5 | const dataMap = { 6 | isteen: 0, 7 | songId: Number(params.songId), 8 | usemkv: 1, 9 | platform: 2, 10 | singerName: params.singerName, 11 | songHash: params.songHash, 12 | version: 12375, 13 | appid, 14 | } 15 | 16 | 17 | const str = '*s&iN#G70*'; 18 | const paramsString = Object.keys(dataMap) 19 | .sort() 20 | .map((key) => `${key}=${typeof dataMap[key] === 'object' ? JSON.stringify(dataMap[key]) : dataMap[key]}`) 21 | .join('&'); 22 | dataMap['sign'] = cryptoMd5(`${paramsString}${str}`).substring(8, 24); 23 | 24 | return useAxios({ 25 | baseURL: 'https://acsing.service.kugou.com', 26 | url: '/sing7/listenguide/json/v2/cdn/listenguide/get_total_opus_num_v02.do', 27 | params: dataMap, 28 | method: 'get', 29 | encryptType: 'android', 30 | cookie: params?.cookie || {}, 31 | clearDefaultParams: true, 32 | notSignature: true, 33 | }); 34 | }; 35 | -------------------------------------------------------------------------------- /module/audio_related.js: -------------------------------------------------------------------------------- 1 | const { cryptoMd5 } = require('../util'); 2 | 3 | const sortType = { all: 1, hot: 2, new: 3 }; 4 | 5 | // https://listkmrp3cdnretry.kugou.com/v3/album_audio/related 6 | module.exports = (params, useAxios) => { 7 | const show_detail = Number(params.show_detail) === 0; 8 | 9 | let dataMap = { 10 | album_audio_id: Number(params.album_audio_id), 11 | appid: 1005, 12 | area_code: 1, 13 | clientver: 12329, 14 | }; 15 | 16 | if (!show_detail) { 17 | dataMap = { 18 | ...dataMap, 19 | page: params.page || 1, 20 | pagesize: params.pagesize || 30, 21 | show_input: 1, 22 | show_type: params.show_type || 0, 23 | sort: sortType[params.sort] || 1, 24 | type: params.type || 0, 25 | }; 26 | } 27 | 28 | dataMap['version'] = 1; 29 | 30 | const str = 'OIlwieks28dk2k092lksi2UIkp'; 31 | const paramsString = Object.keys(dataMap) 32 | .sort() 33 | .map((key) => `${key}=${typeof dataMap[key] === 'object' ? JSON.stringify(dataMap[key]) : dataMap[key]}`) 34 | .join(''); 35 | dataMap['signature'] = cryptoMd5(`${str}${paramsString}${str}`); 36 | 37 | return useAxios({ 38 | baseURL: 'https://listkmrp3cdnretry.kugou.com', 39 | url: !show_detail ? '/v3/album_audio/related' : '/v2/audio_related/total', 40 | params: dataMap, 41 | method: 'get', 42 | encryptType: 'android', 43 | cookie: params?.cookie || {}, 44 | clearDefaultParams: true, 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /module/brush.js: -------------------------------------------------------------------------------- 1 | const { appid, clientver, signParamsKey } = require('../util'); 2 | 3 | module.exports = (params, useAxios) => { 4 | const userid = params?.cookie?.userid || params?.userid || 0; 5 | const vip_type = params?.cookie?.vip_type || params?.vipType || 0; 6 | const dateTime = Date.now(); 7 | 8 | const personal_recommend = { 9 | userid, 10 | appid, 11 | playlist_ver: 2, 12 | clienttime: dateTime, 13 | mid: '', 14 | new_sync_point: dateTime, 15 | module_id: 1, 16 | action: 'login', 17 | vip_type, 18 | vip_flags: 3, 19 | recommend_source_locked: 0, 20 | song_pool_id: Number(params?.song_pool_id || 0), 21 | callerid: 0, 22 | m_type: 1, 23 | kguid: userid, 24 | platform: 'ios', 25 | area_code: 1, 26 | fakem: 'ca981cfc583a4c37f28d2d49000013c16a0a', 27 | clientver: 11850, 28 | mode: params?.mode || 'normal', 29 | active_swtich: 'on', 30 | key: signParamsKey(dateTime), 31 | }; 32 | 33 | const dataMap = { 34 | behaviors: [], 35 | abtest: { 36 | abtest: { shuashua: { commentcard: 2 } }, 37 | }, 38 | personal_recommend_params: personal_recommend, 39 | }; 40 | 41 | return useAxios({ 42 | url: '/genesisapi/v1/newepoch_song_rec/feed', 43 | data: dataMap, 44 | params: { sort_type: 1, platform: 'ios', page: 1, content_ver: 4, clientver: 11850 }, 45 | method: 'post', 46 | encryptType: 'android', 47 | cookie: params?.cookie || {}, 48 | }); 49 | }; 50 | -------------------------------------------------------------------------------- /module/captcha_sent.js: -------------------------------------------------------------------------------- 1 | // 手机验证码发送 2 | module.exports = (params, useAxios) => { 3 | const dataMap = { 4 | businessid: 5, 5 | mobile: `${params?.mobile}`, 6 | plat: 3, 7 | }; 8 | 9 | return useAxios({ 10 | baseURL: 'http://login.user.kugou.com', 11 | url: '/v7/send_mobile_code', 12 | method: 'POST', 13 | data: dataMap, 14 | encryptType: 'android', 15 | cookie: {}, 16 | // headers: { 'x-router': 'loginservice.kugou.com' }, 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /module/comment_album.js: -------------------------------------------------------------------------------- 1 | // 歌曲评论 2 | module.exports = (params, useAxios) => { 3 | 4 | const paramsMap = { 5 | childrenid: params.id, 6 | need_show_image: 1, 7 | p: params.page || 1, 8 | pagesize: params.pagesize || 30, 9 | show_classify: params.show_classify || 1, 10 | show_hotword_list: params.show_hotword_list || 1, 11 | code: '94f1792ced1df89aa68a7939eaf2efca', 12 | } 13 | 14 | return useAxios({ 15 | url: '/m.comment.service/v1/cmtlist', 16 | encryptType: 'android', 17 | method: 'POST', 18 | params: paramsMap, 19 | cookie: params?.cookie || {}, 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /module/comment_count.js: -------------------------------------------------------------------------------- 1 | // 歌曲评论数 2 | module.exports = (params, useAxios) => { 3 | 4 | const paramsMap = { 5 | r: 'comments/getcommentsnum', 6 | code: 'fc4be23b4e972707f36b8a828a93ba8a', 7 | }; 8 | 9 | if (params && params.hash) { 10 | paramsMap.hash = params.hash; 11 | } else if (params && params.special_id) { 12 | paramsMap.childrenid = params.special_id; 13 | } 14 | 15 | return useAxios({ 16 | url: '/index.php', 17 | encryptType: 'web', 18 | method: 'GET', 19 | params: paramsMap, 20 | cookie: params?.cookie || {}, 21 | headers: { 'x-router': 'sum.comment.service.kugou.com' }, 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /module/comment_floor.js: -------------------------------------------------------------------------------- 1 | // 歌曲评论 2 | module.exports = (params, useAxios) => { 3 | 4 | const paramsMap = { 5 | childrenid: params.special_id, 6 | mixsongid: params.mixsongid, 7 | need_show_image: 1, 8 | p: params.page || 1, 9 | pagesize: params.pagesize || 30, 10 | show_classify: params.show_classify || 1, 11 | show_hotword_list: params.show_hotword_list || 1, 12 | code: 'fc4be23b4e972707f36b8a828a93ba8a', 13 | tid: params.tid 14 | } 15 | 16 | return useAxios({ 17 | url: '/mcomment/v1/hot_replylist', 18 | encryptType: 'android', 19 | method: 'POST', 20 | params: paramsMap, 21 | cookie: params?.cookie || {}, 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /module/comment_music.js: -------------------------------------------------------------------------------- 1 | // 歌曲评论 2 | module.exports = (params, useAxios) => { 3 | 4 | const paramsMap = { 5 | mixsongid: params.mixsongid, 6 | need_show_image: 1, 7 | p: params.page || 1, 8 | pagesize: params.pagesize || 30, 9 | show_classify: params.show_classify || 1, 10 | show_hotword_list: params.show_hotword_list || 1, 11 | extdata: '0', 12 | code: 'fc4be23b4e972707f36b8a828a93ba8a' 13 | } 14 | 15 | return useAxios({ 16 | url: '/mcomment/v1/cmtlist', 17 | encryptType: 'android', 18 | method: 'POST', 19 | params: paramsMap, 20 | cookie: params?.cookie || {}, 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /module/comment_music_classify.js: -------------------------------------------------------------------------------- 1 | // 歌曲评论-根据分类获取评论 2 | module.exports = (params, useAxios) => { 3 | 4 | const paramsMap = { 5 | mixsongid: params.mixsongid, 6 | need_show_image: 1, 7 | page: params.page || 1, 8 | pagesize: params.pagesize || 30, 9 | type_id: params.type_id, 10 | extdata: '0', 11 | code: 'fc4be23b4e972707f36b8a828a93ba8a', 12 | sort_method: Number(params.sort) === 2 ? params.sort : 1 13 | } 14 | 15 | return useAxios({ 16 | url: '/mcomment/v1/cmt_classify_list', 17 | encryptType: 'android', 18 | method: 'POST', 19 | params: paramsMap, 20 | cookie: params?.cookie || {}, 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /module/comment_music_hotword.js: -------------------------------------------------------------------------------- 1 | // 歌曲评论-更具热词获取评论 2 | module.exports = (params, useAxios) => { 3 | 4 | const paramsMap = { 5 | mixsongid: params.mixsongid, 6 | need_show_image: 1, 7 | p: params.page || 1, 8 | pagesize: params.pagesize || 30, 9 | hot_word: params.hot_word, 10 | extdata: '0', 11 | code: 'fc4be23b4e972707f36b8a828a93ba8a', 12 | } 13 | 14 | return useAxios({ 15 | url: '/mcomment/v1/get_hot_word', 16 | encryptType: 'android', 17 | method: 'POST', 18 | params: paramsMap, 19 | cookie: params?.cookie || {}, 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /module/comment_playlist.js: -------------------------------------------------------------------------------- 1 | // 歌曲评论 2 | module.exports = (params, useAxios) => { 3 | 4 | const paramsMap = { 5 | childrenid: params.id, 6 | need_show_image: 1, 7 | p: params.page || 1, 8 | pagesize: params.pagesize || 30, 9 | show_classify: params.show_classify || 1, 10 | show_hotword_list: params.show_hotword_list || 1, 11 | code: 'ca53b96fe5a1d9c22d71c8f522ef7c4f', 12 | content_type: 0, 13 | tag: 5 14 | } 15 | 16 | return useAxios({ 17 | url: '/m.comment.service/v1/cmtlist', 18 | encryptType: 'android', 19 | method: 'POST', 20 | params: paramsMap, 21 | cookie: params?.cookie || {}, 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /module/everyday_friend.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (params, useAxios) => { 3 | return useAxios({ 4 | baseURL: 'https://acsing.service.kugou.com', 5 | url: '/sing7/relation/json/v3/friend_rec_by_using_song_list', 6 | encryptType: 'android', 7 | method: 'POST', 8 | data: {list: [{user_id: 853927886, mixsong_ids: [290083753,251724346,571554587,250126644,208831644,40328518,250504076,581706850,318347675,585258401,288481998,407414475,28239430,280584633,291957521,64556644,243149863,488725103,32114153,39951172,29019580,40397606,327507651,32029382,32218359,340353127,276448762,177071956,100031397,249251602]}]}, 9 | params: { channel: 130, isteen: 0, platform: 2, usemkv: 1 }, 10 | cookie: params?.cookie || {}, 11 | headers: { pid: 126556797 }, 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /module/everyday_history.js: -------------------------------------------------------------------------------- 1 | // mode list ,song 2 | 3 | module.exports = (params, useAxios) => { 4 | const paramsMap = { 5 | mode: params.mode || 'list', 6 | platform: params.platform || 'ios' 7 | }; 8 | 9 | if (params.history_name) paramsMap['history_name'] = params.history_name; 10 | if (params.date) paramsMap['date'] = params.date; 11 | 12 | return useAxios({ 13 | url: '/everyday/api/v1/get_history', 14 | encryptType: 'android', 15 | method: 'POST', 16 | params: paramsMap, 17 | cookie: params?.cookie || {}, 18 | headers: { 'x-router': 'everydayrec.service.kugou.com' }, 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /module/everyday_recommend.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | return useAxios({ 3 | url: '/everyday_song_recommend', 4 | encryptType: 'android', 5 | method: 'POST', 6 | params: { platform: params.platform || 'ios' }, 7 | cookie: params?.cookie || {}, 8 | headers: { 'x-router': 'everydayrec.service.kugou.com' }, 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /module/everyday_style_recommend.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | const dataMap = { 3 | platform: params.platform || 'ios' 4 | }; 5 | 6 | return useAxios({ 7 | url: '/everydayrec.service/everyday_style_recommend', 8 | encryptType: 'android', 9 | method: 'POST', 10 | data: {}, 11 | params: { tagids: params.tagids ?? ''}, 12 | cookie: params?.cookie || {}, 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /module/favorite_count.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | return useAxios({ 3 | url: `/count/v1/audio/mget_collect`, 4 | method: 'GET', 5 | encryptType: 'android', 6 | cookie: params?.cookie || {}, 7 | params: { mixsongids: params.mixsongids }, 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /module/fm_class.js: -------------------------------------------------------------------------------- 1 | const { appid, clientver, cryptoMd5, signParamsKey } = require('../util'); 2 | module.exports = (params, useAxios) => { 3 | const dateTime = Date.now(); 4 | const dfid = params?.cookie?.dfid || params?.dfid || '-'; 5 | const userid = params?.cookie?.userid || params?.userid || 0; 6 | const dataMap = { 7 | kguid: userid, 8 | clienttime: dateTime, 9 | mid: cryptoMd5(dfid), 10 | platform: 'android', 11 | clientver, 12 | uid: userid, 13 | get_tracker: 1, 14 | key: signParamsKey(dateTime), 15 | appid, 16 | }; 17 | 18 | return useAxios({ 19 | url: '/v1/class_fm_song', 20 | encryptType: 'android', 21 | method: 'POST', 22 | data: dataMap, 23 | cookie: params?.cookie || {}, 24 | headers: { 'x-router': 'fm.service.kugou.com' }, 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /module/fm_image.js: -------------------------------------------------------------------------------- 1 | const { appid, clientver, cryptoMd5, signParamsKey } = require('../util'); 2 | module.exports = (params, useAxios) => { 3 | const dateTime = Date.now(); 4 | const dfid = params?.cookie?.dfid || params?.dfid || '-'; 5 | const userid = params?.cookie?.userid || params?.userid; 6 | const token = params?.cookie?.token || params?.token; 7 | 8 | const fmData = (params?.fmid || '').split(',').map((s) => ({ fields: 'imgUrl100,imgUrl50', fmid: s, fmtype: 2 })); 9 | 10 | const dataMap = { 11 | appid, 12 | clienttime: dateTime, 13 | clientver, 14 | data: fmData, 15 | dfid, 16 | key: signParamsKey(dateTime), 17 | mid: cryptoMd5(dfid), 18 | }; 19 | if (userid) dataMap['userid'] = userid; 20 | if (token) dataMap['token'] = token; 21 | 22 | return useAxios({ 23 | url: '/v1/fm_info', 24 | encryptType: 'android', 25 | method: 'POST', 26 | data: dataMap, 27 | cookie: params?.cookie || {}, 28 | headers: { 'x-router': 'fm.service.kugou.com', 'Content-Type': 'application/json' }, 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /module/fm_recommend.js: -------------------------------------------------------------------------------- 1 | const { appid, clientver, cryptoMd5, signParamsKey } = require('../util'); 2 | module.exports = (params, useAxios) => { 3 | const dateTime = Date.now(); 4 | const dfid = params?.cookie?.dfid || params?.dfid || '-'; 5 | const dataMap = { 6 | appid, 7 | clientver, 8 | clienttime: dateTime, 9 | mid: cryptoMd5(dfid), 10 | key: signParamsKey(dateTime), 11 | rcmdsongcount: 1, 12 | level: 0, 13 | area_code: 1, 14 | get_tracker: 1, 15 | uid: 0, 16 | }; 17 | 18 | return useAxios({ 19 | url: '/v1/rcmd_list', 20 | encryptType: 'android', 21 | method: 'POST', 22 | data: dataMap, 23 | cookie: params?.cookie || {}, 24 | headers: { 'x-router': 'fm.service.kugou.com' }, 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /module/fm_songs.js: -------------------------------------------------------------------------------- 1 | const { appid, clientver, cryptoMd5, signParamsKey } = require('../util'); 2 | module.exports = (params, useAxios) => { 3 | const dateTime = Date.now(); 4 | const dfid = params?.cookie?.dfid || params?.dfid || '-'; 5 | const userid = params?.cookie?.userid || params?.userid; 6 | 7 | const fmData = (params?.fmid || '').split(',').map((s) => ({ 8 | fmid: s, 9 | fmtype: params?.type || 2, 10 | offset: params?.offset || -1, 11 | size: params?.size || 20, 12 | singername: s.singername || '', 13 | })); 14 | 15 | // fmType 生成 16 | (params?.fmtype || '').split(',').forEach((s, l) => (fmData[l].fmtype = s || fmData[l].fmtype)); 17 | 18 | (params?.fmoffset || '').split(',').forEach((s, l) => (fmData[l].offset = s || fmData[l].offset)); 19 | 20 | (params?.fmsize || '').split(',').forEach((s, l) => (fmData[l].size = s || fmData[l].size)); 21 | 22 | const dataMap = { 23 | appid, 24 | area_code: 1, 25 | clienttime: dateTime, 26 | clientver, 27 | data: fmData, 28 | get_tracker: 1, 29 | key: signParamsKey(dateTime), 30 | mid: cryptoMd5(dfid), 31 | uid: userid, 32 | }; 33 | 34 | return useAxios({ 35 | url: '/v1/app_song_list_offset', 36 | encryptType: 'android', 37 | method: 'POST', 38 | data: dataMap, 39 | cookie: params?.cookie || {}, 40 | headers: { 'x-router': 'fm.service.kugou.com', 'Content-Type': 'application/json' }, 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /module/images.js: -------------------------------------------------------------------------------- 1 | const { signatureAndroidParams, appid, clientver } = require('../util'); 2 | 3 | module.exports = (params, useAxios) => { 4 | const data = (params?.hash || '').split(',').map((s) => ({ album_id: 0, hash: s, album_audio_id: 0 })); 5 | (params?.album_id || '').split(',').forEach((s, index) => { 6 | if (index <= data.length - 1) { 7 | data[index]['album_id'] = s || 0; 8 | } 9 | }); 10 | (params?.album_audio_id || '').split(',').forEach((s, index) => { 11 | if (index <= data.length - 1) { 12 | data[index]['album_audio_id'] = s || 0; 13 | } 14 | }); 15 | 16 | const paramsMap = { 17 | album_image_type: '-3', 18 | appid, 19 | clientver, 20 | author_image_type: '3,4,5', 21 | count: params?.count || 5, 22 | data, 23 | isCdn: 1, 24 | publish_time: 1, 25 | }; 26 | 27 | const query = Object.keys(paramsMap) 28 | .sort() 29 | .map((s) => `${s}=${encodeURIComponent(typeof paramsMap[s] === 'object' ? JSON.stringify(paramsMap[s]) : paramsMap[s])}`); 30 | 31 | const signature = signatureAndroidParams(paramsMap); 32 | 33 | return useAxios({ 34 | baseURL: 'https://expendablekmr.kugou.com', 35 | url: `/container/v2/image?${query.join('&')}`, 36 | method: 'GET', 37 | encryptType: 'android', 38 | params: { signature }, 39 | cookie: params?.cookie || {}, 40 | notSign: true, 41 | clearDefaultParams: true, 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /module/images_audio.js: -------------------------------------------------------------------------------- 1 | const { signatureAndroidParams, appid, clientver } = require('../util'); 2 | 3 | module.exports = (params, useAxios) => { 4 | const data = (params?.hash || '').split(',').map((s) => ({ audio_id: 0, hash: s, album_audio_id: 0, filename: '' })); 5 | (params?.audio_id || '').split(',').forEach((s, index) => { 6 | if (index <= data.length - 1) { 7 | data[index]['audio_id'] = s || 0; 8 | } 9 | }); 10 | (params?.album_audio_id || '').split(',').forEach((s, index) => { 11 | if (index <= data.length - 1) { 12 | data[index]['album_audio_id'] = s || 0; 13 | } 14 | }); 15 | (params?.filename || '').split(',').forEach((s, index) => { 16 | if (index <= data.length - 1) { 17 | data[index]['filename'] = s; 18 | } 19 | }); 20 | 21 | const paramsMap = { 22 | appid, 23 | clientver, 24 | count: params?.count || 5, 25 | data, 26 | isCdn: 1, 27 | publish_time: 1, 28 | show_authors: 1, 29 | }; 30 | 31 | const query = Object.keys(paramsMap) 32 | .sort() 33 | .map((s) => `${s}=${encodeURIComponent(typeof paramsMap[s] === 'object' ? JSON.stringify(paramsMap[s]) : paramsMap[s])}`); 34 | 35 | const signature = signatureAndroidParams(paramsMap); 36 | 37 | return useAxios({ 38 | baseURL: 'https://expendablekmr.kugou.com', 39 | url: `/v2/author_image/audio?${query.join('&')}`, 40 | method: 'GET', 41 | encryptType: 'android', 42 | params: { signature }, 43 | cookie: params?.cookie || {}, 44 | notSign: true, 45 | clearDefaultParams: true, 46 | }); 47 | }; 48 | -------------------------------------------------------------------------------- /module/ip.js: -------------------------------------------------------------------------------- 1 | // 根据 ip id 获取相对应的 歌曲/专辑/视频/歌手 2 | module.exports = (params, useAxios) => { 3 | const dataMap = { 4 | is_publish: 1, 5 | ip_id: params?.id, 6 | sort: 3, 7 | page: params?.page || 1, 8 | pagesize: params?.pagesize || 30, 9 | query: 1, 10 | }; 11 | 12 | const type = ['audios', 'albums', 'videos', 'author_list'].includes(params?.type) ? params.type : 'audios'; 13 | 14 | return useAxios({ 15 | url: `/openapi/v1/ip/${type}`, 16 | encryptType: 'android', 17 | method: 'POST', 18 | data: dataMap, 19 | cookie: params?.cookie || {}, 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /module/ip_dateil.js: -------------------------------------------------------------------------------- 1 | // 获取ip详情 2 | module.exports = (params, useAxios) => { 3 | const data = (params?.id || '').split(',').map((s) => ({ ip_id: s })); 4 | 5 | const dataMap = { 6 | data, 7 | is_publish: 1, 8 | }; 9 | 10 | return useAxios({ 11 | url: '/openapi/v1/ip', 12 | encryptType: 'android', 13 | method: 'POST', 14 | data: dataMap, 15 | cookie: params?.cookie || {}, 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /module/ip_playlist.js: -------------------------------------------------------------------------------- 1 | // 根据 ip 获取相对于歌单 2 | module.exports = (params, useAxios) => { 3 | const dataMap = { 4 | ip: params?.id, 5 | page: params?.page || 1, 6 | pagesize: params?.pagesize || 30, 7 | }; 8 | 9 | return useAxios({ 10 | url: '/ocean/v6/pubsongs/list_info_for_ip', 11 | encryptType: 'android', 12 | method: 'POST', 13 | params: dataMap, 14 | cookie: params?.cookie || {}, 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /module/ip_zone.js: -------------------------------------------------------------------------------- 1 | // ip 专区 2 | module.exports = (params, useAxios) => { 3 | return new Promise((resolve, reject) => { 4 | useAxios({ 5 | url: `/v1/zone/index`, 6 | encryptType: 'android', 7 | method: 'GET', 8 | cookie: params?.cookie || {}, 9 | headers: { 'x-router': 'yuekucategory.kugou.com' }, 10 | }) 11 | .then((resp) => { 12 | if (resp.body.status == 1) { 13 | if (resp.body.data?.list) { 14 | const list = Array.isArray(resp.body.data.list) ? resp.body.data?.list : []; 15 | for (let index = 0; index < list.length; index += 1) { 16 | if (list[index].special_link) { 17 | const urls = new URLSearchParams(list[index].special_link); 18 | if (urls.has('path')) { 19 | const pathUrls = new URLSearchParams(urls.get('path') || ''); 20 | list[index]['ip_id'] = Number(pathUrls.get('ip_id') || ''); 21 | } 22 | } 23 | } 24 | resp.body.data.list = list; 25 | } 26 | } 27 | resolve(resp); 28 | }) 29 | .catch((e) => reject(e)); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /module/ip_zone_home.js: -------------------------------------------------------------------------------- 1 | // 获取今日推荐信息,有可能为空 2 | module.exports = (params, useAxios) => { 3 | const dataMap = { 4 | id: params?.id, 5 | share: 0, 6 | }; 7 | 8 | return useAxios({ 9 | url: '/v1/zone/home', 10 | encryptType: 'android', 11 | method: 'GET', 12 | params: dataMap, 13 | cookie: params?.cookie || {}, 14 | headers: { 'x-router': 'yuekucategory.kugou.com' }, 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /module/kmr_audio_mv.js: -------------------------------------------------------------------------------- 1 | const { appid, clientver } = require('../util'); 2 | // 根据 album_audio_id/MixSongID 获取歌曲 相对应的 mv 3 | module.exports = (params, useAxios) => { 4 | const resource = (params?.album_audio_id || '').split(',').map((s) => ({ album_audio_id: s })); 5 | 6 | const paramsMap = { 7 | data: resource, 8 | fields: params.fields || '', 9 | }; 10 | 11 | 12 | return useAxios({ 13 | url: '/kmr/v1/audio/mv', 14 | method: 'POST', 15 | data: paramsMap, 16 | encryptType: 'android', 17 | cookie: params?.cookie || {}, 18 | headers: {'x-router': 'openapi.kugou.com', 'KG-TID': 38}, 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /module/krm_audio.js: -------------------------------------------------------------------------------- 1 | // 根据 album_audio_id/MixSongID 获取歌曲 相对应的 歌手/专辑/歌曲信息 2 | module.exports = (params, useAxios) => { 3 | const resource = (params?.album_audio_id || '').split(',').map((s) => ({ entity_id: Number(s) })); 4 | 5 | const paramsMap = { 6 | data: resource, 7 | fields: params.fields || 'base', 8 | }; 9 | 10 | return useAxios({ 11 | url: '/kmr/v2/audio', 12 | method: 'POST', 13 | data: paramsMap, 14 | encryptType: 'android', 15 | cookie: params?.cookie || {}, 16 | headers: {'x-router': 'openapi.kugou.com', 'KG-TID': 238}, 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /module/lastest_songs_listen.js: -------------------------------------------------------------------------------- 1 | // 获取继续播放信息 2 | module.exports = (params, useAxios) => { 3 | const token = params?.token || params.cookie?.token || ''; 4 | const userid = Number(params?.userid || params?.cookie?.userid || 0); 5 | const paramsMap = { 6 | area_code: '1', 7 | sources: ['pc', 'mobile', 'tv', 'car'], 8 | userid, 9 | ret_info: 1, 10 | token, 11 | pagesize: Number(params.pagesize || 30) 12 | } 13 | 14 | return useAxios({ 15 | url: '/playque/devque/v1/get_latest_songs', 16 | encryptType: 'android', 17 | method: 'POST', 18 | data: paramsMap, 19 | cookie: params?.cookie || {}, 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /module/login.js: -------------------------------------------------------------------------------- 1 | const { cryptoAesDecrypt, cryptoAesEncrypt, cryptoRSAEncrypt } = require('../util'); 2 | 3 | module.exports = (params, useAxios) => { 4 | const dateNow = Date.now(); 5 | const encrypt = cryptoAesEncrypt({ pwd: params?.password || '', code: '' , clienttime_ms: dateNow}); 6 | const dataMap = { 7 | plat: 1, 8 | support_multi: 1, 9 | clienttime_ms: dateNow, 10 | t1: 0, 11 | t2: 0, 12 | t3: 'MCwwLDAsMCwwLDAsMCwwLDA=', 13 | username: params?.username, 14 | params: encrypt.str, 15 | pk: cryptoRSAEncrypt({'clienttime_ms': dateNow, key: encrypt.key}).toUpperCase() 16 | }; 17 | 18 | return new Promise((resolve, reject) => { 19 | useAxios({ 20 | url: '/v9/login_by_pwd', 21 | method: 'POST', 22 | data: dataMap, 23 | encryptType: 'android', 24 | cookie: params?.cookie || {}, 25 | headers: { 'x-router': 'login.user.kugou.com' }, 26 | }).then(res => { 27 | const { body } = res; 28 | if (body?.status && body?.status === 1) { 29 | if (body?.data?.secu_params) { 30 | const getToken = cryptoAesDecrypt(body.data.secu_params, encrypt.key); 31 | if (typeof getToken === 'object') { 32 | res.body.data = { ...body.data, ...getToken}; 33 | Object.keys(getToken).forEach(key => res.cookie.push(`${key}=${getToken[key]}`)); 34 | } else { 35 | res.body.data['token'] = getToken; 36 | res.cookie.push(`token=${getToken}`); 37 | } 38 | res.cookie.push(`userid=${res.body.data?.userid || 0}`) 39 | res.cookie.push(`vip_type=${res.body.data?.vip_type || 0}`) 40 | res.cookie.push(`vip_token=${res.body.data?.vip_token || ''}`) 41 | 42 | resolve(res); 43 | return; 44 | } 45 | } 46 | resolve(res); 47 | }).catch(e => reject(e)); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /module/login_cellphone.js: -------------------------------------------------------------------------------- 1 | // 手机登录 2 | const { cryptoAesDecrypt, cryptoAesEncrypt, cryptoRSAEncrypt, signParamsKey, isLite } = require('../util'); 3 | 4 | 5 | 6 | module.exports = (params, useAxios) => { 7 | const dateTime = Date.now(); 8 | const encrypt = cryptoAesEncrypt({ mobile: params?.mobile || '', code: params?.code || '' }); 9 | let dataMap = { 10 | plat: 1, 11 | support_multi: 1, 12 | t1: 0, 13 | t2: 0, 14 | clienttime_ms: dateTime, 15 | mobile: params.mobile, 16 | key: signParamsKey(dateTime), 17 | }; 18 | 19 | if (isLite) { 20 | dataMap['p2'] = cryptoRSAEncrypt({ 'clienttime_ms': dateTime, code: params.code, mobile: params.mobile }).toUpperCase(); 21 | } else { 22 | const mobile = params?.mobile && `${params.mobile.toString().substring(0, 2)}*****${params.mobile.toString().substring(10, 11)}`; 23 | dataMap['mobile'] = mobile; 24 | dataMap['t3'] = 'MCwwLDAsMCwwLDAsMCwwLDA='; 25 | dataMap['params'] = encrypt.str; 26 | dataMap['pk'] = cryptoRSAEncrypt({ 'clienttime_ms': dateTime, key: encrypt.key }).toUpperCase() 27 | } 28 | 29 | return new Promise((resolve, reject) => { 30 | useAxios({ 31 | url: `/${isLite ? 'v6' : 'v7'}/login_by_verifycode`, 32 | method: 'POST', 33 | data: dataMap, 34 | encryptType: 'android', 35 | cookie: params?.cookie || {}, 36 | headers: { 'x-router': 'login.user.kugou.com' }, 37 | }) 38 | .then((res) => { 39 | const { body } = res; 40 | if (body?.status && body?.status === 1) { 41 | if (body?.data?.secu_params) { 42 | const getToken = cryptoAesDecrypt(body.data.secu_params, encrypt.key); 43 | if (typeof getToken === 'object') { 44 | res.body.data = { ...body.data, ...getToken }; 45 | Object.keys(getToken).forEach((key) => res.cookie.push(`${key}=${getToken[key]}`)); 46 | } else { 47 | res.body.data['token'] = getToken; 48 | } 49 | } 50 | res.cookie.push(`token=${res.body.data['token']}`); 51 | res.cookie.push(`userid=${res.body.data?.userid || 0}`); 52 | res.cookie.push(`vip_type=${res.body.data?.vip_type || 0}`); 53 | res.cookie.push(`vip_token=${res.body.data?.vip_token || ''}`); 54 | } 55 | resolve(res); 56 | }) 57 | .catch((e) => reject(e)); 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /module/login_openplat.js: -------------------------------------------------------------------------------- 1 | // 开放平台登录 2 | const axios = require('axios'); 3 | const { 4 | wx_appid, 5 | wx_secret, 6 | cryptoAesDecrypt, 7 | cryptoAesEncrypt, 8 | cryptoRSAEncrypt, 9 | wx_lite_appid, 10 | wx_lite_secret, 11 | isLite, 12 | } = require('../util'); 13 | const appid = isLite ? wx_lite_appid : wx_appid; 14 | const secret = isLite ? wx_lite_secret : wx_secret; 15 | 16 | 17 | const assetsToken = (code) => { 18 | return axios({ 19 | url: 'https://api.weixin.qq.com/sns/oauth2/access_token', 20 | method: 'POST', 21 | params: { secret, appid, code, grant_type: 'authorization_code' }, 22 | }); 23 | }; 24 | 25 | module.exports = (params, useAxios) => { 26 | const answer = { status: 500, body: {}, cookie: [] }; 27 | return new Promise(async (resolve, reject) => { 28 | try { 29 | const assetsTokenResp = await assetsToken(params?.code || ''); 30 | 31 | if (assetsTokenResp.data?.access_token && assetsTokenResp.data?.openid) { 32 | const dateNow = Date.now(); 33 | const encrypt = cryptoAesEncrypt({ access_token: assetsTokenResp.data.access_token }); 34 | const pk = cryptoRSAEncrypt({ 'clienttime_ms': dateNow, key: encrypt.key }).toUpperCase(); 35 | 36 | const dataMap = { 37 | force_login: 1, 38 | partnerid: 36, 39 | clienttime_ms: dateNow, 40 | t1: 0, 41 | t2: 0, 42 | t3: 'MCwwLDAsMCwwLDAsMCwwLDA=', 43 | openid: assetsTokenResp.data.openid, 44 | params: encrypt.str, 45 | pk, 46 | }; 47 | 48 | const response = await useAxios({ 49 | url: `/v6/login_by_openplat`, 50 | method: 'POST', 51 | data: dataMap, 52 | cookie: params?.cookie, 53 | encryptType: 'android', 54 | headers: { 'x-router': 'login.user.kugou.com' }, 55 | }); 56 | 57 | if (response.body?.status === 1) { 58 | const getToken = cryptoAesDecrypt(response.body.data?.secu_params, encrypt.key); 59 | if (typeof getToken === 'object') { 60 | response.body.data = { ...response.body.data, ...getToken }; 61 | Object.keys(getToken).forEach((key) => response.cookie.push(`${key}=${getToken[key]}`)); 62 | } else { 63 | response.body.data['token'] = getToken; 64 | response.cookie.push(`token=${getToken}`); 65 | } 66 | response.cookie.push(`userid=${response.body.data?.userid || 0}`); 67 | response.cookie.push(`vip_type=${response.body.data?.vip_type || 0}`); 68 | response.cookie.push(`vip_token=${response.body.data?.vip_token || ''}`); 69 | } 70 | resolve(response); 71 | } else { 72 | answer.status = 502; 73 | answer.body = { status: 0, msg: assetsTokenResp.data }; 74 | reject(answer); 75 | } 76 | } catch (error) { 77 | answer.status = 502; 78 | answer.body = { status: 0, msg: error }; 79 | reject(answer); 80 | } 81 | }); 82 | }; 83 | -------------------------------------------------------------------------------- /module/login_qr_check.js: -------------------------------------------------------------------------------- 1 | const { srcappid, appid } = require('../util'); 2 | 3 | // 酷狗二维码状态检测 4 | // 0 为二维码过期,1 为等待扫码,2 为待确认,4 为授权登录成功(4 状态码下会返回 token) 5 | module.exports = (params, useAxios) => { 6 | return new Promise((resolve, reject) => { 7 | useAxios({ 8 | baseURL: 'https://login-user.kugou.com', 9 | url: '/v2/get_userinfo_qrcode', 10 | method: 'GET', 11 | params: { plat: 4, appid, srcappid, qrcode: params?.key }, 12 | encryptType: 'web', 13 | cookie: params?.cookie || {}, 14 | }).then(resp => { 15 | if (resp.body?.data?.status == 4) { 16 | resp.cookie.push(`token=${resp.body?.data?.token}`); 17 | resp.cookie.push(`userid=${resp.body?.data?.userid}`); 18 | } 19 | resolve(resp); 20 | }).catch(e => reject(e)); 21 | }); 22 | }; -------------------------------------------------------------------------------- /module/login_qr_create.js: -------------------------------------------------------------------------------- 1 | const qrcode = require('qrcode'); 2 | // 酷狗二维码生成 3 | 4 | module.exports = (params, useAxios) => { 5 | return new Promise(async (resolve) => { 6 | const url = `https://h5.kugou.com/apps/loginQRCode/html/index.html?qrcode=${params.key}` 7 | return resolve({ 8 | code: 200, 9 | status: 200, 10 | body: { 11 | code: 200, 12 | data: { 13 | url: url, 14 | base64: params?.qrimg ? await qrcode.toDataURL(url) : '', 15 | }, 16 | }, 17 | }) 18 | }) 19 | } -------------------------------------------------------------------------------- /module/login_qr_key.js: -------------------------------------------------------------------------------- 1 | const { srcappid, appid } = require('../util'); 2 | 3 | // 二维码 key 生成接口 4 | module.exports = (params, useAxios) => { 5 | return useAxios({ 6 | baseURL: 'https://login-user.kugou.com', 7 | url: '/v2/qrcode', 8 | method: 'GET', 9 | params: { 10 | appid: params?.type === 'web' ? 1014 : 1001, 11 | type: 1, 12 | plat: 4, 13 | qrcode_txt: `https://h5.kugou.com/apps/loginQRCode/html/index.html?appid=${appid}&`, 14 | srcappid, 15 | }, 16 | encryptType: 'web', 17 | cookie: params?.cookie || {}, 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /module/login_token.js: -------------------------------------------------------------------------------- 1 | // 刷新登录 2 | const { cryptoAesDecrypt, cryptoAesEncrypt, cryptoRSAEncrypt, isLite } = require('../util'); 3 | 4 | const key = '90b8382a1bb4ccdcf063102053fd75b8'; 5 | const iv = 'f063102053fd75b8'; 6 | 7 | const liteKey = 'c24f74ca2820225badc01946dba4fdf7'; 8 | const liteIv = 'adc01946dba4fdf7'; 9 | 10 | module.exports = (params, useAxios) => { 11 | const dateNow = Date.now(); 12 | const token = params?.token || params?.cookie?.token || ''; 13 | const userid = params?.userid || params?.cookie?.userid || '0'; 14 | const encrypt = cryptoAesEncrypt({ clienttime: Math.floor(dateNow / 1000), token }, { key: isLite ? liteKey : key, iv: isLite ? liteIv : iv }); 15 | const encryptParams = cryptoAesEncrypt({}); 16 | const pk = cryptoRSAEncrypt({ clienttime_ms: dateNow, key: encryptParams.key }); 17 | 18 | 19 | const dataMap = { 20 | dfid: params?.cookie?.dfid || '-', 21 | p3: encrypt, 22 | plat: 1, 23 | t1: 0, 24 | t2: 0, 25 | t3: 'MCwwLDAsMCwwLDAsMCwwLDA=', 26 | pk, 27 | params: encryptParams.str, 28 | userid, 29 | clienttime_ms: dateNow, 30 | }; 31 | 32 | return new Promise((resolve, reject) => { 33 | useAxios({ 34 | baseURL: 'http://login.user.kugou.com', 35 | url: `/${isLite ? 'v4' : 'v5'}/login_by_token`, 36 | method: 'POST', 37 | data: dataMap, 38 | cookie: params?.cookie, 39 | encryptType: 'android', 40 | headers: { 'x-router': 'login.user.kugou.com' }, 41 | }) 42 | .then((res) => { 43 | const { body } = res; 44 | if (body?.status && body?.status === 1) { 45 | if (body?.data?.secu_params) { 46 | const getToken = cryptoAesDecrypt(body.data.secu_params, encryptParams.key); 47 | if (typeof getToken === 'object') { 48 | res.body.data = { ...body.data, ...getToken }; 49 | Object.keys(getToken).forEach((key) => res.cookie.push(`${key}=${getToken[key]}`)); 50 | } else { 51 | res.body.data['token'] = getToken; 52 | } 53 | } 54 | res.cookie.push(`token=${res.body.data['token']}`); 55 | res.cookie.push(`userid=${res.body.data?.userid || 0}`); 56 | res.cookie.push(`vip_type=${res.body.data?.vip_type || 0}`); 57 | res.cookie.push(`vip_token=${res.body.data?.vip_token || ''}`); 58 | } 59 | resolve(res); 60 | }) 61 | .catch((e) => reject(e)); 62 | }); 63 | }; 64 | -------------------------------------------------------------------------------- /module/login_wx_check.js: -------------------------------------------------------------------------------- 1 | 2 | const axios = require('axios'); 3 | 4 | module.exports = (params, useAxios) => { 5 | const answer = { status: 500, body: {}, cookie: [] }; 6 | return new Promise(async (resolve, reject) => { 7 | try { 8 | const resp = await axios({ url: `https://long.open.weixin.qq.com/connect/l/qrconnect?f=json&uuid=${params?.uuid || ''}` }); 9 | 10 | answer.status = 200; 11 | answer.body = resp.data; 12 | 13 | resolve(answer); 14 | } catch (err) { 15 | answer.status = 502; 16 | answer.body = { status: 0, msg: err }; 17 | reject(answer); 18 | } 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /module/login_wx_create.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const { wx_appid, wx_secret, cryptoMd5, cryptoSha1, randomString, wx_lite_appid, wx_lite_secret, isLite } = require('../util'); 3 | 4 | const appid = isLite ? wx_lite_appid : wx_appid; 5 | const secret = isLite ? wx_lite_secret : wx_secret; 6 | 7 | const accessToken = () => { 8 | return axios({ url: 'https://api.weixin.qq.com/cgi-bin/token', params: { appid, secret, grant_type: 'client_credential' } }); 9 | }; 10 | 11 | /** 12 | * 13 | * @param {string} accessToken 14 | * @returns 15 | */ 16 | const ticket = (accessToken) => axios({ url: 'https://api.weixin.qq.com/cgi-bin/ticket/getticket', params: { access_token: accessToken, type: 2 } }); 17 | 18 | module.exports = (params, useAxios) => { 19 | const answer = { status: 500, body: {}, cookie: [] }; 20 | return new Promise(async (resolve, reject) => { 21 | try { 22 | const accessTokenResp = await accessToken(); 23 | if (accessTokenResp.data?.access_token) { 24 | const ticketResp = await ticket(accessTokenResp.data.access_token); 25 | 26 | if (ticketResp.data?.errcode === 0) { 27 | const ticket = ticketResp.data.ticket; 28 | const timestamp = Date.now(); 29 | const noncestr = cryptoMd5(randomString()); 30 | const signaturePrams = `appid=${appid}&noncestr=${noncestr}&sdk_ticket=${ticket}×tamp=${timestamp}`; 31 | const signature = cryptoSha1(signaturePrams); 32 | const params = { appid: appid, noncestr, timestamp, scope: 'snsapi_userinfo', signature }; 33 | const connect = await axios({ url: 'https://open.weixin.qq.com/connect/sdk/qrconnect', params }); 34 | 35 | if (connect.data?.errcode === 0) { 36 | answer.status = 200; 37 | connect.data.qrcode['qrcodeurl'] = `https://open.weixin.qq.com/connect/confirm?uuid=${connect.data.uuid}`; 38 | answer.body = connect.data; 39 | resolve(answer); 40 | } else { 41 | answer.status = 502; 42 | answer.body = { status: 0, msg: connect.data }; 43 | reject(answer); 44 | } 45 | } else { 46 | answer.status = 502; 47 | answer.body = { status: 0, msg: ticketResp.data }; 48 | reject(answer); 49 | } 50 | } else { 51 | answer.status = 502; 52 | answer.body = { status: 0, msg: accessTokenResp.data }; 53 | reject(answer); 54 | } 55 | } catch (e) { 56 | answer.status = 502; 57 | answer.body = { status: 0, msg: e }; 58 | reject(answer); 59 | } 60 | }); 61 | }; 62 | -------------------------------------------------------------------------------- /module/longaudio_album_audios.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | return useAxios({ 3 | url: `/longaudio/v2/album_audios`, 4 | method: 'post', 5 | encryptType: 'android', 6 | data: { 7 | album_id: params.album_id, 8 | area_code: 1, 9 | tagid: 0, 10 | page: params.page || 1, 11 | pagesize: params.pagesize || 30, 12 | }, 13 | cookie: params?.cookie || {}, 14 | headers: { 'x-router': 'openapi.kugou.com', 'KG-TID': '78' }, 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /module/longaudio_album_detail.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | const data = (params.album_id || '').split(',').map((s) => ({ album_id: s })); 3 | return useAxios({ 4 | url: `/openapi/v2/broadcast`, 5 | method: 'post', 6 | encryptType: 'android', 7 | data: { 8 | data, 9 | show_album_tag: 1, 10 | fields: "album_name,album_id,category,authors,sizable_cover,intro,author_name,trans_param,album_tag,mix_intro,full_intro,is_publish" 11 | }, 12 | cookie: params?.cookie || {}, 13 | headers: {'KG-TID': '78'} 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /module/longaudio_daily_recommend.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | return useAxios({ 3 | url: `/longaudio/v1/home_new/daily_recommend`, 4 | method: 'post', 5 | encryptType: 'android', 6 | params: { module_id: 1, size: params.pagesize || 30, page: params.page || 1 }, 7 | cookie: params?.cookie || {}, 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /module/longaudio_rank_recommend.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | return useAxios({ 3 | url: `/longaudio/v1/home_new/rank_card_recommend`, 4 | method: 'get', 5 | encryptType: 'android', 6 | params: {platform: 'ios'}, 7 | cookie: params?.cookie || {}, 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /module/longaudio_vip_recommend.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | return useAxios({ 3 | url: `/longaudio/v1/home_new/vip_select_recommend`, 4 | method: 'post', 5 | encryptType: 'android', 6 | data: {album_playlist: []}, 7 | params: {position: '2', clientver: 12329}, 8 | cookie: params?.cookie || {}, 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /module/longaudio_week_recommend.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | return useAxios({ 3 | url: `/longaudio/v1/home_new/week_new_albums_recommend`, 4 | method: 'post', 5 | encryptType: 'android', 6 | data: {album_playlist: []}, 7 | params: {clientver: 12329}, 8 | cookie: params?.cookie || {}, 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /module/lyric.js: -------------------------------------------------------------------------------- 1 | // 歌词获取 2 | const { decodeLyrics } = require('../util'); 3 | 4 | module.exports = (params, useAxios) => { 5 | const dataMap = { 6 | ver: 1, 7 | client: params?.client || 'android', 8 | id: params?.id, 9 | accesskey: params?.accesskey, 10 | fmt: params.fmt || 'krc', 11 | charset: 'utf8', 12 | }; 13 | 14 | return new Promise((resolve, reject) => { 15 | useAxios({ 16 | baseURL: 'https://lyrics.kugou.com', 17 | url: '/download', 18 | method: 'GET', 19 | params: dataMap, 20 | cookie: params?.cookie || {}, 21 | encryptType: 'android', 22 | }) 23 | .then((res) => { 24 | if (params?.decode) { 25 | if (res.body?.content) { 26 | res.body['decodeContent'] = params?.fmt == 'lrc' || Number(res.body?.contenttype) !== 0 ? Buffer.from(res.body?.content, 'base64').toString() : decodeLyrics(res.body.content); 27 | resolve(res); 28 | return; 29 | } 30 | } 31 | resolve(res); 32 | }) 33 | .catch((e) => reject(e)); 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /module/pc_diantai.js: -------------------------------------------------------------------------------- 1 | // 电台 banner 2 | module.exports = (params, useAxios) => { 3 | const userid = params?.cookie?.userid || params?.userid || 0; 4 | const dataMap = { 5 | isvip: 0, 6 | userid, 7 | vipType: 0, 8 | }; 9 | return useAxios({ 10 | baseURL: 'https://adservice.kugou.com', 11 | url: '/v3/pc_diantai', 12 | data: dataMap, 13 | method: 'post', 14 | encryptType: 'android', 15 | cookie: params?.cookie || {}, 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /module/personal_fm.js: -------------------------------------------------------------------------------- 1 | const { signParamsKey, appid, clientver } = require('../util'); 2 | module.exports = (params, useAxios) => { 3 | const userid = params?.cookie?.userid || params?.userid || 0; 4 | const token = params?.cookie?.token || params?.token || 0; 5 | const vip_type = params?.cookie?.vip_type || params?.vipType || 0; 6 | const dateTime = Date.now(); 7 | 8 | const dataMap = { 9 | appid, 10 | clienttime: dateTime, 11 | mid: '', 12 | action: params?.action || 'play', 13 | recommend_source_locked: 0, 14 | song_pool_id: Number(params?.song_pool_id || 0), 15 | callerid: 0, 16 | m_type: 1, 17 | platform: params?.platform || 'ios', 18 | area_code: 1, 19 | remain_songcnt: Number(params?.remain_songcnt || 0), 20 | clientver, 21 | is_overplay: params?.is_overplay ? 1 : 0, 22 | mode: params?.mode || 'normal', 23 | fakem: 'ca981cfc583a4c37f28d2d49000013c16a0a', 24 | key: signParamsKey(dateTime), 25 | }; 26 | 27 | if (userid) { 28 | dataMap['userid'] = userid; 29 | dataMap['kguid'] = userid; 30 | } 31 | if (token) dataMap['token'] = token; 32 | if (vip_type) dataMap['vip_type'] = vip_type; 33 | 34 | if (params?.hash) dataMap['hash'] = params.hash; 35 | if (params?.songid) dataMap['songid'] = params.songid; 36 | if (params?.playtime) dataMap['playtime'] = params.playtime; 37 | 38 | return useAxios({ 39 | url: '/v2/personal_recommend', 40 | data: dataMap, 41 | method: 'post', 42 | encryptType: 'android', 43 | cookie: params?.cookie || {}, 44 | headers: { 'x-router': 'persnfm.service.kugou.com' }, 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /module/playhistory_upload.js: -------------------------------------------------------------------------------- 1 | // 提交听歌历史 2 | module.exports = (params, useAxios) => { 3 | const userid = params?.userid || params?.cookie?.userid || 0; 4 | const token = params?.token || params?.cookie?.token || ''; 5 | 6 | const songs = [{ mxid: Number(params.mxid), op: 1, ot: Number(params.time || Math.floor(Date.now() / 1000)), pc: Number(params.pc || 1) }]; 7 | const dataMap = { songs, token, userid }; 8 | 9 | return useAxios({ 10 | url: '/playhistory/v1/upload_songs', 11 | data: dataMap, 12 | encryptType: 'android', 13 | method: 'POST', 14 | params: { plat: 3 }, 15 | cookie: params?.cookie || {}, 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /module/playlist_add.js: -------------------------------------------------------------------------------- 1 | // 收藏歌单 2 | module.exports = (params, useAxios) => { 3 | const userid = params?.userid || params?.cookie?.userid || 0; 4 | const token = params?.token || params.cookie?.token || ''; 5 | const clienttime = Math.floor(Date.now() / 1000); 6 | 7 | const dataMap = { 8 | userid, 9 | token, 10 | total_ver: 0, 11 | name: params.name, 12 | type: params.type || 0, 13 | source: params.source === 0 ? 0 : params.source || 1, 14 | is_pri: 0, 15 | list_create_userid: params.list_create_userid, 16 | list_create_listid: params.list_create_listid, 17 | list_create_gid: params.list_create_gid || '', 18 | from_shupinmv: 0, 19 | }; 20 | 21 | if (params.type === 0) { 22 | dataMap['is_pri'] = params.is_pri || 0; 23 | } 24 | 25 | return useAxios({ 26 | url: '/cloudlist.service/v5/add_list', 27 | data: dataMap, 28 | params: params.type === 0 ? { last_time: clienttime, last_area: 'gztx', userid, token } : {}, 29 | method: 'post', 30 | encryptType: 'android', 31 | cookie: params?.cookie || {}, 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /module/playlist_del.js: -------------------------------------------------------------------------------- 1 | const { playlistAesEncrypt, playlistAesDecrypt, rsaEncrypt2, signParamsKey, clientver, appid } = require('../util'); 2 | 3 | // 取消收藏歌单 4 | module.exports = (params, useAxios) => { 5 | const answer = { status: 500, body: {}, cookie: [] }; 6 | return new Promise(async (resolve) => { 7 | try { 8 | const userid = params?.userid || params?.cookie?.userid || 0; 9 | const token = params?.token || params.cookie?.token || ''; 10 | const clienttime = Math.floor(Date.now() / 1000); 11 | 12 | const dataMap = { 13 | listid: Number(params.listid), 14 | total_ver: 0, 15 | type: 1, 16 | }; 17 | 18 | const aesEncrypt = playlistAesEncrypt(dataMap); 19 | 20 | const p = rsaEncrypt2({ aes: aesEncrypt.key, uid: userid, token }).toUpperCase(); 21 | 22 | const paramsMap = { 23 | clienttime, 24 | key: signParamsKey(clienttime.toString()), 25 | last_area: 'gztx', 26 | clientver, 27 | appid, 28 | last_time: clienttime, 29 | p, 30 | }; 31 | 32 | const respone = await useAxios({ 33 | url: '/v2/delete_list', 34 | params: paramsMap, 35 | data: aesEncrypt.str, 36 | method: 'post', 37 | encryptType: 'android', 38 | headers: { 'x-router': 'cloudlist.service.kugou.com' }, 39 | responseType: 'arraybuffer', 40 | cookie: params?.cookie || {}, 41 | }); 42 | 43 | respone.body = playlistAesDecrypt({ str: respone.body.toString('base64'), key: aesEncrypt.key }); 44 | 45 | resolve(respone); 46 | } catch (error) { 47 | console.log(error); 48 | answer.body = error; 49 | resolve(answer); 50 | } 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /module/playlist_detail.js: -------------------------------------------------------------------------------- 1 | // 获取歌单详情 2 | 3 | module.exports = (params, useAxios) => { 4 | const data = (params?.ids || '').split(',').map(s => ({'global_collection_id': s })); 5 | 6 | const dataMap = { 7 | data, 8 | userid: params?.userid || params?.cookie?.userid || 0, 9 | token: params?.token || params?.cookie?.token || '' 10 | }; 11 | 12 | return useAxios({ 13 | url: '/v3/get_list_info', 14 | method: 'POST', 15 | encryptType: 'android', 16 | data: dataMap, 17 | cookie: params?.cookie || {}, 18 | headers: {'x-router': 'pubsongs.kugou.com'} 19 | }) 20 | 21 | 22 | } -------------------------------------------------------------------------------- /module/playlist_effect.js: -------------------------------------------------------------------------------- 1 | // 获取音效歌单 2 | 3 | module.exports = (params, useAxios) => { 4 | const dataMap = { 5 | page: params?.page || 1, 6 | pagesize: params?.pagesize || 30, 7 | }; 8 | 9 | return useAxios({ 10 | url: '/pubsongs/v1/get_sound_effect_list', 11 | method: 'POST', 12 | encryptType: 'android', 13 | data: dataMap, 14 | cookie: params?.cookie || {}, 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /module/playlist_similar.js: -------------------------------------------------------------------------------- 1 | const { appid, clientver, signParamsKey } = require('../util'); 2 | 3 | module.exports = (params, useAxios) => { 4 | const data = (params?.ids || '').split(',').map((s) => ({ 'global_collection_id': s })); 5 | const clienttime = Date.now(); 6 | 7 | const dataMap = { 8 | appid, 9 | clientver, 10 | clienttime, 11 | key: signParamsKey(clienttime), 12 | userid: params?.userid || params?.cookie?.userid || 0, 13 | ugc: 1, 14 | show_list: 1, 15 | need_songs: 1, 16 | data, 17 | }; 18 | 19 | return useAxios({ 20 | url: '/pubsongs/v1/kmr_get_similar_lists', 21 | method: 'POST', 22 | encryptType: 'android', 23 | data: dataMap, 24 | cookie: params?.cookie || {}, 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /module/playlist_tags.js: -------------------------------------------------------------------------------- 1 | // 获取歌单分类 2 | 3 | module.exports = (params, useAxios) => { 4 | const dataMap = { 5 | tag_type: 'collection', 6 | tag_id: 0, 7 | source: 3, 8 | }; 9 | 10 | return useAxios({ 11 | url: '/pubsongs/v1/get_tags_by_type', 12 | method: 'POST', 13 | encryptType: 'android', 14 | data: dataMap, 15 | cookie: params?.cookie || {}, 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /module/playlist_track_all.js: -------------------------------------------------------------------------------- 1 | // 获取歌单所有歌曲 2 | module.exports = (params, useAxios) => { 3 | const pagesize = params?.pagesize || 30; 4 | const paramsMap = { 5 | area_code: 1, 6 | begin_idx: (Number(params.page || 1) - 1) * pagesize, 7 | plat: 1, 8 | type: 1, 9 | // module: 'NONE', 10 | mode: 1, 11 | personal_switch: 1, 12 | extend_fields: 'abtags,hot_cmt,popularization', 13 | // page: params?.page || 1, 14 | pagesize , 15 | global_collection_id: params?.id, 16 | }; 17 | 18 | return useAxios({ 19 | url: '/pubsongs/v2/get_other_list_file_nofilt', 20 | method: 'GET', 21 | encryptType: 'android', 22 | params: paramsMap, 23 | cookie: params?.cookie || {}, 24 | // headers: { 'x-router': 'pubsongscdn.kugou.com' }, 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /module/playlist_track_all_new.js: -------------------------------------------------------------------------------- 1 | // 获取歌单所有歌曲 2 | module.exports = (params, useAxios) => { 3 | const userid = params?.userid || params?.cookie?.userid || '0'; 4 | const token = params?.token || params?.cookie?.token || '0'; 5 | const dataMap = { 6 | listid: params.listid, 7 | userid, 8 | area_code: 1, 9 | show_relate_goods: 0, 10 | pagesize: params.pagesize || 30, 11 | allplatform: 1, 12 | show_cover: 1, 13 | type: 0, 14 | token, 15 | page: params.page || 1, 16 | }; 17 | 18 | return useAxios({ 19 | url: '/v4/get_list_all_file', 20 | method: 'post', 21 | encryptType: 'android', 22 | data: dataMap, 23 | cookie: params?.cookie || {}, 24 | headers: { 'x-router': 'cloudlist.service.kugou.com' }, 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /module/playlist_tracks_add.js: -------------------------------------------------------------------------------- 1 | // 对歌单添加歌曲 2 | // listid, 歌单listid 3 | module.exports = (params, useAxios) => { 4 | const userid = params?.userid || params?.cookie?.userid || 0; 5 | const token = params?.token || params.cookie?.token || ''; 6 | const clienttime = Math.floor(Date.now() / 1000); 7 | 8 | const resource = (params.data || '').split(',').map((s) => { 9 | const data = s.split('|'); 10 | return { 11 | number: 1, 12 | name: data[0] || '', 13 | hash: data[1] || '', 14 | size: 0, 15 | sort: 0, 16 | timelen: 0, 17 | bitrate: 0, 18 | album_id: Number(data[2] || 0), 19 | mixsongid: Number(data[3] || 0), 20 | }; 21 | }); 22 | 23 | const dataMap = { 24 | userid, 25 | token, 26 | listid: params.listid, 27 | list_ver: 0, 28 | type: 0, 29 | slow_upload: 1, 30 | scene: 'false;null', 31 | data: resource, 32 | }; 33 | 34 | return useAxios({ 35 | url: '/cloudlist.service/v6/add_song', 36 | data: dataMap, 37 | params: { last_time: clienttime, last_area: 'gztx', userid, token }, 38 | method: 'post', 39 | encryptType: 'android', 40 | cookie: params?.cookie || {}, 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /module/playlist_tracks_del.js: -------------------------------------------------------------------------------- 1 | // 对歌单删除歌曲 2 | 3 | module.exports = (params, useAxios) => { 4 | const userid = params?.userid || params?.cookie?.userid || 0; 5 | const token = params?.token || params.cookie?.token || ''; 6 | 7 | const resource = (params.fileids || '').split(',').map((s) => ({ fileid: Number(s) })); 8 | 9 | const dataMap = { 10 | listid: params.listid, 11 | userid, 12 | data: resource, 13 | type: 0, 14 | token, 15 | list_ver: 0, 16 | }; 17 | 18 | return useAxios({ 19 | url: '/v4/delete_songs', 20 | data: dataMap, 21 | method: 'post', 22 | encryptType: 'android', 23 | cookie: params?.cookie || {}, 24 | headers: { 'x-router': 'cloudlist.service.kugou.com' }, 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /module/privilege_lite.js: -------------------------------------------------------------------------------- 1 | // 获取歌曲信息 2 | const { appid, clientver } = require('../util'); 3 | 4 | module.exports = (params, useAxios) => { 5 | const resource = (params?.hash || '').split(',').map((s) => ({ type: 'audio', page_id: 0, hash: s, album_id: 0 })); 6 | (params?.album_id || '').split(',').forEach((s, l) => (resource[l]['album_id'] = s)); 7 | 8 | const dataMap = { 9 | appid, 10 | area_code: 1, 11 | behavior: 'play', 12 | clientver, 13 | need_hash_offset: 1, 14 | relate: 1, 15 | support_verify: 1, 16 | resource, 17 | qualities: ['128', '320', 'flac', 'high', 'viper_atmos', 'viper_tape', 'viper_clear'], 18 | }; 19 | 20 | return useAxios({ 21 | url: '/v2/get_res_privilege/lite', 22 | data: dataMap, 23 | method: 'post', 24 | encryptType: 'android', 25 | cookie: params?.cookie || {}, 26 | headers: { 'x-router': 'media.store.kugou.com', 'Content-Type': 'application/json' }, 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /module/rank_audio.js: -------------------------------------------------------------------------------- 1 | // 获取排行榜音乐列表 2 | module.exports = (params, useAxios) => { 3 | const dataMap = { 4 | show_portrait_mv: 1, 5 | show_type_total: 1, 6 | filter_original_remarks: 1, 7 | area_code: 1, 8 | pagesize: params.pagesize || 30, 9 | rank_cid: params.rank_cid || 0, 10 | type: 1, 11 | page: params.page || 1, 12 | rank_id: params.rankid, 13 | }; 14 | return useAxios({ 15 | url: '/openapi/kmr/v2/rank/audio', 16 | method: 'post', 17 | data: dataMap, 18 | encryptType: 'android', 19 | cookie: params?.cookie || {}, 20 | headers: { 'kg-tid': '369' }, 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /module/rank_info.js: -------------------------------------------------------------------------------- 1 | // 获取排行榜详情 2 | module.exports = (params, useAxios) => { 3 | const parmasMap = { 4 | rank_cid: params.rank_cid || 0, 5 | rankid: params.rankid, 6 | with_album_img: params.album_img || 1, 7 | zone: params.zone || '', 8 | }; 9 | 10 | return useAxios({ 11 | url: '/ocean/v6/rank/info', 12 | method: 'get', 13 | encryptType: 'android', 14 | params: parmasMap, 15 | cookie: params?.cookie || {}, 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /module/rank_list.js: -------------------------------------------------------------------------------- 1 | // 获取排行榜列表 2 | 3 | module.exports = (params, useAxios) => { 4 | const parmasMap = { 5 | plat: 2, 6 | withsong: params.withsong || 1, 7 | parentid: 0, 8 | }; 9 | 10 | return useAxios({ 11 | url: '/ocean/v6/rank/list', 12 | method: 'get', 13 | encryptType: 'android', 14 | params: parmasMap, 15 | cookie: params?.cookie || {}, 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /module/rank_top.js: -------------------------------------------------------------------------------- 1 | // 获取排行榜推荐列表 2 | 3 | module.exports = (params, useAxios) => { 4 | return useAxios({ 5 | url: '/mobileservice/api/v5/rank/rec_rank_list', 6 | method: 'get', 7 | encryptType: 'android', 8 | cookie: params?.cookie || {}, 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /module/rank_vol.js: -------------------------------------------------------------------------------- 1 | // 获取排行榜往期列表 2 | module.exports = (params, useAxios) => { 3 | const parmasMap = { 4 | rank_cid: params.rank_cid || 0, 5 | rankid: params.rankid, 6 | ranktype: 1, 7 | type: 0, 8 | plat: 2, 9 | }; 10 | 11 | return useAxios({ 12 | url: '/ocean/v6/rank/vol', 13 | method: 'get', 14 | encryptType: 'android', 15 | params: parmasMap, 16 | cookie: params?.cookie || {}, 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /module/recommend_songs.js: -------------------------------------------------------------------------------- 1 | // 每日推荐歌曲 2 | 3 | module.exports = (params, useAxios) => { 4 | const dataMap = { 5 | platform: params?.platform || 'android', 6 | userid: params?.userid || params?.cookie?.userid || '0', 7 | }; 8 | 9 | return useAxios({ 10 | url: '/everyday_song_recommend', 11 | method: 'POST', 12 | data: dataMap, 13 | encryptType: 'android', 14 | cookie: params?.cookie || {}, 15 | headers: { 'x-router': 'everydayrec.service.kugou.com' }, 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /module/register_dev.js: -------------------------------------------------------------------------------- 1 | // 获取音乐详情 2 | module.exports = (params, useAxios) => { 3 | const dataMap = { 4 | mid: params?.mid || '', 5 | uuid: params?.uuid || '', 6 | appid: '1014', 7 | userid: params?.userid || '0', 8 | }; 9 | 10 | return useAxios({ 11 | baseURL: 'https://userservice.kugou.com', 12 | url: '/risk/v1/r_register_dev', 13 | method: 'POST', 14 | data: Buffer.from(JSON.stringify(dataMap)).toString('base64'), 15 | params: { ...dataMap, 'p.token': '', platid: 4 }, 16 | encryptType: 'register', 17 | cookie: params?.cookie || {}, 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /module/scene_audio_list.js: -------------------------------------------------------------------------------- 1 | const { appid, clientver } = require('../util'); 2 | 3 | module.exports = (params, useAxios) => { 4 | const userid = params?.userid || params?.cookie?.userid || 0; 5 | const token = params?.token || params.cookie?.token || ''; 6 | 7 | const dataMap = { 8 | appid, 9 | clientver, 10 | token, 11 | userid, 12 | }; 13 | 14 | return useAxios({ 15 | url: '/scene/v1/scene/audio_list', 16 | method: 'POST', 17 | encryptType: 'android', 18 | params: { scene_id: params.id, module_id: params.module_id, tag: params.tag, page: params.page || 1, page_size: params.pagesize || 30 }, 19 | data: dataMap, 20 | cookie: params?.cookie || {}, 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /module/scene_collection_list.js: -------------------------------------------------------------------------------- 1 | const { appid, clientver } = require('../util'); 2 | 3 | module.exports = (params, useAxios) => { 4 | const userid = params?.userid || params?.cookie?.userid || 0; 5 | const token = params?.token || params.cookie?.token || ''; 6 | 7 | const dataMap = { 8 | appid, 9 | clientver, 10 | token, 11 | userid, 12 | tag_id: params.tag_id, 13 | page: params.page || 1, 14 | page_size: params.pagesize || 30, 15 | exposed_data: [], 16 | }; 17 | 18 | return useAxios({ 19 | url: '/scene/v1/distribution/collection_list', 20 | method: 'POST', 21 | encryptType: 'android', 22 | data: dataMap, 23 | cookie: params?.cookie || {}, 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /module/scene_lists.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | return useAxios({ 3 | url: '/scene/v1/scene/list', 4 | method: 'GET', 5 | encryptType: 'android', 6 | cookie: params?.cookie || {}, 7 | }); 8 | }; 9 | -------------------------------------------------------------------------------- /module/scene_lists_v2.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | const userid = params?.userid || params?.cookie?.userid || '0'; 3 | 4 | const sortType = { rec: 1, hot: 2, new: 3 }; 5 | 6 | return useAxios({ 7 | url: '/scene/v1/scene/list_v2', 8 | params: { 9 | scene_id: params.id, 10 | page: params.page || 1, 11 | pagesize: params.pagesize || 30, 12 | sort_type: sortType[params.sort || 'rec'] || 1, 13 | kugouid: userid, 14 | }, 15 | data: { exposure: [] }, 16 | method: 'POST', 17 | encryptType: 'android', 18 | cookie: params?.cookie || {}, 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /module/scene_module.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | return useAxios({ 3 | url: '/scene/v1/scene/module', 4 | params: { scene_id: params.id }, 5 | method: 'POST', 6 | encryptType: 'android', 7 | cookie: params?.cookie || {}, 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /module/scene_module_info.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | return useAxios({ 3 | url: '/scene/v1/scene/module_info', 4 | params: { scene_id: params.id, module_id: params.module_id }, 5 | method: 'GET', 6 | encryptType: 'android', 7 | cookie: params?.cookie || {}, 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /module/scene_music.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | return useAxios({ 3 | url: '/genesisapi/v1/scene_music/rec_music', 4 | params: { scene_id: params.id, page: params.page || 1, pagesize: params.pagesize || 30 }, 5 | data: { exposure: [] }, 6 | method: 'POST', 7 | encryptType: 'android', 8 | cookie: params?.cookie || {}, 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /module/scene_video_list.js: -------------------------------------------------------------------------------- 1 | const { appid, clientver } = require('../util'); 2 | 3 | module.exports = (params, useAxios) => { 4 | const userid = params?.userid || params?.cookie?.userid || 0; 5 | const token = params?.token || params.cookie?.token || ''; 6 | 7 | const dataMap = { 8 | appid, 9 | clientver, 10 | token, 11 | userid, 12 | tag_id: params.tag_id, 13 | page: params.page || 1, 14 | page_size: params.pagesize || 30, 15 | exposed_data: [], 16 | }; 17 | 18 | return useAxios({ 19 | url: '/scene/v1/distribution/video_list', 20 | method: 'POST', 21 | encryptType: 'android', 22 | data: dataMap, 23 | cookie: params?.cookie || {}, 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /module/search.js: -------------------------------------------------------------------------------- 1 | // 搜索 2 | module.exports = (params, useAxios) => { 3 | const dataMap = { 4 | // token: '', 5 | albumhide: 0, 6 | iscorrection: 1, 7 | keyword: params?.keywords || '', 8 | nocollect: 0, 9 | page: params?.page || 1, 10 | pagesize: params?.pagesize || 30, 11 | platform: 'AndroidFilter', 12 | }; 13 | 14 | 15 | const type = ['special', 'lyric', 'song', 'album', 'author', 'mv'].includes(params.type) ? params.type : 'song'; 16 | 17 | return useAxios({ 18 | url: `/${type === 'song' ? 'v3' : 'v1'}/search/${type}`, 19 | method: 'GET', 20 | params: dataMap, 21 | encryptType: 'android', 22 | headers: { 'x-router': 'complexsearch.kugou.com' }, 23 | cookie: params?.cookie || {}, 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /module/search_complex.js: -------------------------------------------------------------------------------- 1 | // 综合搜索 2 | module.exports = (params, useAxios) => { 3 | const dataMap = { 4 | platform: 'AndroidFilter', 5 | keyword: params.keywords, 6 | page: params?.page || 1, 7 | pagesize: params?.pagesize || 30, 8 | cursor: 0, 9 | }; 10 | 11 | return useAxios({ 12 | baseURL: 'https://complexsearch.kugou.com', 13 | url: '/v6/search/complex', 14 | method: 'GET', 15 | params: dataMap, 16 | encryptType: 'android', 17 | cookie: params?.cookie || {}, 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /module/search_default.js: -------------------------------------------------------------------------------- 1 | const { clientver } = require('../util'); 2 | module.exports = (params, useAxios) => { 3 | const userid = params?.userid || params?.cookie?.userid || 0; 4 | const vip_type = params?.vip_type || params?.cookie?.vip_type || 65530; 5 | const dataMap = { 6 | 'plat': 0, 7 | 'userid': Number(userid), 8 | 'tags': '{}', 9 | 'vip_type': vip_type, 10 | 'm_type': 0, 11 | 'own_ads': {}, 12 | 'ability': '3', 13 | 'sources': [], 14 | 'bitmap': 2, 15 | 'mode': 'normal', 16 | }; 17 | 18 | return useAxios({ 19 | url: '/searchnofocus/v1/search_no_focus_word', 20 | method: 'POST', 21 | data: dataMap, 22 | params: { clientver: 12329 }, 23 | encryptType: 'android', 24 | cookie: params?.cookie || {}, 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /module/search_hot.js: -------------------------------------------------------------------------------- 1 | // 热搜 2 | module.exports = (params, useAxios) => { 3 | const dataMap = { 4 | navid: 1, 5 | plat: 2, 6 | }; 7 | 8 | return useAxios({ 9 | url: '/api/v3/search/hot_tab', 10 | method: 'GET', 11 | params: dataMap, 12 | encryptType: 'android', 13 | cookie: params?.cookie || {}, 14 | headers: {'x-router': 'msearch.kugou.com'} 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /module/search_lyric.js: -------------------------------------------------------------------------------- 1 | // 歌词搜索 2 | const { appid, clientver } = require('../util'); 3 | 4 | module.exports = (params, useAxios) => { 5 | const dataMap = { 6 | album_audio_id: params?.album_audio_id || 0, 7 | appid, 8 | clientver, 9 | duration: 0, 10 | hash: params?.hash || '', 11 | keyword: params?.keywords || '', 12 | lrctxt: 1, 13 | man: params.man ?? 'no', 14 | }; 15 | 16 | return useAxios({ 17 | baseURL: 'https://lyrics.kugou.com', 18 | url: '/v1/search', 19 | method: 'GET', 20 | params: dataMap, 21 | cookie: params?.cookie || {}, 22 | encryptType: 'android', 23 | clearDefaultParams: true, 24 | notSign: true, 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /module/search_mixed.js: -------------------------------------------------------------------------------- 1 | const { cryptoMd5 } = require('../util'); 2 | // 综合搜索 3 | module.exports = (params, useAxios) => { 4 | const time = Date.now(); 5 | 6 | const dataMap = { 7 | ab_tag: 0, 8 | ability: 511, 9 | albumhide: 0, 10 | apiver: 22, 11 | area_code: 1, 12 | clientver: 20125, 13 | cursor: 0, 14 | is_gpay: 0, 15 | iscorrection: 1, 16 | keyword: params.keyword, 17 | nocollect: 0, 18 | osversion: 16.5, 19 | platform: 'IOSFilter', 20 | recver: 2, 21 | req_ai: 1, 22 | requestid: `${cryptoMd5(`bdaa53d04e7475feb9024164a47032f9${time}`)}_0`, 23 | search_ability: 3, 24 | sec_aggre: 1, 25 | sec_aggre_bitmap: 0, 26 | style_type: 3, 27 | tag: 'em', 28 | }; 29 | 30 | return useAxios({ 31 | url: '/v3/search/mixed', 32 | method: 'GET', 33 | params: dataMap, 34 | encryptType: 'android', 35 | headers: { 'x-router': 'complexsearch.kugou.com', 'kg-clienttimems': time.toString() }, 36 | cookie: params?.cookie || {}, 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /module/search_suggest.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | return useAxios({ 3 | url: '/v2/getSearchTip', 4 | method: 'GET', 5 | params: { 6 | keyword: params.keywords, 7 | AlbumTipCount: params.albumTipCount || 10, 8 | CorrectTipCount: params.correctTipCount || 10, 9 | MVTipCount: params.mvTipCount || 10, 10 | MusicTipCount: params.musicTipCount || 10, 11 | radiotip: 1 12 | }, 13 | encryptType: 'android', 14 | cookie: params?.cookie || {}, 15 | headers: { 'x-router': 'searchtip.kugou.com' }, 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /module/server_now.js: -------------------------------------------------------------------------------- 1 | // 获取服务器时间 2 | module.exports = (params, useAxios) => { 3 | const userid = params?.userid || params?.cookie?.userid || 0; 4 | const token = params?.token || params?.cookie?.token || ''; 5 | 6 | return useAxios({ 7 | url: '/v1/server_now', 8 | data: { token, userid }, 9 | encryptType: 'android', 10 | method: 'POST', 11 | params: { plat: 3 }, 12 | cookie: params?.cookie || {}, 13 | headers: { 'x-router': 'usercenter.kugou.com' }, 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /module/sheet_collection.js: -------------------------------------------------------------------------------- 1 | const { srcappid} = require('../util'); 2 | // 乐谱详情 3 | module.exports = (params, useAxios) => { 4 | const paramsMap = { 5 | srcappid, 6 | position: params.position ?? 2 7 | } 8 | return useAxios({ 9 | url: '/miniyueku/v1/opern_square/get_home_module_config', 10 | encryptType: 'web', 11 | method: 'GET', 12 | params: paramsMap, 13 | cookie: params?.cookie || {}, 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /module/sheet_collection_detail.js: -------------------------------------------------------------------------------- 1 | const { srcappid } = require('../util'); 2 | // 乐谱合集详情 3 | module.exports = (params, useAxios) => { 4 | const paramsMap = { 5 | srcappid, 6 | page: params.page ?? 1, 7 | collection_id: params.collection_id 8 | } 9 | return useAxios({ 10 | url: '/miniyueku/v1/opern_square/collection_detail', 11 | encryptType: 'web', 12 | method: 'GET', 13 | params: paramsMap, 14 | cookie: params?.cookie || {}, 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /module/sheet_detail.js: -------------------------------------------------------------------------------- 1 | // 乐谱详情 2 | module.exports = (params, useAxios) => { 3 | const paramsMap = { 4 | id: params.id, 5 | source: params.source, 6 | } 7 | return useAxios({ 8 | baseURL: 'https://miniyueku.kugou.com', 9 | url: '/v1/opern/detail', 10 | encryptType: 'android', 11 | method: 'GET', 12 | params: paramsMap, 13 | cookie: params?.cookie || {}, 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /module/sheet_hot.js: -------------------------------------------------------------------------------- 1 | const { srcappid } = require('../util'); 2 | // 推荐乐谱 3 | module.exports = (params, useAxios) => { 4 | const paramsMap = { 5 | srcappid, 6 | opern_type: params.opern_type ?? 1, 7 | }; 8 | return useAxios({ 9 | url: '/miniyueku/v1/opern_square/get_home_hot_opern', 10 | encryptType: 'web', 11 | method: 'GET', 12 | params: paramsMap, 13 | cookie: params?.cookie || {}, 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /module/sheet_list.js: -------------------------------------------------------------------------------- 1 | // 乐谱列表 // 0全部,1、钢琴,2、吉他,3、鼓谱,98:简谱,99:其他 2 | module.exports = (params, useAxios) => { 3 | const paramsMap = { 4 | album_audio_id: params.album_audio_id, 5 | opern_type: params.opern_type ?? 0, 6 | page: params.page ?? 1, 7 | pagesize: params.pagesize ?? 30, 8 | } 9 | return useAxios({ 10 | url: '/miniyueku/v1/opern/list', 11 | encryptType: 'android', 12 | method: 'GET', 13 | params: paramsMap, 14 | cookie: params?.cookie || {}, 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /module/singer_list.js: -------------------------------------------------------------------------------- 1 | // 获取歌手列表 2 | module.exports = (params, useAxios) => { 3 | return useAxios({ 4 | url: '/ocean/v6/singer/list', 5 | encryptType: 'android', 6 | method: 'GET', 7 | params: { 8 | hotsize: params?.hotsize ?? 200, 9 | musician: 0, 10 | sextype: params?.sextype ?? 0, 11 | showtype: 2, 12 | type: params?.type ?? 0, 13 | }, 14 | cookie: params?.cookie || {}, 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /module/song_climax.js: -------------------------------------------------------------------------------- 1 | // 获取音频高潮部分 2 | 3 | module.exports = (params, useAxios) => { 4 | const data = (params?.hash || '').split(',').map((s) => ({ hash: s })); 5 | 6 | return useAxios({ 7 | baseURL: 'https://expendablekmrcdn.kugou.com', 8 | url: '/v1/audio_climax/audio', 9 | method: 'GET', 10 | params: { data: JSON.stringify(data) }, 11 | encryptType: 'android', 12 | cookie: params?.cookie || {}, 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /module/song_ranking.js: -------------------------------------------------------------------------------- 1 | // 歌曲成绩单 2 | 3 | module.exports = (params, useAxios) => { 4 | 5 | return useAxios({ 6 | url: '/grow/v1/song_ranking/play_page/ranking_info', 7 | method: 'GET', 8 | params: { album_audio_id: params.album_audio_id }, 9 | encryptType: 'android', 10 | cookie: params?.cookie || {}, 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /module/song_ranking_filter.js: -------------------------------------------------------------------------------- 1 | // 歌曲成绩单 2 | 3 | module.exports = (params, useAxios) => { 4 | 5 | return useAxios({ 6 | url: '/grow/v1/song_ranking/unlock/v2/ranking_filter', 7 | method: 'GET', 8 | params: { album_audio_id: params.album_audio_id, page: params.page || 1, pagesize: params.pagesize || 30 }, 9 | encryptType: 'android', 10 | cookie: params?.cookie || {}, 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /module/song_url.js: -------------------------------------------------------------------------------- 1 | // 获取音乐urls 2 | // quality 支持 魔法音乐 3 | // piano 钢琴 4 | // acappella 人声 伴奏 分离 5 | // subwoofer 乐器 6 | // ancient 尤克里里 7 | // dj dj 8 | module.exports = (params, useAxios) => { 9 | const quality = ['piano', 'acappella', 'subwoofer', 'ancient', 'dj', 'surnay'].includes(params.quality) 10 | ? `magic_${params?.quality}` 11 | : params.quality; 12 | 13 | const isLite = process.env.platform === 'lite'; 14 | const page_id = isLite ? 967177915 : 151369488; 15 | const ppage_id = isLite ? '356753938,823673182,967485191' : '463467626,350369493,788954147'; 16 | 17 | const dataMap = { 18 | album_id: Number(params.album_id ?? 0), 19 | area_code: 1, 20 | hash: (params?.hash || '').toLowerCase(), 21 | ssa_flag: 'is_fromtrack', 22 | version: 11040, 23 | page_id, 24 | quality: quality || 128, 25 | album_audio_id: Number(params.album_audio_id ?? 0), 26 | behavior: 'play', 27 | pid: isLite ? 411 : 2, 28 | cmd: 26, 29 | pidversion: 3001, 30 | IsFreePart: params?.free_part ? 1 : 0, //是否返回试听部分(仅部分歌曲) 31 | ppage_id, 32 | cdnBackup: 1, 33 | kcard: 0, 34 | module: '', 35 | }; 36 | 37 | return useAxios({ 38 | url: '/v5/url', 39 | method: 'GET', 40 | params: dataMap, 41 | encryptType: 'android', 42 | headers: { 'x-router': 'trackercdn.kugou.com' }, 43 | encryptKey: true, 44 | notSign: true, 45 | cookie: params?.cookie || {}, 46 | }); 47 | }; 48 | -------------------------------------------------------------------------------- /module/song_url_new.js: -------------------------------------------------------------------------------- 1 | const { signKey, appid, cryptoMd5 } = require('../util'); 2 | module.exports = (params, useAxios) => { 3 | const quality = ['piano', 'acappella', 'subwoofer', 'ancient', 'dj', 'surnay'].includes(params.quality) 4 | ? `magic_${params?.quality}` 5 | : params.quality; 6 | 7 | const vip_token = params?.vip_token || params?.cookie?.vip_token || ''; 8 | const token = params?.token || params?.cookie?.token || ''; 9 | const clienttime_ms = Date.now(); 10 | const userid = Number(params?.userid || params?.cookie?.userid || '0'); 11 | const dfid = params?.dfid || params?.cookie?.dfid || '-'; // 自定义 12 | const vip_type = params?.cookie?.vip_type || params?.vipType || 0; 13 | 14 | const dataMap = { 15 | area_code: '1', 16 | behavior: 'play', 17 | qualities: ['128', '320', 'flac', 'high', 'multitrack', 'viper_atmos', 'viper_tape', 'viper_clear'], 18 | 'resource': { 19 | 'album_audio_id': params.album_audio_id, 20 | 'collect_list_id': '3', 21 | 'collect_time': clienttime_ms, 22 | 'hash': params.hash, 23 | 'id': 0, 24 | 'page_id': 1, 25 | 'type': 'audio', 26 | }, 27 | token, 28 | // tracker_param, 29 | 'tracker_param': { 30 | all_m: 1, 31 | auth: '', 32 | is_free_part: params?.free_part ? 1 : 0, 33 | // key: signKey(params.hash, cryptoMd5(dfid), userid, appid), 34 | key: cryptoMd5(`${params.hash}185672dd44712f60bb1736df5a377e82${appid}${cryptoMd5(dfid)}${userid}`), 35 | module_id: 0, 36 | need_climax: 1, 37 | need_xcdn: 1, 38 | open_time: '', 39 | pid: '411', 40 | pidversion: '3001', 41 | priv_vip_type: '6', 42 | viptoken: vip_token, 43 | }, 44 | userid: `${userid}`, 45 | 'vip': vip_type, 46 | }; 47 | 48 | return useAxios({ 49 | baseURL: 'http://tracker.kugou.com', 50 | url: '/v6/priv_url', 51 | method: 'POST', 52 | data: dataMap, 53 | encryptType: 'android', 54 | cookie: params?.cookie || {}, 55 | }); 56 | }; 57 | -------------------------------------------------------------------------------- /module/theme_music.js: -------------------------------------------------------------------------------- 1 | // 获取主题音乐 2 | 3 | module.exports = (params, useAxios) => { 4 | const dataMap = { 5 | platform: 'android', 6 | clienttime: Math.floor(Date.now() / 1000), 7 | show_theme_category_ids: params?.ids, 8 | userid: params?.userid || params?.cookie?.userid || 0, 9 | module_id: 508, 10 | }; 11 | 12 | return useAxios({ 13 | url: '/everydayrec.service/v1/mul_theme_category_recommend', 14 | encryptType: 'android', 15 | method: 'POST', 16 | data: dataMap, 17 | cookie: params?.cookie || {}, 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /module/theme_music_detail.js: -------------------------------------------------------------------------------- 1 | // 获取主题音乐详情 2 | module.exports = (params, useAxios) => { 3 | const dataMap = { 4 | platform: 'android', 5 | clienttime: Math.floor(Date.now() / 1000), 6 | theme_category_id: params?.id, 7 | show_theme_category_id: 0, 8 | userid: params?.userid || params?.cookie?.userid || 0, 9 | module_id: 508, 10 | }; 11 | 12 | return useAxios({ 13 | url: '/everydayrec.service/v1/theme_category_recommend', 14 | encryptType: 'android', 15 | method: 'POST', 16 | data: dataMap, 17 | cookie: params?.cookie || {}, 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /module/theme_playlist.js: -------------------------------------------------------------------------------- 1 | // 主题歌单 2 | const { clientver } = require('../util'); 3 | module.exports = (params, useAxios) => { 4 | const dataMap = { 5 | platform: 'android', 6 | clientver, 7 | clienttime: Date.now(), 8 | area_code: 1, 9 | module_id: 1, 10 | userid: params?.userid || params?.cookie?.userid || 0, 11 | }; 12 | 13 | return useAxios({ 14 | url: '/v2/getthemelist', 15 | method: 'POST', 16 | encryptType: 'android', 17 | data: dataMap, 18 | cookie: params?.cookie || {}, 19 | headers: { 'x-router': 'everydayrec.service.kugou.com' }, 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /module/theme_playlist_track.js: -------------------------------------------------------------------------------- 1 | // 获取主题歌单说有歌曲 2 | const { clientver } = require('../util'); 3 | module.exports = (params, useAxios) => { 4 | const dataMap = { 5 | platform: 'android', 6 | clientver, 7 | clienttime: Date.now(), 8 | area_code: 1, 9 | module_id: 1, 10 | userid: params?.userid || params?.cookie?.userid || 0, 11 | theme_id: params?.theme_id, 12 | }; 13 | 14 | return useAxios({ 15 | url: '/v2/gettheme_songidlist', 16 | method: 'POST', 17 | encryptType: 'android', 18 | data: dataMap, 19 | cookie: params?.cookie || {}, 20 | headers: { 'x-router': 'everydayrec.service.kugou.com' }, 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /module/top_album.js: -------------------------------------------------------------------------------- 1 | // 推荐专辑 2 | const { apiver } = require('../util'); 3 | 4 | module.exports = (params, useAxios) => { 5 | const dataMap = { 6 | apiver, 7 | token: params?.token || params?.cookie?.token || '', 8 | page: params?.page || 1, 9 | pagesize: params?.pagesize || 30, 10 | withpriv: 1, 11 | }; 12 | 13 | return useAxios({ 14 | url: '/musicadservice/v1/mobile_newalbum_sp', 15 | method: 'POST', 16 | data: dataMap, 17 | encryptType: 'android', 18 | cookie: params?.cookie || {}, 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /module/top_card.js: -------------------------------------------------------------------------------- 1 | // 热门好歌精选 2 | // song_module_1 card_id_1: 精选好歌随心听 || 私人专属好歌 3 | // song_module_2 card_id_2: 经典怀旧金曲 4 | // song_module_3 card_id_3: 热门好歌精选 5 | // song_module_4 card_id_4: 小众宝藏佳作 6 | // song_module_6 card_id_6: vip专属推荐 7 | 8 | const { appid, clientver, cryptoMd5, signParamsKey } = require('../util'); 9 | 10 | module.exports = (params, useAxios) => { 11 | const dfid = params?.dfid || params?.cookie?.dfid || '-'; 12 | const fakem = '60f7ebf1f812edbac3c63a7310001701760f'; 13 | const mid = cryptoMd5(dfid); 14 | const dateTime = Date.now(); 15 | 16 | const dataMap = { 17 | appid, 18 | clientver, 19 | platform: 'android', 20 | clienttime: dateTime, 21 | userid: params?.userid || params?.cookie?.userid || 0, 22 | key: signParamsKey(dateTime), 23 | fakem, 24 | area_code: 1, 25 | mid, 26 | uuid: cryptoMd5(`${dfid}${mid}`), 27 | client_playlist: [], 28 | u_info: 'a0c35cd40af564444b5584c2754dedec', 29 | }; 30 | 31 | return useAxios({ 32 | url: '/singlecardrec.service/v1/single_card_recommend', 33 | encryptType: 'android', 34 | method: 'POST', 35 | data: dataMap, 36 | params: { 'card_id': params?.card_id || 1, fakem, area_code: 1, platform: 'android' }, 37 | cookie: params?.cookie || {}, 38 | }); 39 | }; 40 | -------------------------------------------------------------------------------- /module/top_ip.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {Record} params 4 | * @param {useAxios} useAxios 5 | * @returns 6 | */ 7 | module.exports = (params, useAxios) => { 8 | const dataMap = { tags: {} }; 9 | return new Promise((resolve, reject) => { 10 | useAxios({ 11 | baseURL: 'http://musicadservice.kugou.com', 12 | url: '/v1/daily_recommend', 13 | encryptType: 'android', 14 | method: 'POST', 15 | data: dataMap, 16 | params: { 17 | clientver: 12349, 18 | area_code: 1, 19 | }, 20 | cookie: params?.cookie || {}, 21 | }) 22 | .then((resp) => { 23 | if (resp.body.status == 1) { 24 | const list = Array.isArray(resp.body.data.list) ? [...resp.body.data.list] : []; 25 | list.forEach((s, index) => { 26 | const inner_url = s?.extra?.inner_url; 27 | if (inner_url) { 28 | const findIndex = inner_url.lastIndexOf('ip_id'); 29 | if (findIndex !== -1) { 30 | list[index]['extra']['ip_id'] = Number(inner_url.substring(findIndex + 6)); 31 | } 32 | } 33 | }); 34 | resp.body.data.list = list; 35 | } 36 | resolve(resp); 37 | }) 38 | .catch((e) => reject(e)); 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /module/top_playlist.js: -------------------------------------------------------------------------------- 1 | // 歌单 2 | // categoryid 0:推荐,11292:HI-RES 3 | 4 | const { appid, clientver, signParamsKey, cryptoMd5 } = require('../util'); 5 | 6 | module.exports = (params, useAxios) => { 7 | const dateTime = (Date.now() / 1000).toFixed(0); 8 | const specialRecommend = { 9 | withtag: params?.withtag || 1, 10 | withsong: params?.withsong || 1, 11 | sort: params?.sort || 1, 12 | ugc: 1, 13 | is_selected: 0, 14 | withrecommend: 1, 15 | area_code: 1, 16 | categoryid: params?.category_id || 0, 17 | }; 18 | 19 | const dataMap = { 20 | appid, 21 | mid: cryptoMd5(params?.dfid || params?.cookie?.dfid || '-'), 22 | clientver, 23 | platform: 'android', 24 | clienttime: dateTime, 25 | userid: params?.userid || params?.cookie?.userid || 0, 26 | module_id: params?.module_id || 1, 27 | page: params?.page || 1, 28 | pagesize: params?.pagesize || 30, 29 | key: signParamsKey(dateTime.toString()), 30 | special_recommend: specialRecommend, 31 | req_multi: 1, 32 | retrun_min: 5, 33 | return_special_falg: 1, 34 | }; 35 | 36 | return useAxios({ 37 | url: '/v2/special_recommend', 38 | encryptType: 'android', 39 | method: 'POST', 40 | data: dataMap, 41 | cookie: params?.cookie || {}, 42 | headers: { 'x-router': 'specialrec.service.kugou.com' }, 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /module/top_song.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 新歌速递 3 | */ 4 | module.exports = (params, useAxios) => { 5 | const dataMap = { 6 | rank_id: params?.type || 21608, 7 | userid: params?.userid || params?.cookie?.userid || 0, 8 | page: params?.page || 1, 9 | pagesize: params?.pagesize || 30, 10 | tags: [], 11 | }; 12 | 13 | return useAxios({ 14 | url: '/musicadservice/container/v1/newsong_publish', 15 | encryptType: 'android', 16 | method: 'POST', 17 | data: dataMap, 18 | cookie: params?.cookie || {}, 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /module/user_cloud.js: -------------------------------------------------------------------------------- 1 | const { playlistAesEncrypt, playlistAesDecrypt, rsaEncrypt2, signParamsKey, clientver, appid, cryptoMd5 } = require('../util'); 2 | module.exports = (params, useAxios) => { 3 | const answer = { status: 500, body: {}, cookie: [] }; 4 | return new Promise(async (resolve) => { 5 | try { 6 | const userid = params?.userid || params?.cookie?.userid || 0; 7 | const token = params?.token || params.cookie?.token || ''; 8 | const dfid = params?.cookie?.dfid || '-'; // 自定义 9 | const mid = cryptoMd5(dfid.toString()); // 可以自定义 10 | const clienttime = Math.floor(Date.now() / 1000); 11 | 12 | const dataMap = { 13 | page: params.page ?? 1, 14 | pagesize: params.pagesize ?? 30, 15 | getkmr: 1, 16 | }; 17 | 18 | const aesEncrypt = playlistAesEncrypt(dataMap); 19 | 20 | const p = rsaEncrypt2({ aes: aesEncrypt.key, uid: userid, token }).toUpperCase(); 21 | 22 | const paramsMap = { 23 | clienttime, 24 | mid, 25 | key: signParamsKey(clienttime.toString(), appid), 26 | clientver, 27 | appid, 28 | p, 29 | }; 30 | 31 | const respone = await useAxios({ 32 | baseURL: 'https://mcloudservice.kugou.com', 33 | url: '/v1/get_list', 34 | params: paramsMap, 35 | data: Buffer.from(aesEncrypt.str, 'base64'), 36 | method: 'post', 37 | encryptType: 'android', 38 | responseType: 'arraybuffer', 39 | cookie: params?.cookie || {}, 40 | clearDefaultParams: true, 41 | notSignature: true, 42 | }); 43 | 44 | respone.body = playlistAesDecrypt({ str: respone.body.toString('base64'), key: aesEncrypt.key }); 45 | 46 | resolve(respone); 47 | } catch (error) { 48 | console.log(error); 49 | answer.body = error; 50 | resolve(answer); 51 | } 52 | }); 53 | }; 54 | -------------------------------------------------------------------------------- /module/user_cloud_url.js: -------------------------------------------------------------------------------- 1 | // 获取云盘音乐url 2 | const { signCloudKey } = require('../util'); 3 | module.exports = (params, useAxios) => { 4 | const hash = String(params.hash).toLocaleLowerCase(); 5 | const paramsMap = { 6 | hash: hash, 7 | ssa_flag: 'is_fromtrack', 8 | version: '20102', 9 | ssl: 0, 10 | album_audio_id: params.album_audio_id ?? 0, 11 | pid: 20026, 12 | audio_id: params.audio_id ?? 0, 13 | kv_id: 2, 14 | key: signCloudKey(hash, 20026), 15 | bucket: 'musicclound', 16 | name: params.name ?? '', 17 | with_res_tag: 0, 18 | }; 19 | 20 | return useAxios({ 21 | url: '/bsstrackercdngz/v2/query_musicclound_url', 22 | encryptType: 'android', 23 | method: 'get', 24 | params: paramsMap, 25 | cookie: params?.cookie, 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /module/user_detail.js: -------------------------------------------------------------------------------- 1 | const { cryptoAesEncrypt, cryptoRSAEncrypt } = require('../util'); 2 | module.exports = (params, useAxios) => { 3 | const token = params?.token || params?.cookie?.token || ''; 4 | const userid = Number(params?.userid || params?.cookie?.userid || '0'); 5 | const clienttime_ms = Math.floor(Date.now() / 1000); 6 | const pk = cryptoRSAEncrypt({ token, clienttime: clienttime_ms }).toUpperCase(); 7 | 8 | const dataMap = { 9 | visit_time: clienttime_ms, 10 | usertype: 1, 11 | p: pk, 12 | userid, 13 | }; 14 | 15 | return useAxios({ 16 | url: '/v3/get_my_info', 17 | method: 'POST', 18 | data: dataMap, 19 | params: { plat: 1 }, 20 | encryptType: 'android', 21 | cookie: params?.cookie || {}, 22 | headers: { 'x-router': 'usercenter.kugou.com' }, 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /module/user_follow.js: -------------------------------------------------------------------------------- 1 | const { cryptoRSAEncrypt } = require('../util'); 2 | /** 3 | * 获取用户关注的歌手 4 | */ 5 | module.exports = (params, useAxios) => { 6 | const token = params?.token || params?.cookie?.token || ''; 7 | const userid = params?.userid || params?.cookie?.userid || '0'; 8 | const dateTime = Math.floor(Date.now() / 1000); 9 | const dataMap = { 10 | merge: 2, 11 | need_iden_type: 1, 12 | ext_params: 'k_pic,jumptype,singerid,score', 13 | userid, 14 | type: 0, 15 | id_type: 0, 16 | p: cryptoRSAEncrypt({ clienttime: dateTime, token }).toUpperCase(), 17 | }; 18 | 19 | return useAxios({ 20 | url: '/v4/follow_list', 21 | encryptType: 'android', 22 | method: 'POST', 23 | data: dataMap, 24 | params: { plat: 1 }, 25 | cookie: params?.cookie || {}, 26 | headers: { 'x-router': 'relationuser.kugou.com' }, 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /module/user_history.js: -------------------------------------------------------------------------------- 1 | // 获取用户听歌排行 2 | module.exports = (params, useAxios) => { 3 | const userid = params?.userid || params?.cookie?.userid || 0; 4 | const token = params?.token || params?.cookie?.token || ''; 5 | 6 | const dataMap = { token, userid, source_classify: 'app', to_subdivide_sr: 1 }; 7 | 8 | if (params.bp) dataMap['bp'] = params.bp; 9 | 10 | return useAxios({ 11 | url: '/playhistory/v1/get_songs', 12 | data: dataMap, 13 | encryptType: 'android', 14 | method: 'POST', 15 | cookie: params?.cookie || {}, 16 | }); 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /module/user_listen.js: -------------------------------------------------------------------------------- 1 | const { cryptoRSAEncrypt } = require('../util'); 2 | module.exports = (params, useAxios) => { 3 | const token = params?.token || params?.cookie?.token || ''; 4 | const userid = params?.userid || params?.cookie?.userid || '0'; 5 | 6 | const clienttime = Math.floor(Date.now() / 1000); 7 | 8 | const p = cryptoRSAEncrypt({ clienttime, token }).toUpperCase(); 9 | const dataMap = { 10 | t_userid: userid, 11 | userid, 12 | list_type: params.type || 0, 13 | area_code: 1, 14 | cover: 2, 15 | p, 16 | }; 17 | 18 | return useAxios({ 19 | baseURL: 'https://listenservice.kugou.com', 20 | url: '/v2/get_list', 21 | data: dataMap, 22 | params: { clienttime, plat: 0 }, 23 | method: 'POST', 24 | encryptType: 'android', 25 | cookie: params?.cookie || {}, 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /module/user_playlist.js: -------------------------------------------------------------------------------- 1 | // 获取用户歌单 2 | 3 | module.exports = (params, useAxios) => { 4 | const userid = params?.cookie?.userid || params?.userid || 0; 5 | const token = params?.cookie?.token || params?.token || ''; 6 | 7 | const dataMap = { 8 | userid, 9 | token, 10 | total_ver: 979, 11 | type: 2, 12 | page: params?.page || 1, 13 | pagesize: params?.pagesize || 30, 14 | }; 15 | 16 | return useAxios({ 17 | url: '/v7/get_all_list', 18 | encryptType: 'android', 19 | method: 'post', 20 | data: dataMap, 21 | params: { plat: 1, userid: Number(userid), token }, 22 | cookie: params?.cookie, 23 | headers: { 'x-router': 'cloudlist.service.kugou.com' }, 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /module/user_video_collect.js: -------------------------------------------------------------------------------- 1 | const { cryptoRSAEncrypt } = require('../util'); 2 | /** 3 | * 获取用户关注的歌手 4 | */ 5 | module.exports = (params, useAxios) => { 6 | const token = params?.token || params?.cookie?.token || ''; 7 | const userid = params?.userid || params?.cookie?.userid || '0'; 8 | const dataMap = { 9 | userid, 10 | token, 11 | page: params?.page ?? 1, 12 | pagesize: params?.pagesize ?? 30, 13 | }; 14 | 15 | return useAxios({ 16 | url: '/collectservice/v2/collect_list_mixvideo', 17 | encryptType: 'android', 18 | method: 'POST', 19 | data: dataMap, 20 | params: { plat: 1 }, 21 | cookie: params?.cookie || {}, 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /module/user_video_love.js: -------------------------------------------------------------------------------- 1 | const { cryptoRSAEncrypt } = require('../util'); 2 | /** 3 | * 获取用户关注的歌手 4 | */ 5 | module.exports = (params, useAxios) => { 6 | const userid = params?.userid || params?.cookie?.userid || '0'; 7 | const paramsMap = { 8 | kugouid: userid, 9 | pagesize: params?.pagesize ?? 30, 10 | load_video_info: 1, 11 | p: 1, 12 | plat: 1 13 | }; 14 | 15 | return useAxios({ 16 | url: '/m.comment.service/v1/get_user_like_video', 17 | encryptType: 'android', 18 | method: 'get', 19 | params: paramsMap, 20 | cookie: params?.cookie || {}, 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /module/user_vip_detail.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | return useAxios({ 3 | baseURL: 'https://kugouvip.kugou.com', 4 | url: '/v1/get_union_vip', 5 | method: 'GET', 6 | params: {busi_type: 'concept'}, 7 | encryptType: 'android', 8 | cookie: params?.cookie || {}, 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /module/video_detail.js: -------------------------------------------------------------------------------- 1 | const { appid, clientver, signParamsKey, cryptoMd5 } = require('../util'); 2 | // 获取视频详情 3 | module.exports = (params, useAxios) => { 4 | const dfid = params?.cookie?.dfid || '-'; // 自定义 5 | const mid = cryptoMd5(dfid.toString()); // 可以自定义 6 | const uuid = cryptoMd5(`${dfid}${mid}`); // 可以自定义 7 | const token = params?.token || params?.cookie?.token || ''; 8 | const clienttime = Math.floor(new Date().getTime() / 1000); 9 | 10 | const resource = (params.id || '').split(',').map((s) => ({ video_id: s })); 11 | 12 | const dataMap = { 13 | appid, 14 | clientver, 15 | clienttime, 16 | mid, 17 | uuid, 18 | dfid, 19 | token: token || '', 20 | key: signParamsKey(clienttime.toString()), 21 | show_resolution: 1, 22 | data: resource, 23 | }; 24 | return useAxios({ 25 | url: '/v1/video', 26 | method: 'POST', 27 | data: dataMap, 28 | encryptType: 'android', 29 | cookie: params?.cookie || {}, 30 | clearDefaultParams: true, 31 | headers: { 'x-router': 'kmr.service.kugou.com' }, 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /module/video_privilege.js: -------------------------------------------------------------------------------- 1 | const { cryptoMd5, signParamsKey, clientver, appid } = require('../util'); 2 | // 获取视频特权 3 | 4 | module.exports = (params, useAxios) => { 5 | const dfid = params?.cookie?.dfid || '-'; 6 | const mid = cryptoMd5(dfid); 7 | 8 | const resource = (params?.hash || '').split(',').map((s) => ({ hash: s, id: 0, name: '' })); 9 | 10 | const dataMap = { 11 | appid, 12 | area_code: 1, 13 | behavior: 'play', 14 | clientver, 15 | dfid, 16 | mid, 17 | resource, 18 | token: params?.cookie?.token || '', 19 | userid: params?.cookie?.userid || 0, 20 | vip: params?.cookie?.vip_type || 0, 21 | }; 22 | 23 | return useAxios({ 24 | url: '/v1/get_video_privilege', 25 | method: 'POST', 26 | data: dataMap, 27 | encryptType: 'android', 28 | cookie: params?.cookie || {}, 29 | headers: { 'x-router': 'media.store.kugou.com' }, 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /module/video_url.js: -------------------------------------------------------------------------------- 1 | // 获取视频urls 2 | module.exports = (params, useAxios) => { 3 | const paramsMap = { 4 | backupdomain: 1, 5 | cmd: 123, 6 | ext: 'mp4', 7 | ismp3: 0, 8 | hash: params.hash, 9 | pid: 1, 10 | type: 1, 11 | }; 12 | 13 | return useAxios({ 14 | url: '/v2/interface/index', 15 | method: 'GET', 16 | params: paramsMap, 17 | encryptType: 'android', 18 | encryptKey: true, 19 | headers: { 'x-router': 'trackermv.kugou.com' }, 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /module/youth_channel_all.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | return useAxios({ 3 | url: '/youth/v2/channel/channel_all_list', 4 | encryptType: 'android', 5 | method: 'get', 6 | params: { page: params.page || 1, pagesize: params.pagesize || 30, type: 1}, 7 | cookie: params?.cookie, 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /module/youth_channel_amway.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | return useAxios({ 3 | url: '/youth/api/amway/v2/index', 4 | encryptType: 'android', 5 | method: 'get', 6 | params: { global_collection_id: params.global_collection_id }, 7 | cookie: params?.cookie, 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /module/youth_channel_detail.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | const data = (params.global_collection_id || '').split(',').map((s) => ({ global_collection_id: s })); 3 | return useAxios({ 4 | url: '/youth/api/channel/v1/channel_list_by_id', 5 | encryptType: 'android', 6 | method: 'post', 7 | data: { data }, 8 | cookie: params?.cookie, 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /module/youth_channel_similar.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | return useAxios({ 3 | url: '/youth/v1/channel/get_friendly_channel', 4 | encryptType: 'android', 5 | method: 'post', 6 | data: { 7 | area_code: 1, 8 | playlist_ver: 2, 9 | vip_type: params?.vip_type || params?.cookie?.vip_type || 0, 10 | platform: 'ios' 11 | }, 12 | params: { channel_id: params.channel_id }, 13 | cookie: params?.cookie, 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /module/youth_channel_song.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (params, useAxios) => { 3 | const dataMap = { 4 | global_collection_id: params.global_collection_id, 5 | pagesize: params.pagesize || 30, 6 | page: params.page || 1, 7 | is_filter: 0, 8 | } 9 | return useAxios({ 10 | url: '/youth/api/channel/v1/channel_get_song_audit_passed', 11 | encryptType: 'android', 12 | method: 'get', 13 | params: dataMap, 14 | cookie: params?.cookie, 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /module/youth_channel_song_detail.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (params, useAxios) => { 3 | const dataMap = { 4 | global_collection_id: params.global_collection_id, 5 | fileid: params.fileid 6 | } 7 | return useAxios({ 8 | url: '/youth/v2/post/get_song_detail', 9 | encryptType: 'android', 10 | method: 'get', 11 | params: dataMap, 12 | cookie: params?.cookie, 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /module/youth_channel_sub.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, useAxios) => { 2 | const t = Number(params.t) === 0 ? 0 : 1; 3 | return useAxios({ 4 | url: `/youth/v1/channel${t === 0 ? '_un' : ''}_subscribe`, 5 | encryptType: 'android', 6 | method: t === 0 ? 'delete': 'post', 7 | params: { global_collection_id: params.global_collection_id, source: 1}, 8 | cookie: params?.cookie, 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /module/youth_day_vip.js: -------------------------------------------------------------------------------- 1 | // 领取vip(领取一天) 需要登录 2 | module.exports = (params, useAxios) => { 3 | return useAxios({ 4 | url: '/youth/v1/recharge/receive_vip_listen_song', 5 | encryptType: 'android', 6 | method: 'post', 7 | params: { source_id: 90137 }, 8 | cookie: params?.cookie, 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /module/youth_day_vip_upgrade.js: -------------------------------------------------------------------------------- 1 | //升级vip 2 | module.exports = (params, useAxios) => { 3 | const paramsMap = { 4 | kugouid: Number(params?.userid || params?.cookie?.userid || 0), 5 | ad_type: '1', 6 | } 7 | 8 | return useAxios({ 9 | url: '/youth/v1/listen_song/upgrade_vip_reward', 10 | encryptType: 'android', 11 | method: 'post', 12 | params: paramsMap, 13 | cookie: params?.cookie, 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /module/youth_dynamic.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (params, useAxios) => { 3 | return useAxios({ 4 | url: '/youth/v3/user/get_dynamic', 5 | encryptType: 'android', 6 | method: 'get', 7 | cookie: params?.cookie, 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /module/youth_dynamic_recent.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (params, useAxios) => { 3 | return useAxios({ 4 | url: '/youth/v3/user/recent_dynamic', 5 | encryptType: 'android', 6 | method: 'get', 7 | cookie: params?.cookie, 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /module/youth_listen_song.js: -------------------------------------------------------------------------------- 1 | // 听歌领取vip 需要登录 2 | module.exports = (params, useAxios) => { 3 | const dataMap = { 4 | mixsongid: params?.mixsongid || 666075191 5 | } 6 | 7 | return useAxios({ 8 | url: '/youth/v2/report/listen_song', 9 | data: dataMap, 10 | method: 'POST', 11 | encryptTyPe: 'android', 12 | params: { clientver: 10566 }, 13 | cookie: params?.cookie, 14 | headers: { 15 | "user-agent": "Android13-1070-10566-201-0-ReportPlaySongToServerProtocol-wifi", 16 | "content-type": "application/json; charset=utf-8", 17 | }, 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /module/youth_month_vip_record.js: -------------------------------------------------------------------------------- 1 | // 领取vip 需要登录 2 | module.exports = (params, useAxios) => { 3 | 4 | 5 | return useAxios({ 6 | url: '/youth/v1/activity/get_month_vip_record', 7 | encryptType: 'android', 8 | method: 'get', 9 | 10 | cookie: params?.cookie, 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /module/youth_union_vip.js: -------------------------------------------------------------------------------- 1 | // 领取vip 需要登录 2 | module.exports = (params, useAxios) => { 3 | const paramsMap = { 4 | busi_type: 'concept', 5 | opt_product_types: 'dvip,qvip', 6 | product_type: 'svip', 7 | } 8 | 9 | return useAxios({ 10 | baseURL: 'https://kugouvip.kugou.com', 11 | url: '/v1/get_union_vip', 12 | encryptType: 'android', 13 | method: 'get', 14 | params: paramsMap, 15 | cookie: params?.cookie, 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /module/youth_user_song.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (params, useAxios) => { 3 | const dataMap = { 4 | filter_video: 0, 5 | type: params?.type || 0, 6 | userid: params.userid, 7 | pagesize: params.pagesize || 30, 8 | page: params.page || 1, 9 | is_filter: 0, 10 | } 11 | return useAxios({ 12 | url: '/youth/v1/get_user_song_public', 13 | encryptType: 'android', 14 | method: 'get', 15 | params: dataMap, 16 | cookie: params?.cookie, 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /module/youth_vip.js: -------------------------------------------------------------------------------- 1 | // 领取vip 需要登录 2 | module.exports = (params, useAxios) => { 3 | const time = Date.now(); 4 | const dataMap = { 5 | ad_id: 12307537187, 6 | play_end: time, 7 | play_start: time - 30000, 8 | }; 9 | 10 | return useAxios({ 11 | url: '/youth/v1/ad/play_report', 12 | encryptType: 'android', 13 | method: 'post', 14 | data: dataMap, 15 | cookie: params?.cookie, 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /module/yueku.js: -------------------------------------------------------------------------------- 1 | // 获取安卓乐库相关内容 2 | 3 | module.exports = (params, useAxios) => { 4 | return useAxios({ 5 | url: '/v1/yueku/recommend_v2', 6 | encryptType: 'android', 7 | method: 'GET', 8 | params: { operator: 7, plat: 0, type: 11, area_code: 1, req_multi: 1 }, 9 | cookie: params?.cookie || {}, 10 | headers: { 'x-router': 'service.mobile.kugou.com' }, 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /module/yueku_banner.js: -------------------------------------------------------------------------------- 1 | // 获取乐库下的 banner 2 | 3 | module.exports = (params, useAxios) => { 4 | const dataMap = { 5 | plat: 0, 6 | channel: 201, 7 | operator: 7, 8 | networktype: 2, 9 | userid: params?.userid || params?.cookie?.userid || 0, 10 | vip_type: 0, 11 | m_type: 0, 12 | tags: [], 13 | apiver: 5, 14 | ability: 2, 15 | mode: 'normal', 16 | }; 17 | 18 | return useAxios({ 19 | url: '/ads.gateway/v3/listen_banner', 20 | encryptType: 'android', 21 | method: 'POST', 22 | data: dataMap, 23 | cookie: params?.cookie || {}, 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /module/yueku_fm.js: -------------------------------------------------------------------------------- 1 | // 获取乐库下的 fm 2 | 3 | module.exports = (params, useAxios) => { 4 | return useAxios({ 5 | url: '/v1/time_fm_info', 6 | encryptType: 'android', 7 | method: 'GET', 8 | params: { operator: 7, plat: 0, type: 11, area_code: 1, req_multi: 1 }, 9 | cookie: params?.cookie || {}, 10 | headers: { 'x-router': 'fm.service.kugou.com' }, 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "restartable": "rs", 3 | "ignore": [ 4 | ".git", 5 | "node_modules/", 6 | "dist/", 7 | "coverage/" 8 | ], 9 | "watch": [ 10 | "module/**", 11 | "plugins/**", 12 | "util/**", 13 | "types/**", 14 | "./server.js", 15 | "./index.js", 16 | "./main.js" 17 | ], 18 | "execMap": { 19 | "js": "node " 20 | }, 21 | "env": { 22 | "NODE_ENV": "development" 23 | }, 24 | "ext": "js,json,ts" 25 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kugoumusicapi", 3 | "version": "1.3.9", 4 | "description": "", 5 | "main": "main.js", 6 | "bin": "./app.js", 7 | "types": "./interface.d.ts", 8 | "pkg": { 9 | "scripts": "module/*.js", 10 | "assets": [ 11 | "node_modules/axios", 12 | "node_modules/express", 13 | "node_modules/pako", 14 | "node_modules/qrcode", 15 | "node_modules/safe-decode-uri-component", 16 | "module", 17 | "public" 18 | ] 19 | }, 20 | "scripts": { 21 | "dev": "nodemon --config nodemon.json index.js", 22 | "start": "node app.js", 23 | "pkgwin": "pkg . -t node14-win-x64 -C GZip -o bin/app_win --no-bytecode", 24 | "pkglinux": "pkg . -t node14-linux-x64 -C GZip -o bin/app_linux --no-bytecode", 25 | "pkgmacos": "pkg . -t node14-macos-x64 -C GZip -o bin/app_macos --no-bytecode", 26 | "pkgwin_lite": "pkg . -t node14-win-x64 --options platform=lite -C GZip -o bin/app_win --no-bytecode", 27 | "pkgjs": "esbuild index.js --bundle --minify --outfile=bin/api_js/app.js --platform=node && mkdir -p bin/api_js/util bin/api_js/module && esbuild util/*.js --bundle --minify --outdir=bin/api_js/util --platform=node && esbuild module/*.js --bundle --minify --outdir=bin/api_js/module --platform=node" 28 | }, 29 | "engines": { 30 | "node": ">=12" 31 | }, 32 | "keywords": [ 33 | "酷狗音乐", 34 | "酷狗", 35 | "音乐", 36 | "酷狗音乐nodejs" 37 | ], 38 | "author": "Lines", 39 | "license": "MIT", 40 | "devDependencies": { 41 | "@types/express": "^4.17.17", 42 | "@types/node": "^18.11.9", 43 | "@types/pako": "^2.0.0", 44 | "@types/qrcode": "^1.5.0", 45 | "nodemon": "^3.1.10", 46 | "pkg": "^5.8.1", 47 | "prettier": "^2.7.1", 48 | "ts-node": "^10.9.1", 49 | "typescript": "^4.9.3" 50 | }, 51 | "dependencies": { 52 | "axios": "^1.1.3", 53 | "dotenv": "^16.4.5", 54 | "esbuild": "^0.25.3", 55 | "express": "^4.18.2", 56 | "pako": "^2.1.0", 57 | "qrcode": "^1.5.3", 58 | "safe-decode-uri-component": "^1.2.1" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /public/.gitignore: -------------------------------------------------------------------------------- 1 | audio_match_demo -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 酷狗音乐 API 8 | 9 | 10 | 11 |

酷狗 API

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

例子:

15 | 20 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs'); 2 | const path = require('node:path'); 3 | const express = require('express'); 4 | const decode = require('safe-decode-uri-component'); 5 | const { cookieToJson } = require('./util/util'); 6 | const { createRequest } = require('./util/request'); 7 | const dotenv = require('dotenv'); 8 | const cache = require('./util/apicache').middleware; 9 | 10 | /** 11 | * @typedef {{ 12 | * identifier?: string, 13 | * route: string, 14 | * module: any, 15 | * }}ModuleDefinition 16 | */ 17 | 18 | /** 19 | * @typedef {{ 20 | * server?: import('http').Server, 21 | * }} ExpressExtension 22 | */ 23 | 24 | const envPath = path.join(process.cwd(), '.env'); 25 | if (fs.existsSync(envPath)) { 26 | dotenv.config(envPath); 27 | } 28 | 29 | /** 30 | * 描述:动态获取模块定义 31 | * @param {string} modulesPath 模块路径(TS) 32 | * @param {Record} specificRoute 特定模块定义 33 | * @param {boolean} doRequire 如果为 true,则使用 require 加载模块, 否则打印模块路径, 默认为true 34 | * @return { Promise } 35 | * @example getModuleDefinitions("./module", {"album_new.js": "/album/create"}) 36 | */ 37 | async function getModulesDefinitions(modulesPath, specificRoute, doRequire = true) { 38 | const files = await fs.promises.readdir(modulesPath); 39 | const parseRoute = (fileName) => 40 | specificRoute && fileName in specificRoute ? specificRoute[fileName] : `/${fileName.replace(/\.(js)$/i, '').replace(/_/g, '/')}`; 41 | 42 | return files 43 | .reverse() 44 | .filter((fileName) => fileName.endsWith('.js') && !fileName.startsWith('_')) 45 | .map((fileName) => { 46 | const identifier = fileName.split('.').shift(); 47 | const route = parseRoute(fileName); 48 | const modulePath = path.resolve(modulesPath, fileName); 49 | const module = doRequire ? require(modulePath) : modulePath; 50 | return { identifier, route, module }; 51 | }); 52 | } 53 | 54 | /** 55 | * 创建服务 56 | * @param {ModuleDefinition[]} moduleDefs 57 | * @return {Promise} 58 | */ 59 | async function consturctServer(moduleDefs) { 60 | const app = express(); 61 | const { CORS_ALLOW_ORIGIN } = process.env; 62 | app.set('trust proxy', true); 63 | 64 | /** 65 | * CORS & Preflight request 66 | */ 67 | app.use((req, res, next) => { 68 | if (req.path !== '/' && !req.path.includes('.')) { 69 | res.set({ 70 | 'Access-Control-Allow-Credentials': true, 71 | 'Access-Control-Allow-Origin': CORS_ALLOW_ORIGIN || req.headers.origin || '*', 72 | 'Access-Control-Allow-Headers': 'X-Requested-With,Content-Type', 73 | 'Access-Control-Allow-Methods': 'PUT,POST,GET,DELETE,OPTIONS', 74 | 'Content-Type': 'application/json; charset=utf-8', 75 | }); 76 | } 77 | req.method === 'OPTIONS' ? res.status(204).end() : next(); 78 | }); 79 | 80 | // Cookie Parser 81 | app.use((req, _, next) => { 82 | req.cookies = {}; 83 | (req.headers.cookie || '').split(/;\s+|(? { 84 | const crack = pair.indexOf('='); 85 | if (crack < 1 || crack === pair.length - 1) { 86 | return; 87 | } 88 | req.cookies[decode(pair.slice(0, crack)).trim()] = decode(pair.slice(crack + 1)).trim(); 89 | }); 90 | next(); 91 | }); 92 | 93 | // 将当前平台写入Cookie 以方便查看 94 | app.use((req, res, next) => { 95 | const cookies = (req.headers.cookie || '').split(/;\s+|(? res.statusCode === 200)); 124 | 125 | const moduleDefinitions = moduleDefs || (await getModulesDefinitions(path.join(__dirname, 'module'), {})); 126 | 127 | for (const moduleDef of moduleDefinitions) { 128 | app.use(moduleDef.route, async (req, res) => { 129 | [req.query, req.body].forEach((item) => { 130 | if (typeof item.cookie === 'string') { 131 | item.cookie = cookieToJson(decode(item.cookie)); 132 | } 133 | }); 134 | 135 | const query = Object.assign({}, { cookie: req.cookies }, req.query, { body: req.body }); 136 | 137 | try { 138 | const moduleResponse = await moduleDef.module(query, (config) => { 139 | let ip = req.ip; 140 | if (ip.substring(0, 7) === '::ffff:') { 141 | ip = ip.substring(7); 142 | } 143 | config.ip = ip; 144 | return createRequest(config); 145 | }); 146 | 147 | console.log('[OK]', decode(req.originalUrl)); 148 | 149 | const cookies = moduleResponse.cookie; 150 | if (!query.noCookie) { 151 | if (Array.isArray(cookies) && cookies.length > 0) { 152 | if (req.protocol === 'https') { 153 | // Try to fix CORS SameSite Problem 154 | res.append( 155 | 'Set-Cookie', 156 | cookies.map((cookie) => { 157 | return `${cookie}; PATH=/; SameSite=None; Secure`; 158 | }) 159 | ); 160 | } else { 161 | res.append( 162 | 'Set-Cookie', 163 | cookies.map((cookie) => { 164 | return `${cookie}; PATH=/`; 165 | }) 166 | ); 167 | } 168 | } 169 | } 170 | 171 | res.header(moduleResponse.headers).status(moduleResponse.status).send(moduleResponse.body); 172 | } catch (e) { 173 | const moduleResponse = e; 174 | console.log('[ERR]', decode(req.originalUrl), { 175 | status: moduleResponse.status, 176 | body: moduleResponse.body, 177 | }); 178 | 179 | if (!moduleResponse.body) { 180 | res.status(404).send({ 181 | code: 404, 182 | data: null, 183 | msg: 'Not Found', 184 | }); 185 | return; 186 | } 187 | 188 | res.header(moduleResponse.headers).status(moduleResponse.status).send(moduleResponse.body); 189 | } 190 | }); 191 | } 192 | 193 | return app; 194 | } 195 | 196 | /** 197 | * Serve the KG API 198 | * @returns {Promise} 199 | */ 200 | async function startService() { 201 | const port = Number(process.env.PORT || '3000'); 202 | const host = process.env.HOST || ''; 203 | 204 | const app = await consturctServer(); 205 | 206 | /** @type {import('express').Express & ExpressExtension} */ 207 | const appExt = app; 208 | 209 | appExt.service = app.listen(port, host, () => { 210 | console.log(`server running @ http://${host || 'localhost'}:${port}`); 211 | }); 212 | 213 | return appExt; 214 | } 215 | 216 | module.exports = { startService, getModulesDefinitions }; 217 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "experimentalDecorators": true, 6 | "moduleResolution": "node", 7 | "lib": ["esnext", "dom"], 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "allowJs": true, 11 | "sourceMap": true, 12 | "strict": true, 13 | "noImplicitAny": false, 14 | "noEmit": true, 15 | "baseUrl": "./", 16 | "resolveJsonModule": true, 17 | "rootDir": "./", 18 | "outDir": "./dist", 19 | "forceConsistentCasingInFileNames": true, 20 | "skipLibCheck": true 21 | }, 22 | 23 | "include": ["./interface.d.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /util/apicache.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 该缓存中间件文件来源与 [Binaryify/NeteaseCloudMusicApi](ttps://github.com/Binaryify/NeteaseCloudMusicApi) 3 | */ 4 | 5 | const url = require('node:url'); 6 | const MemoryCache = require('./memory-cache'); 7 | 8 | const t = { 9 | ms: 1, 10 | second: 1000, 11 | minute: 60000, 12 | hour: 3600000, 13 | day: 3600000 * 24, 14 | week: 3600000 * 24 * 7, 15 | month: 3600000 * 24 * 30, 16 | }; 17 | 18 | const instances = []; 19 | 20 | const matches = function (a) { 21 | return function (b) { 22 | return a === b; 23 | }; 24 | }; 25 | 26 | const doesntMatch = function (a) { 27 | return function (b) { 28 | return !matches(a)(b); 29 | }; 30 | }; 31 | 32 | const logDuration = function (d, prefix) { 33 | const str = d > 1000 ? `${(d / 1000).toFixed(2)}sec` : `${d}ms`; 34 | return `\x1B[33m- ${prefix ? `${prefix} ` : ''}${str}\x1B[0m`; 35 | }; 36 | 37 | function getSafeHeaders(res) { 38 | return res.getHeaders ? res.getHeaders() : res._headers; 39 | } 40 | 41 | function ApiCache() { 42 | const memCache = new MemoryCache(); 43 | 44 | const globalOptions = { 45 | debug: false, 46 | defaultDuration: 3600000, 47 | enabled: true, 48 | appendKey: [], 49 | jsonp: false, 50 | redisClient: false, 51 | headerBlacklist: [], 52 | statusCodes: { 53 | include: [], 54 | exclude: [], 55 | }, 56 | events: { 57 | expire: undefined, 58 | }, 59 | headers: { 60 | // 'cache-control': 'no-cache' // example of header overwrite 61 | }, 62 | trackPerformance: false, 63 | }; 64 | 65 | const middlewareOptions = []; 66 | const instance = this; 67 | let index = null; 68 | const timers = {}; 69 | const performanceArray = []; // for tracking cache hit rate 70 | 71 | instances.push(this); 72 | this.id = instances.length; 73 | 74 | function debug(a, b, c, d) { 75 | const arr = ['\x1B[36m[apicache]\x1B[0m', a, b, c, d].filter((arg) => { 76 | return arg !== undefined; 77 | }); 78 | const debugEnv = process.env.DEBUG && process.env.DEBUG.split(',').includes('apicache'); 79 | 80 | return (globalOptions.debug || debugEnv) && console.log.apply(null, arr); 81 | } 82 | 83 | function shouldCacheResponse(request, response, toggle) { 84 | const opt = globalOptions; 85 | const codes = opt.statusCodes; 86 | 87 | if (!response) { 88 | return false; 89 | } 90 | 91 | if (toggle && !toggle(request, response)) { 92 | return false; 93 | } 94 | 95 | if (codes.exclude && codes.exclude.length && codes.exclude.includes(response.statusCode)) { 96 | return false; 97 | } 98 | if (codes.include && codes.include.length && !codes.include.includes(response.statusCode)) { 99 | return false; 100 | } 101 | 102 | return true; 103 | } 104 | 105 | function addIndexEntries(key, req) { 106 | const groupName = req.apicacheGroup; 107 | 108 | if (groupName) { 109 | debug(`group detected "${groupName}"`); 110 | const group = (index.groups[groupName] = index.groups[groupName] || []); 111 | group.unshift(key); 112 | } 113 | 114 | index.all.unshift(key); 115 | } 116 | 117 | function filterBlacklistedHeaders(headers) { 118 | return Object.keys(headers) 119 | .filter((key) => { 120 | return !globalOptions.headerBlacklist.includes(key); 121 | }) 122 | .reduce((acc, header) => { 123 | acc[header] = headers[header]; 124 | return acc; 125 | }, {}); 126 | } 127 | 128 | function createCacheObject(status, headers, data, encoding) { 129 | return { 130 | status, 131 | headers: filterBlacklistedHeaders(headers), 132 | data, 133 | encoding, 134 | timestamp: new Date().getTime() / 1000, // seconds since epoch. This is used to properly decrement max-age headers in cached responses. 135 | }; 136 | } 137 | 138 | function cacheResponse(key, value, duration) { 139 | const redis = globalOptions.redisClient; 140 | const expireCallback = globalOptions.events.expire; 141 | 142 | if (redis && redis.connected) { 143 | try { 144 | redis.hset(key, 'response', JSON.stringify(value)); 145 | redis.hset(key, 'duration', duration); 146 | redis.expire(key, duration / 1000, expireCallback || (() => {})); 147 | } catch (err) { 148 | debug('[apicache] error in redis.hset()'); 149 | } 150 | } else { 151 | memCache.add(key, value, duration, expireCallback); 152 | } 153 | 154 | // add automatic cache clearing from duration, includes max limit on setTimeout 155 | timers[key] = setTimeout(() => { 156 | instance.clear(key, true); 157 | }, Math.min(duration, 2147483647)); 158 | } 159 | 160 | function accumulateContent(res, content) { 161 | if (content) { 162 | if (typeof content == 'string') { 163 | res._apicache.content = (res._apicache.content || '') + content; 164 | } else if (Buffer.isBuffer(content)) { 165 | let oldContent = res._apicache.content; 166 | 167 | if (typeof oldContent === 'string') { 168 | oldContent = !Buffer.from ? new Buffer(oldContent) : Buffer.from(oldContent); 169 | } 170 | 171 | if (!oldContent) { 172 | oldContent = !Buffer.alloc ? Buffer.alloc(0) : Buffer.alloc(0); 173 | } 174 | 175 | res._apicache.content = Buffer.concat([oldContent, content], oldContent.length + content.length); 176 | } else { 177 | res._apicache.content = content; 178 | } 179 | } 180 | } 181 | 182 | function makeResponseCacheable(req, res, next, key, duration, strDuration, toggle) { 183 | // monkeypatch res.end to create cache object 184 | res._apicache = { 185 | write: res.write, 186 | writeHead: res.writeHead, 187 | end: res.end, 188 | cacheable: true, 189 | content: undefined, 190 | }; 191 | 192 | // append header overwrites if applicable 193 | Object.keys(globalOptions.headers).forEach((name) => { 194 | res.setHeader(name, globalOptions.headers[name]); 195 | }); 196 | 197 | res.writeHead = function () { 198 | // add cache control headers 199 | if (!globalOptions.headers['cache-control']) { 200 | if (shouldCacheResponse(req, res, toggle)) { 201 | res.setHeader('cache-control', `max-age=${(duration / 1000).toFixed(0)}`); 202 | } else { 203 | res.setHeader('cache-control', 'no-cache, no-store, must-revalidate'); 204 | } 205 | } 206 | 207 | res._apicache.headers = Object.assign({}, getSafeHeaders(res)); 208 | return res._apicache.writeHead.apply(this, arguments); 209 | }; 210 | 211 | // patch res.write 212 | res.write = function (content) { 213 | accumulateContent(res, content); 214 | return res._apicache.write.apply(this, arguments); 215 | }; 216 | 217 | // patch res.end 218 | res.end = function (content, encoding) { 219 | if (shouldCacheResponse(req, res, toggle)) { 220 | accumulateContent(res, content); 221 | 222 | if (res._apicache.cacheable && res._apicache.content) { 223 | addIndexEntries(key, req); 224 | const headers = res._apicache.headers || getSafeHeaders(res); 225 | const cacheObject = createCacheObject(res.statusCode, headers, res._apicache.content, encoding); 226 | cacheResponse(key, cacheObject, duration); 227 | 228 | // display log entry 229 | const elapsed = new Date() - req.apicacheTimer; 230 | debug(`adding cache entry for "${key}" @ ${strDuration}`, logDuration(elapsed)); 231 | debug('_apicache.headers: ', res._apicache.headers); 232 | debug('res.getHeaders(): ', getSafeHeaders(res)); 233 | debug('cacheObject: ', cacheObject); 234 | } 235 | } 236 | 237 | return res._apicache.end.apply(this, arguments); 238 | }; 239 | 240 | next(); 241 | } 242 | 243 | function sendCachedResponse(request, response, cacheObject, toggle, next, duration) { 244 | if (toggle && !toggle(request, response)) { 245 | return next(); 246 | } 247 | 248 | const headers = getSafeHeaders(response); 249 | 250 | Object.assign(headers, filterBlacklistedHeaders(cacheObject.headers || {}), { 251 | // set properly-decremented max-age header. This ensures that max-age is in sync with the cache expiration. 252 | 'cache-control': `max-age=${Math.max(0, (duration / 1000 - (new Date().getTime() / 1000 - cacheObject.timestamp)).toFixed(0))}`, 253 | }); 254 | 255 | // only embed apicache headers when not in production environment 256 | 257 | // unstringify buffers 258 | let data = cacheObject.data; 259 | if (data && data.type === 'Buffer') { 260 | data = typeof data.data === 'number' ? new Buffer.alloc(data.data) : new Buffer.from(data.data); 261 | } 262 | 263 | // test Etag against If-None-Match for 304 264 | const cachedEtag = cacheObject.headers.etag; 265 | const requestEtag = request.headers['if-none-match']; 266 | 267 | if (requestEtag && cachedEtag === requestEtag) { 268 | response.writeHead(304, headers); 269 | return response.end(); 270 | } 271 | 272 | response.writeHead(cacheObject.status || 200, headers); 273 | 274 | return response.end(data, cacheObject.encoding); 275 | } 276 | 277 | function syncOptions() { 278 | for (const i in middlewareOptions) { 279 | Object.assign(middlewareOptions[i].options, globalOptions, middlewareOptions[i].localOptions); 280 | } 281 | } 282 | 283 | this.clear = function (target, isAutomatic) { 284 | const group = index.groups[target]; 285 | const redis = globalOptions.redisClient; 286 | 287 | if (group) { 288 | debug(`clearing group "${target}"`); 289 | 290 | group.forEach((key) => { 291 | debug(`clearing cached entry for "${key}"`); 292 | clearTimeout(timers[key]); 293 | delete timers[key]; 294 | if (!globalOptions.redisClient) { 295 | memCache.delete(key); 296 | } else { 297 | try { 298 | redis.del(key); 299 | } catch (err) { 300 | console.log(`[apicache] error in redis.del("${key}")`); 301 | } 302 | } 303 | index.all = index.all.filter(doesntMatch(key)); 304 | }); 305 | 306 | delete index.groups[target]; 307 | } else if (target) { 308 | debug(`clearing ${isAutomatic ? 'expired' : 'cached'} entry for "${target}"`); 309 | clearTimeout(timers[target]); 310 | delete timers[target]; 311 | // clear actual cached entry 312 | if (!redis) { 313 | memCache.delete(target); 314 | } else { 315 | try { 316 | redis.del(target); 317 | } catch (err) { 318 | console.log(`[apicache] error in redis.del("${target}")`); 319 | } 320 | } 321 | 322 | // remove from global index 323 | index.all = index.all.filter(doesntMatch(target)); 324 | 325 | // remove target from each group that it may exist in 326 | Object.keys(index.groups).forEach((groupName) => { 327 | index.groups[groupName] = index.groups[groupName].filter(doesntMatch(target)); 328 | 329 | // delete group if now empty 330 | if (!index.groups[groupName].length) { 331 | delete index.groups[groupName]; 332 | } 333 | }); 334 | } else { 335 | debug('clearing entire index'); 336 | 337 | if (!redis) { 338 | memCache.clear(); 339 | } else { 340 | // clear redis keys one by one from internal index to prevent clearing non-apicache entries 341 | index.all.forEach((key) => { 342 | clearTimeout(timers[key]); 343 | delete timers[key]; 344 | try { 345 | redis.del(key); 346 | } catch (err) { 347 | console.log(`[apicache] error in redis.del("${key}")`); 348 | } 349 | }); 350 | } 351 | this.resetIndex(); 352 | } 353 | 354 | return this.getIndex(); 355 | }; 356 | 357 | function parseDuration(duration, defaultDuration) { 358 | if (typeof duration === 'number') { 359 | return duration; 360 | } 361 | 362 | if (typeof duration === 'string') { 363 | const split = duration.match(/^([\d\.,]+)\s?(\w+)$/); 364 | 365 | if (split.length === 3) { 366 | const len = Number.parseFloat(split[1]); 367 | let unit = split[2].replace(/s$/i, '').toLowerCase(); 368 | if (unit === 'm') { 369 | unit = 'ms'; 370 | } 371 | 372 | return (len || 1) * (t[unit] || 0); 373 | } 374 | } 375 | 376 | return defaultDuration; 377 | } 378 | 379 | this.getDuration = function (duration) { 380 | return parseDuration(duration, globalOptions.defaultDuration); 381 | }; 382 | 383 | /** 384 | * Return cache performance statistics (hit rate). Suitable for putting into a route: 385 | * 386 | * app.get('/api/cache/performance', (req, res) => { 387 | * res.json(apicache.getPerformance()) 388 | * }) 389 | * 390 | */ 391 | this.getPerformance = function () { 392 | return performanceArray.map((p) => { 393 | return p.report(); 394 | }); 395 | }; 396 | 397 | this.getIndex = function (group) { 398 | if (group) { 399 | return index.groups[group]; 400 | } else { 401 | return index; 402 | } 403 | }; 404 | 405 | this.middleware = function cache(strDuration, middlewareToggle, localOptions) { 406 | const duration = instance.getDuration(strDuration); 407 | const opt = {}; 408 | 409 | middlewareOptions.push({ 410 | options: opt, 411 | }); 412 | 413 | const options = function (localOptions) { 414 | if (localOptions) { 415 | middlewareOptions.find((middleware) => { 416 | return middleware.options === opt; 417 | }).localOptions = localOptions; 418 | } 419 | 420 | syncOptions(); 421 | 422 | return opt; 423 | }; 424 | 425 | options(localOptions); 426 | 427 | /** 428 | * A Function for non tracking performance 429 | */ 430 | function NOOPCachePerformance() { 431 | this.report = this.hit = this.miss = function () {}; // noop; 432 | } 433 | 434 | /** 435 | * A function for tracking and reporting hit rate. These statistics are returned by the getPerformance() call above. 436 | */ 437 | function CachePerformance() { 438 | /** 439 | * Tracks the hit rate for the last 100 requests. 440 | * If there have been fewer than 100 requests, the hit rate just considers the requests that have happened. 441 | */ 442 | this.hitsLast100 = new Uint8Array(100 / 4); // each hit is 2 bits 443 | 444 | /** 445 | * Tracks the hit rate for the last 1000 requests. 446 | * If there have been fewer than 1000 requests, the hit rate just considers the requests that have happened. 447 | */ 448 | this.hitsLast1000 = new Uint8Array(1000 / 4); // each hit is 2 bits 449 | 450 | /** 451 | * Tracks the hit rate for the last 10000 requests. 452 | * If there have been fewer than 10000 requests, the hit rate just considers the requests that have happened. 453 | */ 454 | this.hitsLast10000 = new Uint8Array(10000 / 4); // each hit is 2 bits 455 | 456 | /** 457 | * Tracks the hit rate for the last 100000 requests. 458 | * If there have been fewer than 100000 requests, the hit rate just considers the requests that have happened. 459 | */ 460 | this.hitsLast100000 = new Uint8Array(100000 / 4); // each hit is 2 bits 461 | 462 | /** 463 | * The number of calls that have passed through the middleware since the server started. 464 | */ 465 | this.callCount = 0; 466 | 467 | /** 468 | * The total number of hits since the server started 469 | */ 470 | this.hitCount = 0; 471 | 472 | /** 473 | * The key from the last cache hit. This is useful in identifying which route these statistics apply to. 474 | */ 475 | this.lastCacheHit = null; 476 | 477 | /** 478 | * The key from the last cache miss. This is useful in identifying which route these statistics apply to. 479 | */ 480 | this.lastCacheMiss = null; 481 | 482 | /** 483 | * Return performance statistics 484 | */ 485 | this.report = function () { 486 | return { 487 | lastCacheHit: this.lastCacheHit, 488 | lastCacheMiss: this.lastCacheMiss, 489 | callCount: this.callCount, 490 | hitCount: this.hitCount, 491 | missCount: this.callCount - this.hitCount, 492 | hitRate: this.callCount == 0 ? null : this.hitCount / this.callCount, 493 | hitRateLast100: this.hitRate(this.hitsLast100), 494 | hitRateLast1000: this.hitRate(this.hitsLast1000), 495 | hitRateLast10000: this.hitRate(this.hitsLast10000), 496 | hitRateLast100000: this.hitRate(this.hitsLast100000), 497 | }; 498 | }; 499 | 500 | /** 501 | * Computes a cache hit rate from an array of hits and misses. 502 | * @param {Uint8Array} array An array representing hits and misses. 503 | * @returns a number between 0 and 1, or null if the array has no hits or misses 504 | */ 505 | this.hitRate = function (array) { 506 | let hits = 0; 507 | let misses = 0; 508 | for (let i = 0; i < array.length; i++) { 509 | let n8 = array[i]; 510 | for (j = 0; j < 4; j++) { 511 | switch (n8 & 3) { 512 | case 1: 513 | hits++; 514 | break; 515 | case 2: 516 | misses++; 517 | break; 518 | } 519 | n8 >>= 2; 520 | } 521 | } 522 | const total = hits + misses; 523 | if (total == 0) { 524 | return null; 525 | } 526 | return hits / total; 527 | }; 528 | 529 | /** 530 | * Record a hit or miss in the given array. It will be recorded at a position determined 531 | * by the current value of the callCount variable. 532 | * @param {Uint8Array} array An array representing hits and misses. 533 | * @param {boolean} hit true for a hit, false for a miss 534 | * Each element in the array is 8 bits, and encodes 4 hit/miss records. 535 | * Each hit or miss is encoded as to bits as follows: 536 | * 00 means no hit or miss has been recorded in these bits 537 | * 01 encodes a hit 538 | * 10 encodes a miss 539 | */ 540 | this.recordHitInArray = function (array, hit) { 541 | const arrayIndex = ~~(this.callCount / 4) % array.length; 542 | const bitOffset = (this.callCount % 4) * 2; // 2 bits per record, 4 records per uint8 array element 543 | const clearMask = ~(3 << bitOffset); 544 | const record = (hit ? 1 : 2) << bitOffset; 545 | array[arrayIndex] = (array[arrayIndex] & clearMask) | record; 546 | }; 547 | 548 | /** 549 | * Records the hit or miss in the tracking arrays and increments the call count. 550 | * @param {boolean} hit true records a hit, false records a miss 551 | */ 552 | this.recordHit = function (hit) { 553 | this.recordHitInArray(this.hitsLast100, hit); 554 | this.recordHitInArray(this.hitsLast1000, hit); 555 | this.recordHitInArray(this.hitsLast10000, hit); 556 | this.recordHitInArray(this.hitsLast100000, hit); 557 | if (hit) { 558 | this.hitCount++; 559 | } 560 | this.callCount++; 561 | }; 562 | 563 | /** 564 | * Records a hit event, setting lastCacheMiss to the given key 565 | * @param {string} key The key that had the cache hit 566 | */ 567 | this.hit = function (key) { 568 | this.recordHit(true); 569 | this.lastCacheHit = key; 570 | }; 571 | 572 | /** 573 | * Records a miss event, setting lastCacheMiss to the given key 574 | * @param {string} key The key that had the cache miss 575 | */ 576 | this.miss = function (key) { 577 | this.recordHit(false); 578 | this.lastCacheMiss = key; 579 | }; 580 | } 581 | 582 | const perf = globalOptions.trackPerformance ? new CachePerformance() : new NOOPCachePerformance(); 583 | 584 | performanceArray.push(perf); 585 | 586 | const cache = function (req, res, next) { 587 | function bypass() { 588 | debug('bypass detected, skipping cache.'); 589 | return next(); 590 | } 591 | 592 | // initial bypass chances 593 | if (!opt.enabled) { 594 | return bypass(); 595 | } 596 | if (req.headers['x-apicache-bypass'] || req.headers['x-apicache-force-fetch']) { 597 | return bypass(); 598 | } 599 | 600 | // REMOVED IN 0.11.1 TO CORRECT MIDDLEWARE TOGGLE EXECUTE ORDER 601 | // if (typeof middlewareToggle === 'function') { 602 | // if (!middlewareToggle(req, res)) return bypass() 603 | // } else if (middlewareToggle !== undefined && !middlewareToggle) { 604 | // return bypass() 605 | // } 606 | 607 | // embed timer 608 | req.apicacheTimer = new Date(); 609 | 610 | // In Express 4.x the url is ambigious based on where a router is mounted. originalUrl will give the full Url 611 | let key = req.hostname + (req.originalUrl || req.url); 612 | // Remove querystring from key if jsonp option is enabled 613 | if (opt.jsonp) { 614 | key = url.parse(key).pathname; 615 | } 616 | 617 | // add appendKey (either custom function or response path) 618 | if (typeof opt.appendKey === 'function') { 619 | key += `$$appendKey=${opt.appendKey(req, res)}`; 620 | } else if (opt.appendKey.length > 0) { 621 | let appendKey = req; 622 | 623 | for (let i = 0; i < opt.appendKey.length; i++) { 624 | appendKey = appendKey[opt.appendKey[i]]; 625 | } 626 | key += `$$appendKey=${appendKey}`; 627 | } 628 | 629 | // attempt cache hit 630 | const redis = opt.redisClient; 631 | const cached = !redis ? memCache.getValue(key) : null; 632 | 633 | // send if cache hit from memory-cache 634 | if (cached) { 635 | const elapsed = new Date() - req.apicacheTimer; 636 | debug('sending cached (memory-cache) version of', key, logDuration(elapsed)); 637 | 638 | perf.hit(key); 639 | return sendCachedResponse(req, res, cached, middlewareToggle, next, duration); 640 | } 641 | 642 | // send if cache hit from redis 643 | if (redis && redis.connected) { 644 | try { 645 | redis.hgetall(key, (err, obj) => { 646 | if (!err && obj && obj.response) { 647 | const elapsed = new Date() - req.apicacheTimer; 648 | debug('sending cached (redis) version of', key, logDuration(elapsed)); 649 | 650 | perf.hit(key); 651 | return sendCachedResponse(req, res, JSON.parse(obj.response), middlewareToggle, next, duration); 652 | } else { 653 | perf.miss(key); 654 | return makeResponseCacheable(req, res, next, key, duration, strDuration, middlewareToggle); 655 | } 656 | }); 657 | } catch (err) { 658 | // bypass redis on error 659 | perf.miss(key); 660 | return makeResponseCacheable(req, res, next, key, duration, strDuration, middlewareToggle); 661 | } 662 | } else { 663 | perf.miss(key); 664 | return makeResponseCacheable(req, res, next, key, duration, strDuration, middlewareToggle); 665 | } 666 | }; 667 | 668 | cache.options = options; 669 | 670 | return cache; 671 | }; 672 | 673 | this.options = function (options) { 674 | if (options) { 675 | Object.assign(globalOptions, options); 676 | syncOptions(); 677 | 678 | if ('defaultDuration' in options) { 679 | // Convert the default duration to a number in milliseconds (if needed) 680 | globalOptions.defaultDuration = parseDuration(globalOptions.defaultDuration, 3600000); 681 | } 682 | 683 | if (globalOptions.trackPerformance) { 684 | debug('WARNING: using trackPerformance flag can cause high memory usage!'); 685 | } 686 | 687 | return this; 688 | } else { 689 | return globalOptions; 690 | } 691 | }; 692 | 693 | this.resetIndex = function () { 694 | index = { 695 | all: [], 696 | groups: {}, 697 | }; 698 | }; 699 | 700 | this.newInstance = function (config) { 701 | const instance = new ApiCache(); 702 | 703 | if (config) { 704 | instance.options(config); 705 | } 706 | 707 | return instance; 708 | }; 709 | 710 | this.clone = function () { 711 | return this.newInstance(this.options()); 712 | }; 713 | 714 | // initialize index 715 | this.resetIndex(); 716 | } 717 | 718 | module.exports = new ApiCache(); 719 | -------------------------------------------------------------------------------- /util/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "wx_appid": "wx79f2c4418704b4f8", 3 | "wx_lite_appid": "wx72b795aca60ad321", 4 | "wx_secret": "4efcab88b700769e376e3f6087b8abc9", 5 | "wx_lite_secret": "33e486041e5e25729a4e3d2da7502f9a", 6 | "srcappid": 2919, 7 | "appid": 1005, 8 | "apiver": 20, 9 | "clientver": 12569, 10 | "liteAppid": 3116, 11 | "liteClientver": 11040 12 | } -------------------------------------------------------------------------------- /util/crypto.js: -------------------------------------------------------------------------------- 1 | const crypto = require('node:crypto'); 2 | const { randomString } = require('./util'); 3 | const publicRasKey = `-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIAG7QOELSYoIJvTFJhMpe1s/gbjDJX51HBNnEl5HXqTW6lQ7LC8jr9fWZTwusknp+sVGzwd40MwP6U5yDE27M/X1+UR4tvOGOqp94TJtQ1EPnWGWXngpeIW5GxoQGao1rmYWAu6oi1z9XkChrsUdC6DJE5E221wf/4WLFxwAtRQIDAQAB\n-----END PUBLIC KEY-----`; 4 | const publicLiteRasKey = `-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDECi0Np2UR87scwrvTr72L6oO01rBbbBPriSDFPxr3Z5syug0O24QyQO8bg27+0+4kBzTBTBOZ/WWU0WryL1JSXRTXLgFVxtzIY41Pe7lPOgsfTCn5kZcvKhYKJesKnnJDNr5/abvTGf+rHG3YRwsCHcQ08/q6ifSioBszvb3QiwIDAQAB\n-----END PUBLIC KEY-----`; 5 | 6 | /** 7 | * @typedef {{str: string, key: string}} AesEncrypt 8 | */ 9 | 10 | /** 11 | * MD5 加密 12 | * @param {BufferLike} data 13 | * @returns {string} 14 | */ 15 | function cryptoMd5(data) { 16 | const buffer = typeof data === 'object' ? JSON.stringify(data) : data; 17 | return crypto.createHash('md5').update(buffer).digest('hex'); 18 | } 19 | 20 | /** 21 | * Sha1 加密 22 | * @param {BufferLike} data 23 | * @returns { string } 24 | */ 25 | function cryptoSha1(data) { 26 | const buffer = typeof data === 'object' ? JSON.stringify(data) : data; 27 | return crypto.createHash('sha1').update(buffer).digest('hex'); 28 | } 29 | 30 | /** 31 | * AES 加密 32 | * @param {BufferLike} data 需要加密的数据 33 | * @param {{ key?:string, iv?: string } | undefined} opt 34 | * @returns {AesEncrypt | string} 35 | */ 36 | function cryptoAesEncrypt(data, opt) { 37 | if (typeof data === 'object') data = JSON.stringify(data); 38 | const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data); 39 | let key, 40 | iv, 41 | tempKey = ''; 42 | if (opt?.key && opt?.iv) { 43 | key = opt.key; 44 | iv = opt.iv; 45 | } else { 46 | tempKey = opt?.key || randomString(16).toLowerCase(); 47 | key = cryptoMd5(tempKey).substring(0, 32); 48 | iv = key.substring(key.length - 16, key.length); 49 | } 50 | 51 | const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); 52 | const dest = Buffer.concat([cipher.update(buffer), cipher.final()]); 53 | if (opt?.key && opt?.key) return dest.toString('hex'); 54 | return { str: dest.toString('hex'), key: tempKey }; 55 | } 56 | 57 | /** 58 | * AES 解密 59 | * @param {string} data 60 | * @param {string} key 61 | * @param {string?} iv 62 | * @returns {string | Record} 63 | */ 64 | function cryptoAesDecrypt(data, key, iv) { 65 | if (!iv) key = cryptoMd5(key).substring(0, 32); 66 | iv = iv || key.substring(key.length - 16, key.length); 67 | const cipher = crypto.createDecipheriv('aes-256-cbc', key, iv); 68 | const dest = Buffer.concat([cipher.update(data, 'hex'), cipher.final()]); 69 | try { 70 | return JSON.parse(dest.toString()); 71 | } catch (e) { 72 | return dest.toString(); 73 | } 74 | } 75 | 76 | /** 77 | * RSA加密 78 | * @param {BufferLike} data 79 | * @param {string?} publicKey 80 | * @returns {string} hex 81 | */ 82 | function cryptoRSAEncrypt(data, publicKey) { 83 | const isLite = process.env.platform === 'lite'; 84 | if (typeof data === 'object') data = JSON.stringify(data); 85 | const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data); 86 | const _buffer = Buffer.concat([buffer, Buffer.alloc(128 - buffer.length)]); 87 | publicKey = publicKey || (isLite ? publicLiteRasKey : publicRasKey); 88 | return crypto.publicEncrypt({ key: publicKey, padding: crypto.constants.RSA_NO_PADDING }, _buffer).toString('hex'); 89 | } 90 | function rsaEncrypt2(data) { 91 | const isLite = process.env.platform === 'lite'; 92 | const useData = typeof data === 'object' ? Buffer.from(JSON.stringify(data)) : Buffer.from(data); 93 | 94 | const buffer = Buffer.concat([useData]); 95 | 96 | return crypto.publicEncrypt({ key: isLite ? publicLiteRasKey : publicRasKey, padding: crypto.constants.RSA_PKCS1_PADDING }, buffer).toString('hex'); 97 | } 98 | 99 | function playlistAesEncrypt(data) { 100 | const useData = typeof data === 'object' ? JSON.stringify(data) : data; 101 | const key = randomString(6).toLocaleLowerCase(); 102 | const encryptKey = cryptoMd5(key).substring(0, 16); 103 | const iv = cryptoMd5(key).substring(16, 32); 104 | 105 | const cipher = crypto.createCipheriv('aes-128-cbc', encryptKey, iv); 106 | const dest = Buffer.concat([cipher.update(useData), cipher.final()]); 107 | return { key, str: dest.toString('base64') }; 108 | } 109 | 110 | function playlistAesDecrypt(data) { 111 | const encryptKey = cryptoMd5(data.key).substring(0, 16); 112 | const iv = cryptoMd5(data.key).substring(16, 32); 113 | 114 | const cipher = crypto.createDecipheriv('aes-128-cbc', encryptKey, iv); 115 | const dest = Buffer.concat([cipher.update(data.str, 'base64'), cipher.final()]); 116 | 117 | const t = dest.toString(); 118 | try { 119 | return JSON.parse(t); 120 | } catch (e) { 121 | return t; 122 | } 123 | } 124 | 125 | module.exports = { 126 | cryptoAesDecrypt, 127 | cryptoAesEncrypt, 128 | cryptoMd5, 129 | cryptoRSAEncrypt, 130 | rsaEncrypt2, 131 | cryptoSha1, 132 | playlistAesEncrypt, 133 | playlistAesDecrypt, 134 | publicLiteRasKey, 135 | publicRasKey, 136 | }; 137 | -------------------------------------------------------------------------------- /util/helper.js: -------------------------------------------------------------------------------- 1 | const { cryptoMd5 } = require('./crypto'); 2 | const { appid: useAppid, liteAppid, clientver: useClientver, liteClientver } = require('./config.json'); 3 | 4 | /** 5 | * web版本 signature 加密 6 | * @param {HelperParams} params 7 | * @returns {string} 加密后的signature 8 | */ 9 | const signatureWebParams = (params) => { 10 | const str = 'NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt'; 11 | const paramsString = Object.keys(params) 12 | .map((key) => `${key}=${params[key]}`) 13 | .sort() 14 | .join(''); 15 | return cryptoMd5(`${str}${paramsString}${str}`); 16 | }; 17 | 18 | /** 19 | * Android版本 signature 加密 20 | * @param {HelperParams} params 21 | * @param {string?} data 22 | * @returns {string} 加密后的signature 23 | */ 24 | const signatureAndroidParams = (params, data) => { 25 | const isLite = process.env.platform === 'lite'; 26 | const str = isLite ? 'LnT6xpN3khm36zse0QzvmgTZ3waWdRSA' : `OIlwieks28dk2k092lksi2UIkp`; 27 | const paramsString = Object.keys(params) 28 | .sort() 29 | .map((key) => `${key}=${typeof params[key] === 'object' ? JSON.stringify(params[key]) : params[key]}`) 30 | .join(''); 31 | return cryptoMd5(`${str}${paramsString}${data || ''}${str}`); 32 | }; 33 | 34 | /** 35 | * Register版本 signature 加密 36 | * @param {HelperParams} params 37 | * @returns {string} 加密后的signature 38 | */ 39 | const signatureRegisterParams = (params) => { 40 | const paramsString = Object.keys(params) 41 | .map((key) => params[key]) 42 | .sort() 43 | .join(''); 44 | return cryptoMd5(`1014${paramsString}1014`); 45 | }; 46 | 47 | /** 48 | * sign 加密 49 | * @param {HelperParams} params 50 | * @param {string?} data 51 | * @returns {string} 加密后的sign 52 | */ 53 | const signParams = (params, data) => { 54 | const str = 'R6snCXJgbCaj9WFRJKefTMIFp0ey6Gza'; 55 | const paramsString = Object.keys(params) 56 | .sort() 57 | .map((key) => `${key}${params[key]}`) 58 | .join(''); 59 | return cryptoMd5(`${paramsString}${data || ''}${str}`); 60 | }; 61 | 62 | /** 63 | * signKey 加密 64 | * @param {string} hash 65 | * @param {string} mid 66 | * @param {(string | number)?} userid 67 | * @param {(string | number)?} appid 68 | * @returns {string} 加密后的sign 69 | */ 70 | const signKey = (hash, mid, userid, appid) => { 71 | const isLite = process.env.platform === 'lite'; 72 | const str = isLite ? '185672dd44712f60bb1736df5a377e82' : '57ae12eb6890223e355ccfcb74edf70d'; 73 | return cryptoMd5(`${hash}${str}${appid || useAppid}${mid}${userid || 0}`); 74 | }; 75 | 76 | /** 77 | * signKey 加密云盘key 78 | * @param {string} hash 79 | * @param {string} pid 80 | * @returns {string} 加密后的sign 81 | */ 82 | const signCloudKey = (hash, pid) => { 83 | const str = 'ebd1ac3134c880bda6a2194537843caa0162e2e7'; 84 | return cryptoMd5(`musicclound${hash}${pid}${str}`); 85 | }; 86 | 87 | /** 88 | * signParams 加密 89 | * @param {string | number} data 90 | * @param {(string | number)?} appid 91 | * @param {(string | number)?} clientver 92 | * @returns {string} 加密后的signParams 93 | */ 94 | 95 | const signParamsKey = (data, appid, clientver) => { 96 | const isLite = process.env.platform === 'lite'; 97 | const str = isLite ? 'LnT6xpN3khm36zse0QzvmgTZ3waWdRSA' : 'OIlwieks28dk2k092lksi2UIkp'; 98 | 99 | appid = appid || (isLite ? liteAppid : useAppid); 100 | 101 | clientver = clientver || (isLite ? liteClientver : useClientver); 102 | 103 | return cryptoMd5(`${appid}${str}${clientver}${data}`); 104 | }; 105 | 106 | module.exports = { 107 | signKey, 108 | signParams, 109 | signParamsKey, 110 | signCloudKey, 111 | signatureAndroidParams, 112 | signatureRegisterParams, 113 | signatureWebParams, 114 | }; 115 | -------------------------------------------------------------------------------- /util/index.js: -------------------------------------------------------------------------------- 1 | const { apiver, appid, wx_appid, wx_lite_appid, wx_secret, wx_lite_secret, srcappid, clientver, liteAppid, liteClientver } = require('./config.json'); 2 | const { 3 | cryptoAesDecrypt, 4 | cryptoAesEncrypt, 5 | cryptoMd5, 6 | cryptoRSAEncrypt, 7 | cryptoSha1, 8 | rsaEncrypt2, 9 | playlistAesEncrypt, 10 | playlistAesDecrypt, 11 | publicLiteRasKey, 12 | publicRasKey, 13 | } = require('./crypto'); 14 | const { createRequest } = require('./request'); 15 | const { signKey, signParams, signParamsKey, signCloudKey, signatureAndroidParams, signatureRegisterParams, signatureWebParams } = require('./helper'); 16 | const { randomString, decodeLyrics, parseCookieString, cookieToJson } = require('./util'); 17 | 18 | // 判断是否为概念版 19 | const isLite = process.env.platform === 'lite'; 20 | const useAppid = isLite ? liteAppid : appid; 21 | const useClientver = isLite ? liteClientver : clientver; 22 | 23 | module.exports = { 24 | apiver, 25 | appid: useAppid, 26 | // liteAppid, 27 | // liteClientver, 28 | wx_appid, 29 | wx_lite_appid, 30 | wx_secret, 31 | wx_lite_secret, 32 | srcappid, 33 | clientver: useClientver, 34 | isLite, 35 | cryptoAesDecrypt, 36 | cryptoAesEncrypt, 37 | cryptoMd5, 38 | cryptoRSAEncrypt, 39 | cryptoSha1, 40 | rsaEncrypt2, 41 | playlistAesEncrypt, 42 | playlistAesDecrypt, 43 | createRequest, 44 | signKey, 45 | signParams, 46 | signParamsKey, 47 | signCloudKey, 48 | signatureAndroidParams, 49 | signatureRegisterParams, 50 | signatureWebParams, 51 | randomString, 52 | decodeLyrics, 53 | parseCookieString, 54 | cookieToJson, 55 | publicLiteRasKey, 56 | publicRasKey, 57 | }; 58 | -------------------------------------------------------------------------------- /util/memory-cache.js: -------------------------------------------------------------------------------- 1 | function MemoryCache() { 2 | this.cache = {}; 3 | this.size = 0; 4 | } 5 | 6 | MemoryCache.prototype.add = function (key, value, time, timeoutCallback) { 7 | const old = this.cache[key]; 8 | const instance = this; 9 | 10 | const entry = { 11 | value, 12 | expire: time + Date.now(), 13 | timeout: setTimeout(function () { 14 | instance.delete(key); 15 | return timeoutCallback && typeof timeoutCallback === 'function' && timeoutCallback(value, key); 16 | }, time), 17 | }; 18 | 19 | this.cache[key] = entry; 20 | this.size = Object.keys(this.cache).length; 21 | 22 | return entry; 23 | }; 24 | 25 | MemoryCache.prototype.delete = function (key) { 26 | const entry = this.cache[key]; 27 | if (entry) clearTimeout(entry.timeout); 28 | 29 | delete this.cache[key]; 30 | 31 | this.size = Object.keys(this.cache).length; 32 | 33 | return null; 34 | }; 35 | 36 | MemoryCache.prototype.get = function (key) { 37 | return this.cache[key]; 38 | }; 39 | 40 | MemoryCache.prototype.getValue = function (key) { 41 | const entry = this.get(key); 42 | 43 | return entry && entry.value; 44 | }; 45 | 46 | MemoryCache.prototype.clear = function () { 47 | Object.keys(this.cache).forEach(function (key) { 48 | this.delete(key); 49 | }, this); 50 | 51 | return true; 52 | }; 53 | 54 | module.exports = MemoryCache; 55 | -------------------------------------------------------------------------------- /util/request.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const { cryptoMd5 } = require('./crypto'); 3 | const { signKey, signatureAndroidParams, signatureRegisterParams, signatureWebParams } = require('./helper'); 4 | const { parseCookieString } = require('./util'); 5 | const { appid, clientver, liteAppid, liteClientver } = require('./config.json'); 6 | 7 | /** 8 | * @typedef {{status: number;body: any, cookie: string[], headers?: Record}} UseAxiosResponse 9 | */ 10 | 11 | /** 12 | * 请求创建 13 | * @param {Object} options 14 | * @param {'get' | 'GET' | 'post' | 'POST'} options.method 请求方法 15 | * @param {string} options.url 请求 url 16 | * @param {string?} options.baseURL 17 | * @param {Record?} options.params 请求参数 18 | * @param {Record?} options.data 请求Body 19 | * @param {Record?} options.headers 请求headers 20 | * @param {'android' | 'web' | 'register'} options.encryptType signature加密方式 21 | * @param {{ [key: string]: string | number }} options.cookie 请求cookie 22 | * @param {boolean?} options.encryptKey 23 | * @param {boolean?} options.clearDefaultParams 清除默认请求参数 24 | * @param {boolean?} options.notSignature 25 | * @param {string?} options.ip 26 | * @param {string?} options.realIP 27 | * @returns {Promise} 28 | */ 29 | const createRequest = (options) => { 30 | return new Promise(async (resolve, reject) => { 31 | const isLite = process.env.platform === 'lite'; 32 | const dfid = options?.cookie?.dfid || '-'; // 自定义 33 | const mid = cryptoMd5(dfid); // 可以自定义 34 | const uuid = cryptoMd5(`${dfid}${mid}`); // 可以自定义 35 | const token = options?.cookie?.token || ''; 36 | const userid = options?.cookie?.userid || 0; 37 | const clienttime = Math.floor(Date.now() / 1000); 38 | const ip = options?.realIP || options?.ip || ''; 39 | const headers = { dfid, clienttime, mid }; 40 | 41 | if (ip) { 42 | headers['X-Real-IP'] = ip; 43 | headers['X-Forwarded-For'] = ip; 44 | } 45 | 46 | const defaultParams = { 47 | dfid, 48 | mid, 49 | uuid, 50 | appid: isLite ? liteAppid : appid, 51 | // apiver: apiver, 52 | clientver: isLite ? liteClientver : clientver, 53 | userid, 54 | clienttime, 55 | }; 56 | 57 | if (token) defaultParams['token'] = token; 58 | const params = options?.clearDefaultParams ? options?.params || {} : Object.assign({}, defaultParams, options?.params || {}); 59 | 60 | headers['clienttime'] = params.clienttime; 61 | 62 | if (options?.encryptKey) { 63 | params['key'] = signKey(params['hash'], params['mid'], params['userid'], params['appid']); 64 | } 65 | 66 | const data = typeof options?.data === 'object' ? JSON.stringify(options.data) : options?.data || ''; 67 | 68 | 69 | if (!params['signature'] && !options.notSignature) { 70 | switch (options?.encryptType) { 71 | case 'register': 72 | params['signature'] = signatureRegisterParams(params); 73 | break; 74 | case 'web': 75 | params['signature'] = signatureWebParams(params); 76 | break; 77 | case 'android': 78 | default: 79 | params['signature'] = signatureAndroidParams(params, data); 80 | break; 81 | } 82 | } 83 | 84 | // options.params = params; 85 | options['params'] = params; 86 | options['baseURL'] = options?.baseURL || 'https://gateway.kugou.com'; 87 | options['headers'] = Object.assign({ 'User-Agent': 'Android15-1070-11083-46-0-DiscoveryDRADProtocol-wifi' }, options?.headers || {}, { dfid, clienttime: params.clienttime, mid }); 88 | 89 | const requestOptions = { 90 | params, 91 | data: options?.data, 92 | method: options.method, 93 | baseURL: options?.baseURL, 94 | url: options.url, 95 | headers: Object.assign({}, options?.headers || {}, headers), 96 | withCredentials: true, 97 | responseType: options.responseType, 98 | }; 99 | 100 | if (options.data) requestOptions.data = options.data; 101 | if (params) requestOptions.params = params; 102 | 103 | if (options.baseURL?.includes('openapicdn')) { 104 | const url = requestOptions.url; 105 | const _params = Object.keys(params) 106 | .map((key) => `${key}=${params[key]}`) 107 | .join('&'); 108 | requestOptions.url = `${url}?${_params}`; 109 | requestOptions.params = {}; 110 | } 111 | 112 | const answer = { status: 500, body: {}, cookie: [], headers: {} }; 113 | try { 114 | const response = await axios(requestOptions); 115 | 116 | const body = response.data; 117 | 118 | answer.cookie = (response.headers['set-cookie'] || []).map((x) => parseCookieString(x)); 119 | 120 | if (response.headers['ssa-code']) { 121 | answer.headers['ssa-code'] = response.headers['ssa-code']; 122 | } 123 | 124 | try { 125 | answer.body = JSON.parse(body.toString()); 126 | } catch (error) { 127 | answer.body = body; 128 | } 129 | 130 | if (response.data.status === 0 || (response.data?.error_code && response.data.error_code !== 0)) { 131 | answer.status = 502; 132 | reject(answer); 133 | } else { 134 | answer.status = 200; 135 | resolve(answer); 136 | } 137 | } catch (e) { 138 | answer.status = 502; 139 | answer.body = { status: 0, msg: e }; 140 | reject(answer); 141 | } 142 | }); 143 | }; 144 | 145 | module.exports = { createRequest }; 146 | -------------------------------------------------------------------------------- /util/util.js: -------------------------------------------------------------------------------- 1 | const pako = require('pako'); 2 | 3 | /** 4 | * 随机字符串 5 | * @param {number} len 6 | * @returns {string} 7 | */ 8 | const randomString = (len = 16) => { 9 | const keyString = '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 10 | const _key = []; 11 | const keyStringArr = keyString.split(''); 12 | for (let i = 0; i < len; i += 1) { 13 | const ceil = Math.ceil((keyStringArr.length - 1) * Math.random()); 14 | const _tmp = keyStringArr[ceil]; 15 | _key.push(_tmp); 16 | } 17 | 18 | return _key.join(''); 19 | }; 20 | 21 | /** 22 | * 格式化cookie 23 | * @param {string} cookie 24 | * @returns {string} 25 | */ 26 | const parseCookieString = (cookie) => { 27 | const t = cookie.replace(/\s*(Domain|domain|path|expires)=[^(;|$)]+;*/g, ''); 28 | return t.replace(/;HttpOnly/g, ''); 29 | }; 30 | 31 | /** 32 | * cookie 转 json 33 | * @param {string} cookie 34 | * @returns 35 | */ 36 | const cookieToJson = (cookie) => { 37 | if (!cookie) return {}; 38 | let cookieArr = cookie.split(';'); 39 | let obj = {}; 40 | cookieArr.forEach((i) => { 41 | let arr = i.split('='); 42 | obj[arr[0]] = arr[1]; 43 | }); 44 | return obj; 45 | }; 46 | 47 | /** 48 | * krc解码 49 | * @param {string | Uint8Array | Buffer} val 50 | * @returns {string} 51 | */ 52 | const decodeLyrics = (val) => { 53 | let bytes = null; 54 | if (val instanceof Uint8Array) bytes = val; 55 | if (Buffer.isBuffer(val)) bytes = new Uint8Array(val); 56 | if (typeof val === 'string') bytes = new Uint8Array(Buffer.from(val, 'base64')); 57 | if (bytes === null) return ''; 58 | const enKey = [64, 71, 97, 119, 94, 50, 116, 71, 81, 54, 49, 45, 206, 210, 110, 105]; 59 | const krcBytes = bytes.slice(4); 60 | const len = krcBytes.byteLength; 61 | for (let index = 0; index < len; index += 1) { 62 | krcBytes[index] = krcBytes[index] ^ enKey[index % enKey.length]; 63 | } 64 | try { 65 | const inflate = pako.inflate(krcBytes); 66 | return Buffer.from(inflate).toString('utf8'); 67 | } catch { 68 | return ''; 69 | } 70 | }; 71 | 72 | module.exports = { 73 | decodeLyrics, 74 | cookieToJson, 75 | parseCookieString, 76 | randomString, 77 | }; 78 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "./index.js", 6 | "use": "@vercel/node" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/(.*)", 12 | "dest": "/" 13 | } 14 | ] 15 | } --------------------------------------------------------------------------------