├── .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://github.com/MakcRe)
6 | 
7 | 
8 | 
9 | 
10 | 
11 | 
12 | 
13 |
14 | [//]: # '
'
15 |
16 | 
17 | 
18 | 
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 | 
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------