mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced.git
synced 2026-10-20 22:51:23 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a5a1242f2 | ||
|
|
6946dc8e14 | ||
|
|
22b78779ca | ||
|
|
35607f5050 | ||
|
|
60af95ac06 | ||
|
|
46073e5f50 | ||
|
|
bf12abe2f1 | ||
|
|
1491b91829 | ||
|
|
63f669d7f8 | ||
|
|
d525204dda | ||
|
|
6eb31984fd | ||
|
|
9faeb0aa25 | ||
|
|
f6db3e76e9 | ||
|
|
9163d48f0a | ||
|
|
1454ee216c | ||
|
|
83ee2cda34 | ||
|
|
3c352b5752 | ||
|
|
60ee927e32 | ||
|
|
eb07a525eb | ||
|
|
6fc4231142 | ||
|
|
fa69736300 | ||
|
|
73a5e9d788 | ||
|
|
dc6f56e8a8 | ||
|
|
2c9da8507d | ||
|
|
1db354464f | ||
|
|
395a80e74b | ||
|
|
6550c7c827 | ||
|
|
c959866436 | ||
|
|
e3a1c041b6 | ||
|
|
e2324e26da | ||
|
|
f01e770c35 | ||
|
|
abb56ec014 | ||
|
|
0300c1b51e | ||
|
|
9896242333 | ||
|
|
e3812be97b | ||
|
|
e7ff0da4ba | ||
|
|
65261fa658 | ||
|
|
cb5b2e7d0f | ||
|
|
f58fd4b50d | ||
|
|
1a5f91a192 | ||
|
|
50cc26f297 | ||
|
|
c92613b19e | ||
|
|
7aacc8990b | ||
|
|
8e934789a1 | ||
|
|
d26e6b0615 | ||
|
|
a9c33de182 | ||
|
|
d0935f6e42 | ||
|
|
3e0337ab94 | ||
|
|
5cf43c48ef | ||
|
|
68567f4a14 | ||
|
|
d0d71bb4f9 | ||
|
|
ec7eff1697 | ||
|
|
1882c3c31e | ||
|
|
f21f5677e8 | ||
|
|
fc5a76d5e2 | ||
|
|
fbc34a1846 | ||
|
|
3ef5ea341b | ||
|
|
4195c41171 | ||
|
|
232cc3d916 | ||
|
|
1b4af30a49 | ||
|
|
39e0e0718d | ||
|
|
01d391ac91 | ||
|
|
85d70ba9dd | ||
|
|
3cfecbcc1f | ||
|
|
c6e9746ed6 | ||
|
|
d6961ecb4d | ||
|
|
02242eecf6 | ||
|
|
269456def3 | ||
|
|
086e379f2c | ||
|
|
38803a18ae | ||
|
|
3980d09bcd | ||
|
|
64d4847350 | ||
|
|
cf74e90c1d | ||
|
|
a366983e99 | ||
|
|
6175d55f89 | ||
|
|
15fa49a2e8 | ||
|
|
f5fe69c863 | ||
|
|
b6be789ccd | ||
|
|
0395d087d9 | ||
|
|
3f679c0d87 | ||
|
|
ba9c6deaee | ||
|
|
2c36daf07a | ||
|
|
5b5f380cd5 | ||
|
|
ff7c5518d1 | ||
|
|
bbb2b9d4a0 | ||
|
|
3188238678 | ||
|
|
8e60324452 | ||
|
|
c9909cddd3 | ||
|
|
dbdbba3f98 | ||
|
|
27aa9a01cb | ||
|
|
30e522018f | ||
|
|
7b2507e43d | ||
|
|
3647fc5fa0 | ||
|
|
8521c198d3 | ||
|
|
4d8c4daa14 | ||
|
|
2e3cf4d451 | ||
|
|
288459cddb | ||
|
|
c1b8a381f3 | ||
|
|
2dd12b88ae | ||
|
|
b6a1f9ce0a | ||
|
|
5991e41c5d | ||
|
|
f61554714c | ||
|
|
266cd8d059 | ||
|
|
89a70b14d4 | ||
|
|
a9332af453 | ||
|
|
cffe68d9ff | ||
|
|
75ec2de66f | ||
|
|
0bd023c188 | ||
|
|
2c82c9b7be | ||
|
|
1320e5780b | ||
|
|
bde1f0864b | ||
|
|
e891287f1a | ||
|
|
59241631db | ||
|
|
29ef4f68c2 | ||
|
|
ee25eb17a6 | ||
|
|
31d2ec9225 | ||
|
|
b45bdd411f | ||
|
|
c14b06f6c3 | ||
|
|
b027aca40a | ||
|
|
b1729f19aa | ||
|
|
755489546b | ||
|
|
92df6e13a0 | ||
|
|
1ad8ab088f | ||
|
|
a09f126ab2 | ||
|
|
872bae1b43 | ||
|
|
83c527af01 | ||
|
|
84efe5d758 | ||
|
|
cfa475bf94 | ||
|
|
4087bafd7d | ||
|
|
8951e32a0e | ||
|
|
33ccc83615 | ||
|
|
2d6173b2aa | ||
|
|
ba7d1a8574 | ||
|
|
26d55255e0 | ||
|
|
4489f10f63 | ||
|
|
2047bdfa65 | ||
|
|
bc9d7b949f | ||
|
|
f08f192328 | ||
|
|
b04b9d51fb | ||
|
|
c91f5f3714 | ||
|
|
7fceec12e4 | ||
|
|
1df147edab | ||
|
|
6d1f75f229 | ||
|
|
2a0b54e442 | ||
|
|
a19418bbb6 | ||
|
|
ce51da9150 | ||
|
|
27002d7665 | ||
|
|
43d1c72f8f | ||
|
|
1d7e9cb6d8 | ||
|
|
609c910d5a | ||
|
|
2b69e9dda5 | ||
|
|
796c2803df | ||
|
|
ab00919a93 | ||
|
|
db2ca44bbc | ||
|
|
6c79f7495b | ||
|
|
fd0608adef | ||
|
|
f6fc452666 | ||
|
|
748d5da8fe | ||
|
|
6613fbf50d | ||
|
|
c8493b93ad | ||
|
|
b93578a879 | ||
|
|
de54c7fe97 | ||
|
|
2e707e2f5e | ||
|
|
2994e2fadd | ||
|
|
eb08ed4ba3 | ||
|
|
d5fedb5c54 | ||
|
|
f2f78b2774 | ||
|
|
871414f2e6 | ||
|
|
2d3f15f0e5 | ||
|
|
598dd8bd34 | ||
|
|
30bd839809 | ||
|
|
d4ee3b88f8 | ||
|
|
77eb154028 | ||
|
|
140ea858c4 | ||
|
|
f7f8c54236 | ||
|
|
bcda215a6c | ||
|
|
ca6834b83f | ||
|
|
c8fba0f7d1 | ||
|
|
546a84b1b8 | ||
|
|
205ac7292f | ||
|
|
1aba68561d | ||
|
|
bd2fdff5ae | ||
|
|
4af8323694 | ||
|
|
a5a1a91dfa | ||
|
|
ee43878834 | ||
|
|
9d18639464 | ||
|
|
fe28194445 | ||
|
|
b14cb9bb24 | ||
|
|
06739b2d6d | ||
|
|
8639c9d0f1 | ||
|
|
bc4a0d4916 | ||
|
|
4e00b94ee6 | ||
|
|
9019a2a4a5 | ||
|
|
3a10a4d753 | ||
|
|
f39aafaff4 | ||
|
|
2a8fb72d1d | ||
|
|
ea6ab4f6f7 | ||
|
|
6fe19a71e3 | ||
|
|
abd3a79ecd | ||
|
|
044ef355c1 | ||
|
|
2944ca42ba | ||
|
|
d4d465e731 | ||
|
|
f49d3cac19 | ||
|
|
2f2755ac8c | ||
|
|
f46c0d4a84 | ||
|
|
14ee86e15b | ||
|
|
5e820187a1 | ||
|
|
781299c1c5 | ||
|
|
8fc6ed1a76 | ||
|
|
8bb68accee | ||
|
|
55858d89d9 | ||
|
|
597ac1498f | ||
|
|
398f410109 | ||
|
|
97980b5ac3 | ||
|
|
65ff21526a | ||
|
|
fb052fdecd | ||
|
|
b6453a1c1b | ||
|
|
0c3da3bcb7 | ||
|
|
80fbd7a620 | ||
|
|
a4c9c0edaf | ||
|
|
19e51bc39a | ||
|
|
a914b5bc11 | ||
|
|
5eeeaeee8a | ||
|
|
1ab9def4d5 | ||
|
|
446ad4c871 | ||
|
|
316826dd81 | ||
|
|
11a240cb18 | ||
|
|
df2d0e6431 | ||
|
|
a1436e1d64 | ||
|
|
1f87ccf080 | ||
|
|
323b1014d2 | ||
|
|
d2351f6f30 | ||
|
|
dd653c07e9 | ||
|
|
173496f305 | ||
|
|
b45739b621 | ||
|
|
f63068b386 | ||
|
|
927b79030b | ||
|
|
6d6eee13b9 | ||
|
|
a7f086cf8f | ||
|
|
a38d689de4 | ||
|
|
4ffb20c4ff | ||
|
|
84c19ee237 | ||
|
|
f3d95f17b4 | ||
|
|
6d33efee47 | ||
|
|
acc458f6cb | ||
|
|
d78a20ea11 | ||
|
|
2cbd17a6d7 | ||
|
|
240d8042fb | ||
|
|
f9f6db3fee | ||
|
|
89e7c98412 | ||
|
|
16bf878135 | ||
|
|
8dcbe4db9d | ||
|
|
a1fbcf7008 | ||
|
|
c9c881ba7d | ||
|
|
0e0777d175 | ||
|
|
765734cd2d | ||
|
|
7f3be34fdd | ||
|
|
00af0c281d | ||
|
|
001f206a31 | ||
|
|
4d9045d597 | ||
|
|
e001de3efa | ||
|
|
6ae1ccd5e3 | ||
|
|
cb72918c55 | ||
|
|
ba16f2f0ab | ||
|
|
a76576af23 | ||
|
|
72526ba20d | ||
|
|
db05eddb9c | ||
|
|
fb6804364a | ||
|
|
9e4626e905 | ||
|
|
6c8f7be2a5 | ||
|
|
cf97ed45b7 | ||
|
|
1d640a789f | ||
|
|
22e47582c4 | ||
|
|
ed3959f545 | ||
|
|
771c2f9d52 | ||
|
|
f5b7fa2d26 | ||
|
|
b798f1383b | ||
|
|
6a1b7d987c | ||
|
|
db95ee1987 | ||
|
|
edb9fbba09 | ||
|
|
ede6534ebf | ||
|
|
15c6ea7266 | ||
|
|
a719631607 | ||
|
|
e7d9d0fdf2 | ||
|
|
27bba9ce55 | ||
|
|
27562092b1 | ||
|
|
053640fd33 | ||
|
|
f7ad6b7d61 | ||
|
|
da6f590632 | ||
|
|
96148385c4 | ||
|
|
348b7569b8 | ||
|
|
ea3acc0b14 | ||
|
|
09f6b862fe | ||
|
|
0535b88b24 | ||
|
|
9fe3a0f491 | ||
|
|
b73685ace3 | ||
|
|
f16c92e825 | ||
|
|
abd0c9ca91 | ||
|
|
4f4aa134d5 |
+2
-1
@@ -7,8 +7,9 @@
|
|||||||
!/app.js
|
!/app.js
|
||||||
!/server.js
|
!/server.js
|
||||||
!/package.json
|
!/package.json
|
||||||
!/package-lock.json
|
|
||||||
!/index.js
|
!/index.js
|
||||||
!/generateConfig.js
|
!/generateConfig.js
|
||||||
!/main.js
|
!/main.js
|
||||||
!/data
|
!/data
|
||||||
|
!/.env
|
||||||
|
!/pnpm-lock.yaml
|
||||||
@@ -8,31 +8,20 @@ ENABLE_PROXY = false
|
|||||||
## 代理配置
|
## 代理配置
|
||||||
PROXY_URL = "https://your-proxy-url.com/?proxy="
|
PROXY_URL = "https://your-proxy-url.com/?proxy="
|
||||||
|
|
||||||
|
### 随机IP设置
|
||||||
|
## 启用随机中国IP(默认关闭);启用后,所有请求默认使用随机中国IP,除非请求参数randomCNIP显式关闭
|
||||||
|
ENABLE_RANDOM_CN_IP = false
|
||||||
|
|
||||||
### UnblockNeteaseMusic 设置项
|
### UnblockNeteaseMusic 设置项
|
||||||
## 启用全局解灰, 无论是否调用参数都会使用解灰(推荐开启)
|
## 启用全局解灰, 无论是否调用参数都会使用解灰(不推荐开启)
|
||||||
ENABLE_GENERAL_UNBLOCK = true
|
ENABLE_GENERAL_UNBLOCK = false
|
||||||
## 歌曲启用无损音质
|
## 歌曲启用无损音质
|
||||||
ENABLE_FLAC = true
|
ENABLE_FLAC = true
|
||||||
## 启用无损音质时,是否选择音质最高的
|
## 启用无损音质时,是否选择音质最高的
|
||||||
SELECT_MAX_BR = true
|
SELECT_MAX_BR = false
|
||||||
## 音源设置
|
|
||||||
UNBLOCK_SOURCE = pyncmd,qq,kuwo,migu,kugou
|
|
||||||
## 严格按照配置音源设置顺序进行匹配
|
## 严格按照配置音源设置顺序进行匹配
|
||||||
FOLLOW_SOURCE_ORDER = true
|
FOLLOW_SOURCE_ORDER = true
|
||||||
## Cookie设置项; 推荐在Vercel等平台进行环境变量的设置或者设置github secret
|
## Cookie设置项; 推荐在Vercel等平台进行环境变量的设置或者设置github secret
|
||||||
# 网易云 cookie
|
# 网易云 cookie
|
||||||
# 格式:MUSIC_U=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
# 格式:MUSIC_U=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
NETEASE_COOKIE = ""
|
# NETEASE_COOKIE = ""
|
||||||
# JOOX cookie
|
|
||||||
# 格式:JOOX_COOKIE="wmid=<your_wmid>; session_key=<your_session_key>"
|
|
||||||
JOOX_COOKIE = ""
|
|
||||||
# 咪咕 cookie
|
|
||||||
# 格式:MIGU_COOKIE="<your_aversionid>"
|
|
||||||
MIGU_COOKIE = ""
|
|
||||||
# qq cookie
|
|
||||||
# 格式:QQ_COOKIE="uin=<your_uin>; qm_keyst=<your_qm_keyst>"
|
|
||||||
QQ_COOKIE = ""
|
|
||||||
# Youtube 密钥
|
|
||||||
# 格式:YOUTUBE_KEY="<your_data_api_key>"
|
|
||||||
YOUTUBE_KEY = ""
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
|
|
||||||
ignorePatterns: ['public/'],
|
|
||||||
parserOptions: {
|
|
||||||
parser: 'babel-eslint',
|
|
||||||
ecmaVersion: 2018,
|
|
||||||
sourceType: 'module',
|
|
||||||
},
|
|
||||||
plugins: ['html'],
|
|
||||||
extends: ['plugin:prettier/recommended'],
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
rules: {
|
|
||||||
'prettier/prettier': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
endOfLine: 'auto',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
indent: ['error', 2, { SwitchCase: 1 }],
|
|
||||||
'space-infix-ops': ['error', { int32Hint: false }],
|
|
||||||
'key-spacing': [
|
|
||||||
2,
|
|
||||||
{
|
|
||||||
beforeColon: false,
|
|
||||||
afterColon: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'no-octal': 2,
|
|
||||||
'no-redeclare': 2,
|
|
||||||
'comma-spacing': 2,
|
|
||||||
'no-new-object': 2,
|
|
||||||
'arrow-spacing': 2,
|
|
||||||
quotes: [
|
|
||||||
2,
|
|
||||||
'single',
|
|
||||||
{
|
|
||||||
avoidEscape: true,
|
|
||||||
allowTemplateLiterals: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ['**/*.ts'],
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
extends: [
|
|
||||||
'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
|
|
||||||
// 'prettier/@typescript-eslint',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
custom: ["https://rewards.focalors.ltd"]
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
---
|
|
||||||
name: 创建问题反馈
|
|
||||||
about: 问题反馈
|
|
||||||
title: "<不按照模版格式或者删除模版信息将得不到处理,确认看完了调用前须知再发 issues>"
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 环境
|
|
||||||
- 系统/平台: <你的系统和平台>
|
|
||||||
|
|
||||||
- nodejs 版本: <你的 NodeJS 版本号>
|
|
||||||
|
|
||||||
- API版本: <运行的云音乐 API 的版本号, 对应 package.json 里面的 version>
|
|
||||||
|
|
||||||
## 出现问题
|
|
||||||
<请清晰描述出现的问题,不要使用xxx不行之类的模糊不清的语句,我没有读心术>
|
|
||||||
|
|
||||||
## 重现步骤
|
|
||||||
<请清晰描述重现步骤,调用的接口链接和参数以及命令行错误信息,最好贴上截图(命令行错误信息和接口返回内容)>
|
|
||||||
|
|
||||||
## 期待效果
|
|
||||||
<请清晰描述现在的效果和期待的效果>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
>注意: 如果解灰出现问题, 本项目只是集成[UnblockNeteaseMusic](https://github.com/UnblockNeteaseMusic/server)的接口, 请在对应的仓库开启议题
|
|
||||||
|
|
||||||
>维护项目都是业余时间,精力有限,我只能挑容易解决的issues处理,为了节约双方时间,请尽可能提供足够的有用的信息,给的信息不够我只能根据精力和时间看情况处理,如果模板信息看都不看就删掉,我不会进行任何回复,并且一个月后close掉issue
|
|
||||||
|
|
||||||
>先看文档有没有相关说明,调用前须知必看,确认看完了调用前须知再发 issues
|
|
||||||
|
|
||||||
>先在 issues 搜一下是否有相似问题,没有再发,否则直接关闭
|
|
||||||
|
|
||||||
>不处理别人搭建的线上服务的问题,此项目提供任何线上服务不保证质量,请自行搭建
|
|
||||||
|
|
||||||
>重现步骤尽量详细,不能含糊不清,包含请求地址和对应参数以及操作过程描述,不是每个人都喜欢猜别人遇到了什么问题和找参数一个个试,也比较浪费时间
|
|
||||||
|
|
||||||
>如果不是提建议,提 issues 如果不照着模版来将不会优先处理或放着不管
|
|
||||||
|
|
||||||
>460 cheating 的问题把 `utils/request.js` 里面的 `headers['X-Real-IP']` 的注释取消掉就好
|
|
||||||
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
name: 功能需求
|
|
||||||
about: 希望添加新接口
|
|
||||||
title: ''
|
|
||||||
labels: 功能需求
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**功能描述**
|
|
||||||
- 请清晰描述你所希望添加的功能,最好贴上对应客户端界面的截图
|
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
name: 🐛 Bug报告
|
||||||
|
description: 报告API其中的错误或问题
|
||||||
|
title: "[Bug] "
|
||||||
|
labels: ["bug"]
|
||||||
|
assignees: []
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
感谢您报告Bug!请填写以下信息帮助我们快速定位和修复问题。
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: 确认事项
|
||||||
|
description: |
|
||||||
|
在提交Bug报告前,请确认以下事项:
|
||||||
|
|
||||||
|
- 我已经搜索了现有的issues,确认这不是重复问题
|
||||||
|
- 我使用的是最新版本的API, 而且是官方发布的版本,而不是fork或修改版
|
||||||
|
- 不处理别人搭建的线上服务的问题,此项目提供任何线上服务不保证质量
|
||||||
|
- 如果不是提建议,提 issues 如果不照着模版来将不会优先处理或放着不管
|
||||||
|
- 维护项目都是业余时间,精力有限,我只能挑容易解决的issues处理,为了节约双方时间,请尽可能提供足够的有用的信息,给的信息不够我只能根据精力和时间看情况处理,如果模板信息看都不看就删掉,我不会进行任何回复,并且一个月后close掉issue
|
||||||
|
|
||||||
|
options:
|
||||||
|
- label: 我已确认以上事项
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: API版本
|
||||||
|
description: 请提供您使用的API版本以及nodejs版本
|
||||||
|
placeholder: "v4.2x.x, Node.js v22.x.x"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: deployment
|
||||||
|
attributes:
|
||||||
|
label: 部署方式
|
||||||
|
description: 此项目支持本地部署和云平台部署,您使用的是哪种方式?
|
||||||
|
options:
|
||||||
|
- 本机直接运行 (node / pm2)
|
||||||
|
- 本机 Docker 部署
|
||||||
|
- Vercel
|
||||||
|
- Heroku
|
||||||
|
- Railway
|
||||||
|
- 其他云平台 (请在描述中说明)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: bug-description
|
||||||
|
attributes:
|
||||||
|
label: Bug描述
|
||||||
|
description: 请详细描述您遇到的问题
|
||||||
|
placeholder: |
|
||||||
|
清晰简洁地描述bug的具体表现...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: reproduction-steps
|
||||||
|
attributes:
|
||||||
|
label: 重现步骤
|
||||||
|
description: 请提供详细的重现步骤
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected-behavior
|
||||||
|
attributes:
|
||||||
|
label: 期望行为
|
||||||
|
description: 请描述您期望发生的行为
|
||||||
|
placeholder: 清晰简洁地描述您期望应该发生什么...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: actual-behavior
|
||||||
|
attributes:
|
||||||
|
label: 实际行为
|
||||||
|
description: 请描述实际发生的行为
|
||||||
|
placeholder: 清晰简洁地描述实际发生了什么...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: error-output
|
||||||
|
attributes:
|
||||||
|
label: 错误输出日志
|
||||||
|
description: 请提供完整的错误信息日志
|
||||||
|
render: shell
|
||||||
|
placeholder: |
|
||||||
|
错误信息输出...
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
|
attributes:
|
||||||
|
label: 额外信息
|
||||||
|
description: 添加任何其他有助于理解问题的信息
|
||||||
|
placeholder: |
|
||||||
|
- 截图
|
||||||
|
- 相关配置文件
|
||||||
|
- 其他相关信息
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: help
|
||||||
|
attributes:
|
||||||
|
label: 愿意协助
|
||||||
|
description: 您是否愿意帮助修复这个bug?
|
||||||
|
options:
|
||||||
|
- label: 我愿意提交PR来修复这个bug
|
||||||
|
- label: 我可以提供更多测试用例
|
||||||
|
- label: 我可以帮助测试修复方案
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
blank_issues_enabled: true
|
||||||
|
contact_links:
|
||||||
|
- name: ↑请尽量使用议题模板创建议题↑
|
||||||
|
url: https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced/issues/new/choose
|
||||||
|
about: 选择适合的议题模板可以帮助我们更快地定位和解决问题
|
||||||
|
- name: 提问的艺术
|
||||||
|
url: https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md
|
||||||
|
about: 默认所有 Issues 发起者均已了解此处的内容
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
name: ✨ 接口添加请求
|
||||||
|
description: 希望添加新接口
|
||||||
|
title: "[Feature] "
|
||||||
|
labels: ["enhancement"]
|
||||||
|
assignees: []
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
感谢您提出新功能建议!请填写以下信息帮助我们理解您的需求。
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: 确认事项
|
||||||
|
description: 在提交功能请求前,请确认以下事项
|
||||||
|
options:
|
||||||
|
- label: 我已经搜索了现有的issues,确认这不是重复请求
|
||||||
|
required: true
|
||||||
|
- label: 我已经查看了项目文档
|
||||||
|
required: true
|
||||||
|
- label: 该项目符合该项目的目标和范围
|
||||||
|
required: true
|
||||||
|
- label: 我确认我提供的接口请求是合理且有意义的
|
||||||
|
required: true
|
||||||
|
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: proposed-solution
|
||||||
|
attributes:
|
||||||
|
label: 功能描述
|
||||||
|
description: 请详细描述您希望添加的功能,最好贴上对应客户端界面的截图
|
||||||
|
placeholder: |
|
||||||
|
请详细描述您期望的功能:
|
||||||
|
- 新的网易云音乐api 接口?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: priority
|
||||||
|
attributes:
|
||||||
|
label: 优先级
|
||||||
|
description: 您认为这个功能的优先级如何?
|
||||||
|
options:
|
||||||
|
- 低 (可有可无的改进)
|
||||||
|
- 中 (会显著改善使用体验)
|
||||||
|
- 高 (严重影响了我使用)
|
||||||
|
- 紧急 (阻塞了重要工作)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: compatibility
|
||||||
|
attributes:
|
||||||
|
label: 兼容性考虑
|
||||||
|
description: 这个功能是否会影响现有代码的兼容性?
|
||||||
|
placeholder: |
|
||||||
|
- 是否是破坏性变更?
|
||||||
|
- 是否需要语法变更?
|
||||||
|
- 对性能的影响?
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: implementation
|
||||||
|
attributes:
|
||||||
|
label: 实现意愿
|
||||||
|
description: 您是否愿意参与这个功能的实现?
|
||||||
|
options:
|
||||||
|
- label: 我愿意提交PR来实现这个功能
|
||||||
|
- label: 我可以协助设计API接口
|
||||||
|
- label: 我可以提供测试用例
|
||||||
|
- label: 我可以帮助编写文档
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# .github/dependabot.yml
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
# 更新 npm (JavaScript) 依赖
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/" # package.json 所在的位置
|
||||||
|
schedule:
|
||||||
|
interval: "weekly" # 每周一检查
|
||||||
|
# 默认情况下,Dependabot 会自动获取最新版本。
|
||||||
|
# 你可以使用 `ignore` 来排除某些特定更新
|
||||||
|
ignore:
|
||||||
|
- dependency-name: "eslint"
|
||||||
|
# 对于 ESLint,忽略主版本 8 以上的所有更新
|
||||||
|
versions: ["> 8"]
|
||||||
|
# 自动合并小版本更新的 PR (可选)
|
||||||
|
# 可以设置针对 patch 或 minor 版本更新自动合并
|
||||||
|
commit-message:
|
||||||
|
prefix: "build(packages):" # PR 合并提交信息的前缀
|
||||||
|
|
||||||
|
# 更新 GitHub Actions 依赖
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/" # .github/workflows/ 目录所在的位置
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
|
|
||||||
|
# 更新 Python (pip) 依赖,如果你的项目有 requirements.txt
|
||||||
|
- package-ecosystem: "pip"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
|
||||||
|
# 如果你有多个目录(例如一个 monorepo),可以为每个目录单独配置
|
||||||
|
# - package-ecosystem: "npm"
|
||||||
|
# directory: "/packages/frontend"
|
||||||
|
# schedule:
|
||||||
|
# interval: "weekly"
|
||||||
|
# - package-ecosystem: "npm"
|
||||||
|
# directory: "/packages/backend"
|
||||||
|
# schedule:
|
||||||
|
# interval: "weekly"
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
name: Build Image
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- v*
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
if: github.event.base_ref == 'refs/heads/main'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Build image job
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout main
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Get version
|
|
||||||
id: get_version
|
|
||||||
if: startsWith(github.ref, 'refs/tags/') && startsWith(github.repository, 'Binaryify/ncm')
|
|
||||||
run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
- name: Build and publish image
|
|
||||||
uses: elgohr/Publish-Docker-Github-Action@v5
|
|
||||||
if: startsWith(github.ref, 'refs/tags/') && startsWith(github.repository, 'Binaryify/ncm')
|
|
||||||
with:
|
|
||||||
name: binaryify/netease_cloud_music_api # dockerid/imageName
|
|
||||||
platforms: linux/arm64,linux/amd64 # 你准备构建的镜像平台
|
|
||||||
tags: latest,${{ steps.get_version.outputs.VERSION }}
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }} # docker hub userid 在setting创建secrets name=DOCKER_USERNAME value=dockerid
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }} # docker hub password,在setting创建secrets name=DOCKER_PASSWORD value=dockerpassword
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
name: Build Artifacts
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch: # 手动触发
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
platform: linux
|
||||||
|
target: node18-linux-x64
|
||||||
|
output: precompiled/app
|
||||||
|
- os: windows-latest
|
||||||
|
platform: win
|
||||||
|
target: node18-win-x64
|
||||||
|
output: precompiled/app.exe
|
||||||
|
- os: macos-latest
|
||||||
|
platform: macos
|
||||||
|
target: node18-macos-x64
|
||||||
|
output: precompiled/app
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: '18'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
npm install -g pnpm
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
- name: Build for ${{ matrix.platform }}
|
||||||
|
run: |
|
||||||
|
npm run pkg${{ matrix.platform }}
|
||||||
|
env:
|
||||||
|
PKG_TARGET: ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: app-${{ matrix.platform }}
|
||||||
|
path: ${{ matrix.output }}
|
||||||
|
if-no-files-found: error
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
name: Issue Management
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened, labeled]
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
welcome-new-issues:
|
||||||
|
if: github.event.action == 'opened'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Welcome new issue
|
||||||
|
uses: peter-evans/create-or-update-comment@v5
|
||||||
|
with:
|
||||||
|
issue-number: ${{ github.event.issue.number }}
|
||||||
|
body: |
|
||||||
|
## 快速链接
|
||||||
|
|
||||||
|
在我们处理您的问题之前,您可以先查看以下资源:
|
||||||
|
|
||||||
|
- 📖 查看[项目文档](https://neteasecloudmusicapienhanced.js.org)
|
||||||
|
- 💬 加入[QQ交流群](https://qm.qq.com/q/TpeP9Uv2yk)
|
||||||
|
- 🔍 搜索[现有issues](https://github.com/neteasecloudmusicapienhanced/api-enhanced/issues) 看是否有类似问题
|
||||||
|
|
||||||
|
handle-help-wanted:
|
||||||
|
if: contains(github.event.issue.labels.*.name, 'question')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Add help resources
|
||||||
|
uses: peter-evans/create-or-update-comment@v5
|
||||||
|
with:
|
||||||
|
issue-number: ${{ github.event.issue.number }}
|
||||||
|
body: |
|
||||||
|
🆘 需要帮助?
|
||||||
|
|
||||||
|
这里有一些可能对您有用的资源:
|
||||||
|
|
||||||
|
📚 **文档资源**
|
||||||
|
- [项目文档](https://neteasecloudmusicapienhanced.js.org)
|
||||||
|
|
||||||
|
🔍 **常见问题**
|
||||||
|
- 搜索[已关闭的issues](https://github.com/neteasecloudmusicapienhanced/api-enhanced/issues?q=is%3Aissue+is%3Aclosed) 看看是否有类似的问题已经被解答。
|
||||||
|
|
||||||
|
💬 **即时帮助**
|
||||||
|
- 加入QQ群:https://qm.qq.com/q/TpeP9Uv2yk
|
||||||
|
|
||||||
|
如果以上资源无法解决您的问题,请提供更多详细信息,我们会尽快为您解答!
|
||||||
|
|
||||||
|
stale-issues:
|
||||||
|
if: github.event.action == 'opened'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Mark stale issues
|
||||||
|
uses: actions/stale@v10
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
stale-issue-message: |
|
||||||
|
这个issue已经30天没有活动了。
|
||||||
|
|
||||||
|
如果这个问题仍然存在,请回复此评论说明当前状态。
|
||||||
|
否则,这个issue将在7天后自动关闭。
|
||||||
|
|
||||||
|
您可以随时重新打开已关闭的issue。
|
||||||
|
stale-pr-message: |
|
||||||
|
这个PR已经30天没有活动了。
|
||||||
|
|
||||||
|
如果您仍在处理这个PR,请回复此评论。
|
||||||
|
否则,这个PR将在7天后自动关闭。
|
||||||
|
days-before-stale: 30
|
||||||
|
days-before-close: 7
|
||||||
|
stale-issue-label: "stale"
|
||||||
|
stale-pr-label: "stale"
|
||||||
|
exempt-issue-labels: "pinned,enhancement,priority:high,ongoing,fatal"
|
||||||
|
exempt-pr-labels: "pinned,priority:high,ongoing,fatal"
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
name: Node.js CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [ main ]
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
name: Test
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [14.x, 16.x, 18.x ]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: ~/.npm
|
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-node-
|
|
||||||
- run: npm ci
|
|
||||||
name: Install dependencies
|
|
||||||
- name: Test
|
|
||||||
env:
|
|
||||||
NCM_API_TEST_LOGIN_COUNTRY_CODE: ${{ secrets.NCM_API_TEST_LOGIN_COUNTRY_CODE }}
|
|
||||||
NCM_API_TEST_LOGIN_PHONE: ${{ secrets.NCM_API_TEST_LOGIN_PHONE }}
|
|
||||||
NCM_API_TEST_LOGIN_PASSWORD: ${{ secrets.NCM_API_TEST_LOGIN_PASSWORD }}
|
|
||||||
run: npm test
|
|
||||||
lint:
|
|
||||||
name: Lint
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [14.x]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: ~/.npm
|
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-node-
|
|
||||||
- run: npm ci
|
|
||||||
name: Install dependencies
|
|
||||||
- name: Lint
|
|
||||||
run: npm run lint
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# .github/workflows/release.yml
|
|
||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
# 发布到 NPM Registry
|
|
||||||
- name: Setup Node.js for NPM
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
registry-url: 'https://registry.npmjs.org/'
|
|
||||||
- run: npm install
|
|
||||||
- name: Publish to NPM
|
|
||||||
run: npm publish --access public
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
# 发布到 GitHub Packages
|
|
||||||
- name: Setup Node.js for GitHub Packages
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
registry-url: 'https://npm.pkg.github.com'
|
|
||||||
- name: Update package name for GitHub Packages
|
|
||||||
run: |
|
|
||||||
json=`cat package.json`
|
|
||||||
echo "$json" | jq '.name = "@neteasecloudmusicapienhanced/api"' > package.json
|
|
||||||
- name: Publish to GitHub Packages
|
|
||||||
run: npm publish --access public
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ github.token }}
|
|
||||||
@@ -0,0 +1,315 @@
|
|||||||
|
name: Create Release on Version Change
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- package.json
|
||||||
|
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: release-${{ github.ref }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
detect:
|
||||||
|
name: Detect Version Change
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.version.outputs.value }}
|
||||||
|
should_release: ${{ steps.check.outputs.should_release }}
|
||||||
|
tag_exists: ${{ steps.tagcheck.outputs.exists }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Extract version from package.json
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "value=$(jq -r .version package.json)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Check if version changed
|
||||||
|
id: check
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||||||
|
echo "should_release=true" >> $GITHUB_OUTPUT
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if git diff "${{ github.event.before }}" "${{ github.sha }}" -- package.json | grep -q '"version"'; then
|
||||||
|
echo "should_release=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "should_release=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check if tag already exists
|
||||||
|
id: tagcheck
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.version.outputs.value }}"
|
||||||
|
|
||||||
|
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
|
||||||
|
echo "exists=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "exists=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build ${{ matrix.platform }}
|
||||||
|
needs: detect
|
||||||
|
|
||||||
|
if: |
|
||||||
|
needs.detect.outputs.should_release == 'true' &&
|
||||||
|
needs.detect.outputs.tag_exists != 'true'
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
platform: linux
|
||||||
|
target: node18-linux-x64
|
||||||
|
output: ncm-api-linux-x64
|
||||||
|
|
||||||
|
- os: windows-latest
|
||||||
|
platform: windows
|
||||||
|
target: node18-win-x64
|
||||||
|
output: ncm-api-win-x64.exe
|
||||||
|
|
||||||
|
- os: macos-latest
|
||||||
|
platform: macos
|
||||||
|
target: node18-macos-x64
|
||||||
|
output: ncm-api-macos-x64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v6
|
||||||
|
with:
|
||||||
|
version: 9
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build binary
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
PKG_TARGET: ${{ matrix.target }}
|
||||||
|
run: |
|
||||||
|
mkdir -p release-artifacts
|
||||||
|
|
||||||
|
case "${{ matrix.platform }}" in
|
||||||
|
linux)
|
||||||
|
npm run pkglinux
|
||||||
|
mv precompiled/app "release-artifacts/${{ matrix.output }}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
windows)
|
||||||
|
npm run pkgwin
|
||||||
|
mv precompiled/app.exe "release-artifacts/${{ matrix.output }}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
macos)
|
||||||
|
npm run pkgmacos
|
||||||
|
mv precompiled/app "release-artifacts/${{ matrix.output }}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
- name: Upload build artifact
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.platform }}-binary
|
||||||
|
path: release-artifacts/*
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Create GitHub Release
|
||||||
|
|
||||||
|
needs:
|
||||||
|
- detect
|
||||||
|
- build
|
||||||
|
|
||||||
|
if: |
|
||||||
|
needs.detect.outputs.should_release == 'true' &&
|
||||||
|
needs.detect.outputs.tag_exists != 'true'
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
fetch-tags: true
|
||||||
|
|
||||||
|
- name: Download build artifacts
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
path: release-artifacts
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: List flattened artifacts
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir -p final-artifacts
|
||||||
|
cp release-artifacts/* final-artifacts/
|
||||||
|
ls -lah final-artifacts
|
||||||
|
|
||||||
|
- name: Generate release notes
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
VERSION="${{ needs.detect.outputs.version }}"
|
||||||
|
|
||||||
|
PREV_TAG=$(git tag --sort=-v:refname | head -1)
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# Release v${VERSION}"
|
||||||
|
echo ""
|
||||||
|
echo "## 更新内容 / Changelog"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -n "$PREV_TAG" ]; then
|
||||||
|
echo "从 \`$PREV_TAG\` 到 \`v$VERSION\` 的提交记录:"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
git log "$PREV_TAG..HEAD" \
|
||||||
|
--no-merges \
|
||||||
|
--pretty=format:"- %s (%h)"
|
||||||
|
|
||||||
|
else
|
||||||
|
echo "首次发布,包含以下提交:"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
git log \
|
||||||
|
--no-merges \
|
||||||
|
--pretty=format:"- %s (%h)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
echo "---"
|
||||||
|
echo "自动发布 via GitHub Actions"
|
||||||
|
|
||||||
|
} > release-notes.md
|
||||||
|
|
||||||
|
cat release-notes.md
|
||||||
|
|
||||||
|
- name: Create Git tag
|
||||||
|
run: |
|
||||||
|
git tag "v${{ needs.detect.outputs.version }}"
|
||||||
|
git push origin "v${{ needs.detect.outputs.version }}"
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: v${{ needs.detect.outputs.version }}
|
||||||
|
name: Release v${{ needs.detect.outputs.version }}
|
||||||
|
body_path: release-notes.md
|
||||||
|
files: final-artifacts/*
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
|
||||||
|
publish-docker:
|
||||||
|
name: Publish Docker Image
|
||||||
|
needs: detect
|
||||||
|
|
||||||
|
if: |
|
||||||
|
needs.detect.outputs.should_release == 'true' &&
|
||||||
|
needs.detect.outputs.tag_exists != 'true'
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Read package version
|
||||||
|
id: pkg
|
||||||
|
run: echo "VERSION=$(jq -r .version package.json)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v4
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v4
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
moefurina/ncm-api:latest
|
||||||
|
moefurina/ncm-api:${{ env.VERSION }}
|
||||||
|
ghcr.io/neteasecloudmusicapienhanced/ncm-api:latest
|
||||||
|
ghcr.io/neteasecloudmusicapienhanced/ncm-api:${{ env.VERSION }}
|
||||||
|
platforms: linux/amd64,linux/arm64/v8
|
||||||
|
|
||||||
|
publish-npm:
|
||||||
|
name: Publish to npm
|
||||||
|
needs: detect
|
||||||
|
|
||||||
|
if: |
|
||||||
|
needs.detect.outputs.should_release == 'true' &&
|
||||||
|
needs.detect.outputs.tag_exists != 'true'
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v6
|
||||||
|
with:
|
||||||
|
version: 9
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- run: pnpm publish --access public --provenance
|
||||||
@@ -17,12 +17,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# Step 1: run a standard checkout action
|
# Step 1: run a standard checkout action
|
||||||
- name: Checkout target repo
|
- name: Checkout target repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
# Step 2: run the sync action
|
# Step 2: run the sync action
|
||||||
- name: Sync upstream changes
|
- name: Sync upstream changes
|
||||||
id: sync
|
id: sync
|
||||||
uses: aormsby/[email protected].1
|
uses: aormsby/[email protected].3
|
||||||
with:
|
with:
|
||||||
upstream_sync_repo: NeteaseCloudMusicApiEnhanced/api-enhanced
|
upstream_sync_repo: NeteaseCloudMusicApiEnhanced/api-enhanced
|
||||||
upstream_sync_branch: main
|
upstream_sync_branch: main
|
||||||
|
|||||||
@@ -8,3 +8,5 @@ examples/moddef.json
|
|||||||
bin
|
bin
|
||||||
anonymous_token
|
anonymous_token
|
||||||
.vercel
|
.vercel
|
||||||
|
.env
|
||||||
|
IFLOW.md
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
|
|
||||||
node_js:
|
node_js:
|
||||||
- 12
|
- 22
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Agent Instructions for NeteaseCloudMusicApiEnhanced
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
- **Package Manager**: Use `pnpm` (not npm or yarn).
|
||||||
|
- **Node Version**: Requires Node.js 18 or later.
|
||||||
|
|
||||||
|
## Developer Commands
|
||||||
|
- **Install dependencies**: `pnpm i`
|
||||||
|
- **Start server**: `pnpm start` or `node app.js`
|
||||||
|
- **Start dev server**: `pnpm dev` (uses nodemon)
|
||||||
|
- **Run tests**: `pnpm test` (uses Mocha)
|
||||||
|
- **Linting**: `pnpm lint` (check) or `pnpm lint-fix` (auto-fix)
|
||||||
|
|
||||||
|
## Architecture & Entrypoints
|
||||||
|
- **Executable Server**: `app.js` is the main entrypoint for running the API server.
|
||||||
|
- **Module Exports**: `main.js` is the entrypoint when the project is imported as a Node.js dependency.
|
||||||
|
- **API Endpoints**: Located in the `module/` directory. Each file typically corresponds to an API route.
|
||||||
|
- **Core Utilities**: Request handling, encryption, and core utilities are found in the `util/` directory.
|
||||||
|
|
||||||
|
## Important Gotchas & Quirks
|
||||||
|
- **Environment Variables**: The server defaults to port 3000 but can be overridden with the `PORT` environment variable.
|
||||||
|
- **Proxy Variables**: Be very careful with proxy environment variables (`http_proxy`, `https_proxy`, `no_proxy`). The request library (like axios) will automatically pick these up. If they point to an unavailable proxy (especially common in Docker environments), requests will fail silently or throw connection errors.
|
||||||
|
- **Code Style**: The project uses ESLint and Prettier. Always run `pnpm lint-fix` before committing changes to ensure formatting consistency.
|
||||||
-1393
File diff suppressed because it is too large
Load Diff
@@ -1,23 +1,58 @@
|
|||||||
# 网易云音乐 API Enhanced
|
<p align="center">
|
||||||
|
<img src="./public/static/neteaselogo.png" alt="网易云音乐 API Enhanced Logo" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.npmjs.com/package/@neteasecloudmusicapienhanced/api">
|
||||||
|
<img src="https://img.shields.io/npm/v/@neteasecloudmusicapienhanced/api?style=flat-square" alt="NPM Version" />
|
||||||
|
</a>
|
||||||
|
<a href="https://www.npmjs.com/package/@neteasecloudmusicapienhanced/api">
|
||||||
|
<img src="https://img.shields.io/npm/dm/@neteasecloudmusicapienhanced/api?style=flat-square" alt="NPM Downloads" />
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced">
|
||||||
|
<img src="https://img.shields.io/github/license/NeteaseCloudMusicApiEnhanced/api-enhanced?style=flat-square" alt="License" />
|
||||||
|
</a>
|
||||||
|
<a href="https://nodejs.org/">
|
||||||
|
<img src="https://img.shields.io/node/v/@neteasecloudmusicapienhanced/api?style=flat-square" alt="Node Version" />
|
||||||
|
</a>
|
||||||
|
<a href="https://hub.docker.com/r/moefurina/ncm-api">
|
||||||
|
<img src="https://img.shields.io/docker/v/moefurina/ncm-api?style=flat-square" alt="Docker Version" />
|
||||||
|
</a>
|
||||||
|
<a href="https://hub.docker.com/r/moefurina/ncm-api">
|
||||||
|
<img src="https://img.shields.io/docker/pulls/moefurina/ncm-api?style=flat-square" alt="Docker Pulls" />
|
||||||
|
</a>
|
||||||
|
<a href="https://www.typescriptlang.org/">
|
||||||
|
<img src="https://img.shields.io/badge/TypeScript-Ready-blue?style=flat-square&logo=typescript" alt="TypeScript" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 项目简介
|
## 项目简介
|
||||||
|
|
||||||
本项目为网易云音乐第三方 Node.js API,基于社区停更的原版 API 持续维护和升级,支持丰富的音乐相关接口,适合自建服务、二次开发和多平台部署(如果原版诈尸, 我会及时同步 or 归档)。
|
网易云音乐第三方 Node.js API, 支持丰富的音乐相关接口,适合自建服务、二次开发和多平台部署
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
>
|
||||||
|
> ## 注意
|
||||||
|
>
|
||||||
|
> - 本项目是自由项目, 不带任何担保, 在基于`MIT License`的许可下, 任何人都可以自由使用、修改和分发本项目的代码, 包括用于商业目的.
|
||||||
|
> - 该项目是个合作计划, 有很多人参与了此次贡献, 前往 [Contributors](https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced/graphs/contributors) 页面查看所有贡献者
|
||||||
|
> - 原作者项目 [Binaryify/NeteaseCloudMusicApi](https://github.com/binaryify/NeteaseCloudMusicApi) 并非完全停止维护, 你可以在 [NeteaseCloudMusicApi 的 NPMJS 页面](https://www.npmjs.com/package/NeteaseCloudMusicApi) 查看最新版本信息。
|
||||||
|
> - 感谢尊重和理解!
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
### 环境要求
|
### 环境要求
|
||||||
|
|
||||||
- Node.js 14 及以上
|
- Node.js 22 及以上
|
||||||
- 推荐使用 pnpm 进行依赖管理
|
- 推荐使用 pnpm 进行依赖管理
|
||||||
|
|
||||||
### 安装
|
### 安装
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/neteasecloudmusicapienhanced/api-enhanced.git
|
git clone https://github.com/neteasecloudmusicapienhanced/api-enhanced.git
|
||||||
cd api
|
cd api-enhanced
|
||||||
pnpm i
|
pnpm i
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -34,25 +69,93 @@ set PORT=4000 && node app.js # Windows
|
|||||||
|
|
||||||
### 重要提示
|
### 重要提示
|
||||||
|
|
||||||
- 调用前请务必阅读文档的 [调用前须知](https://music-api.focalors.ltd/#/?id=%e8%b0%83%e7%94%a8%e5%89%8d%e9%a1%bb%e7%9f%a5) 部分。
|
- 调用前请务必阅读文档的 [调用前须知](https://neteasecloudmusicapienhanced.js.org/#/?id=%e8%b0%83%e7%94%a8%e5%89%8d%e9%a1%bb%e7%9f%a5) 部分。
|
||||||
- 推荐将敏感信息(如 cookie)通过部署平台的环境变量进行配置。
|
- 推荐将敏感信息(如 cookie)通过部署平台的环境变量进行配置。
|
||||||
|
|
||||||
## 在线体验与文档
|
## 在线体验与文档
|
||||||
|
|
||||||
- [在线文档](https://music-api.focalors.ltd/)
|
- [在线文档](https://neteasecloudmusicapienhanced.js.org/)
|
||||||
- [NPM 包地址](https://www.npmjs.com/package/@neteaseapireborn/api)
|
- [NPM 包地址](https://www.npmjs.com/package/@neteasecloudmusicapienhanced/api)
|
||||||
|
- [Dockerhub 主页](https://hub.docker.com/r/moefurina/ncm-api)
|
||||||
|
|
||||||
## 常见部署方式
|
## 常见部署方式
|
||||||
|
|
||||||
- 推荐参考[在线文档](https://music-api.focalors.ltd/)
|
- 推荐参考[在线文档](https://neteasecloudmusicapienhanced.js.org/)
|
||||||
|
|
||||||
### Vercel 一键部署
|
## Docker 部署说明
|
||||||
|
|
||||||
|
本项目支持通过 Docker 一键部署,无需手动安装 Node.js 或依赖。
|
||||||
|
|
||||||
|
> 注意: 在 docker 中运行的时候, 由于使用了 request 来发请求, 所以会检查几个 proxy 相关的环境变量(如下所列), 这些环境变量 会影响到 request 的代理, 详情请参考[request 的文档](https://github.com/request/request#proxies), 如果这些环境变量 指向的代理不可用, 那么就会造成错误, 所以在使用 docker 的时候一定要注意这些环境变量. 不过, 要是你在 query 中加上了 proxy 参数, 那么环境变量会被覆盖, 就会用你通过 proxy 参数提供的代理了.
|
||||||
|
|
||||||
|
request 相关的环境变量
|
||||||
|
|
||||||
|
1. http_proxy
|
||||||
|
2. https_proxy
|
||||||
|
3. HTTP_PROXY
|
||||||
|
4. HTTPS_PROXY
|
||||||
|
5. no_proxy
|
||||||
|
6. NO_PROXY
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker pull moefurina/ncm-api:latest
|
||||||
|
|
||||||
|
docker run -d -p 3000:3000 --name ncm-api moefurina/ncm-api:latest
|
||||||
|
|
||||||
|
|
||||||
|
// 或者
|
||||||
|
docker run -d -p 3000:3000 moefurina/ncm-api:latest
|
||||||
|
|
||||||
|
// 去掉或者设置相关的环境变量
|
||||||
|
|
||||||
|
docker run -d -p 3000:3000 --name ncm-api -e http_proxy= -e https_proxy= -e no_proxy= -e HTTP_PROXY= -e HTTPS_PROXY= -e NO_PROXY= moefurina/ncm-api:latest
|
||||||
|
|
||||||
|
// 或者
|
||||||
|
docker run -d -p 3000:3000 -e http_proxy= -e https_proxy= -e no_proxy= -e HTTP_PROXY= -e HTTPS_PROXY= -e NO_PROXY= moefurina/ncm-api:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
> 以下是自行 build docker 镜像方式
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ git clone https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced && cd api-enhanced
|
||||||
|
|
||||||
|
$ sudo docker build . -t ncm-api
|
||||||
|
|
||||||
|
$ sudo docker run -d -p 3000:3000 ncm-api
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 环境变量
|
||||||
|
|
||||||
|
| 变量名 | 默认值 | 说明 |
|
||||||
|
|----------------------------|--------------------------------------|----------------------------------------------------|
|
||||||
|
| **CORS_ALLOW_ORIGIN** | `*` | 允许跨域请求的域名。可填写单个源,或使用逗号分隔多个源(例如 `https://a.com,https://b.com`)。 |
|
||||||
|
| **ENABLE_PROXY** | `false` | 是否启用反向代理功能。 |
|
||||||
|
| **PROXY_URL** | `https://your-proxy-url.com/?proxy=` | 代理服务地址。仅当 `ENABLE_PROXY=true` 时生效。 |
|
||||||
|
| **ENABLE_RANDOM_CN_IP** | `false` | 是否默认启用随机中国IP。启用后,所有请求默认使用随机中国IP,除非请求参数 `randomCNIP` 显式关闭。 |
|
||||||
|
| **ENABLE_GENERAL_UNBLOCK** | `true` | 是否启用全局解灰(推荐开启)。开启后所有歌曲都尝试自动解锁。 |
|
||||||
|
| **ENABLE_FLAC** | `true` | 是否启用无损音质(FLAC)。 |
|
||||||
|
| **SELECT_MAX_BR** | `false` | 启用无损音质时,是否选择最高码率音质。 |
|
||||||
|
| **FOLLOW_SOURCE_ORDER** | `true` | 是否严格按照音源列表顺序进行匹配。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 更新镜像
|
||||||
|
|
||||||
|
更新到最新版本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull moefurina/ncm-api:latest
|
||||||
|
docker stop ncm-api && docker rm ncm-api
|
||||||
|
docker run -d -p 3000:3000 moefurina/ncm-api:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Vercel 一键部署
|
||||||
|
|
||||||
1. fork 本项目
|
1. fork 本项目
|
||||||
2. 在 Vercel 官网新建项目,导入 fork 的仓库
|
2. 在 Vercel 官网新建项目,导入 fork 的仓库
|
||||||
3. 直接 Deploy
|
3. 直接 Deploy
|
||||||
|
|
||||||
### 腾讯云 Serverless 部署
|
## 腾讯云 Serverless 部署
|
||||||
|
|
||||||
1. fork 本项目
|
1. fork 本项目
|
||||||
2. 在腾讯云 serverless 控制台新建 Web 应用,选择 Express 框架
|
2. 在腾讯云 serverless 控制台新建 Web 应用,选择 Express 框架
|
||||||
@@ -69,7 +172,10 @@ set PORT=4000 && node app.js # Windows
|
|||||||
支持直接在 Node.js 项目中引入和调用,返回 Promise:
|
支持直接在 Node.js 项目中引入和调用,返回 Promise:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const { login_cellphone, user_cloud } = require('@neteaseapireborn/api')
|
const {
|
||||||
|
login_cellphone,
|
||||||
|
user_cloud,
|
||||||
|
} = require('@neteasecloudmusicapienhanced/api')
|
||||||
async function main() {
|
async function main() {
|
||||||
const result = await login_cellphone({ phone: '手机号', password: '密码' })
|
const result = await login_cellphone({ phone: '手机号', password: '密码' })
|
||||||
console.log(result)
|
console.log(result)
|
||||||
@@ -82,7 +188,7 @@ main()
|
|||||||
## TypeScript 支持
|
## TypeScript 支持
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { banner } from '@neteaseapireborn/api'
|
import { banner } from '@neteasecloudmusicapienhanced/api'
|
||||||
banner({ type: 0 }).then((res) => console.log(res))
|
banner({ type: 0 }).then((res) => console.log(res))
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -98,22 +204,50 @@ pnpm test
|
|||||||
- 用户信息、歌单、动态、播放记录
|
- 用户信息、歌单、动态、播放记录
|
||||||
- 歌曲、专辑、歌手、MV、歌词、评论、排行榜
|
- 歌曲、专辑、歌手、MV、歌词、评论、排行榜
|
||||||
- 搜索、推荐、私人 FM、签到、云盘
|
- 搜索、推荐、私人 FM、签到、云盘
|
||||||
- 歌曲解锁(解灰)、多音源支持(qq/kuwo/kugou/migu/pyncmd)
|
- 歌曲解锁(解灰)
|
||||||
> 注意: 如果解灰出现问题, 本项目只是集成[UnblockNeteaseMusic](https://github.com/UnblockNeteaseMusic/server)的接口, 请在对应的仓库开启议题
|
|
||||||
- 详细接口请见[在线文档](https://music-api.focalors.ltd/)
|
|
||||||
|
|
||||||
## 贡献与社区
|
## 贡献与社区
|
||||||
|
|
||||||
- 欢迎提交 PR、Issue 参与维护
|
- 欢迎提交 PR、Issue 参与维护
|
||||||
|
- 要贡献新接口, 请参考[这篇文章](https://www.focalors.ltd/post/how-to-contribute-ncm-api)
|
||||||
|
|
||||||
## SDK 生态
|
### 致谢
|
||||||
|
|
||||||
| 语言 | 作者 | 地址 | 类型 |
|
原作者 [Binaryify/NeteaseCloudMusicApi](https://github.com/binaryify/NeteaseCloudMusicApi) 项目为本项目基础 (该项目在`npmjs`网站上仍持续维护, 但 github 仓库已不再更新)
|
||||||
| ------ | ------------------------------------------- | ---------------------------------------------------------------------------------------- | ------ |
|
|
||||||
|
感谢大佬们为逆向eapi, weapi, xeapi等加密算法所做的贡献
|
||||||
|
|
||||||
|
项目参考:
|
||||||
|
|
||||||
|
[trazyn/ieaseMusic](https://github.com/trazyn/ieaseMusic)
|
||||||
|
|
||||||
|
[listen1/listen1_chrome_extension](https://github.com/listen1/listen1_chrome_extension)
|
||||||
|
|
||||||
|
[chaunsin/netease-cloud-music](https://github.com/chaunsin/netease-cloud-music)
|
||||||
|
|
||||||
|
[folltoshe/netease-report-listen-song](https://github.com/folltoshe/netease-report-listen-song)
|
||||||
|
|
||||||
|
### SDK 生态
|
||||||
|
|
||||||
|
| 语言 | 作者 | 地址 | 类型 |
|
||||||
|
|--------|---------------------------------------------|------------------------------------------------------------------------------------------|-----|
|
||||||
| Java | [JackuXL](https://github.com/JackuXL) | [NeteaseCloudMusicApi-SDK](https://github.com/JackuXL/NeteaseCloudMusicApi-SDK) | 第三方 |
|
| Java | [JackuXL](https://github.com/JackuXL) | [NeteaseCloudMusicApi-SDK](https://github.com/JackuXL/NeteaseCloudMusicApi-SDK) | 第三方 |
|
||||||
| Java | [1015770492](https://github.com/1015770492) | https://github.com/1015770492/yumbo-music-utils | 第三方 |
|
| Java | [1015770492](https://github.com/1015770492) | [yumbo-music-utils](https://github.com/1015770492/yumbo-music-utils) | 第三方 |
|
||||||
| Python | [盧瞳](https://github.com/2061360308) | [NeteaseCloudMusic_PythonSDK](https://github.com/2061360308/NeteaseCloudMusic_PythonSDK) | 第三方 |
|
| Python | [盧瞳](https://github.com/2061360308) | [NeteaseCloudMusic_PythonSDK](https://github.com/2061360308/NeteaseCloudMusic_PythonSDK) | 第三方 |
|
||||||
|
| Swift | [Lincb522](https://github.com/Lincb522) | [NeteaseCloudMusicApi-Swift](https://github.com/Lincb522/NeteaseCloudMusicAPI-Swift) | 第三方 |
|
||||||
|
| Rust | [SPlayer-Dev](https://github.com/splayer-dev) | [ncm-api-rs](https://github.com/SPlayer-Dev/ncm-api-rs) | 第三方 |
|
||||||
|
|
||||||
|
|
||||||
|
### 依赖此项目的优秀开源项目
|
||||||
|
|
||||||
|
- [qier2222/YesPlayMusic](https://github.com/qier222/YesPlayMusic)
|
||||||
|
|
||||||
|
- [MaigoLabs/amaoke.app](https://github.com/MaigoLabs/amaoke.app)
|
||||||
|
|
||||||
|
- [Yueby/music-together](https://github.com/Yueby/music-together)
|
||||||
|
|
||||||
|
- [chthollyphlie/folia-major](https://github.com/chthollyphile/folia-major)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[MIT License](https://github.com/IamFurina/NeteaseCloudMusicApiReborn/blob/main/LICENSE)
|
[MIT License](https://github.com/MoeFurina/NeteaseCloudMusicApiEnhanced/blob/main/LICENSE)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"functions": [
|
|
||||||
{
|
|
||||||
"path": "/(.*)",
|
|
||||||
"method": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
||||||
"handler": "index.js",
|
|
||||||
"headers": {
|
|
||||||
"Access-Control-Allow-Credentials": "true",
|
|
||||||
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
||||||
"Access-Control-Allow-Headers": "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
const { defineConfig, globalIgnores } = require('eslint/config')
|
||||||
|
|
||||||
|
const html = require('eslint-plugin-html')
|
||||||
|
const globals = require('globals')
|
||||||
|
const tsParser = require('@typescript-eslint/parser')
|
||||||
|
const js = require('@eslint/js')
|
||||||
|
|
||||||
|
const { FlatCompat } = require('@eslint/eslintrc')
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
allConfig: js.configs.all,
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = defineConfig([
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
sourceType: 'module',
|
||||||
|
parserOptions: {
|
||||||
|
parser: 'babel-eslint',
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: {
|
||||||
|
html,
|
||||||
|
},
|
||||||
|
|
||||||
|
extends: compat.extends('plugin:prettier/recommended'),
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
'prettier/prettier': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
endOfLine: 'auto',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
indent: [
|
||||||
|
'error',
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
SwitchCase: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
'space-infix-ops': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
int32Hint: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
'key-spacing': [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
beforeColon: false,
|
||||||
|
afterColon: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
'no-octal': 2,
|
||||||
|
'no-redeclare': 2,
|
||||||
|
'comma-spacing': 2,
|
||||||
|
'no-new-object': 2,
|
||||||
|
'arrow-spacing': 2,
|
||||||
|
|
||||||
|
quotes: [
|
||||||
|
2,
|
||||||
|
'single',
|
||||||
|
{
|
||||||
|
avoidEscape: true,
|
||||||
|
allowTemplateLiterals: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
globalIgnores(['**/public/']),
|
||||||
|
{
|
||||||
|
files: ['**/*.ts'],
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
},
|
||||||
|
extends: compat.extends('plugin:@typescript-eslint/recommended'),
|
||||||
|
},
|
||||||
|
])
|
||||||
@@ -2,6 +2,7 @@ const fs = require('fs')
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const { register_anonimous } = require('./main')
|
const { register_anonimous } = require('./main')
|
||||||
const { cookieToJson, generateRandomChineseIP } = require('./util/index')
|
const { cookieToJson, generateRandomChineseIP } = require('./util/index')
|
||||||
|
const { getXeapiPublicKey } = require('./util/xeapiKey')
|
||||||
const tmpPath = require('os').tmpdir()
|
const tmpPath = require('os').tmpdir()
|
||||||
|
|
||||||
async function generateConfig() {
|
async function generateConfig() {
|
||||||
@@ -20,5 +21,21 @@ async function generateConfig() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
let currentPublicKey = {}
|
||||||
|
try {
|
||||||
|
currentPublicKey = JSON.parse(
|
||||||
|
fs.readFileSync(path.resolve(tmpPath, 'xeapi_public_key'), 'utf-8'),
|
||||||
|
)
|
||||||
|
} catch (_) {}
|
||||||
|
const publicKey = await getXeapiPublicKey(currentPublicKey, global.deviceId)
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.resolve(tmpPath, 'xeapi_public_key'),
|
||||||
|
JSON.stringify(publicKey),
|
||||||
|
'utf-8',
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
module.exports = generateConfig
|
module.exports = generateConfig
|
||||||
|
|||||||
Vendored
+756
@@ -2,6 +2,7 @@ export interface RequestBaseConfig {
|
|||||||
cookie?: string
|
cookie?: string
|
||||||
realIP?: string // IPv4/IPv6 filled in X-Real-IP
|
realIP?: string // IPv4/IPv6 filled in X-Real-IP
|
||||||
proxy?: string // HTTP proxy
|
proxy?: string // HTTP proxy
|
||||||
|
randomCNIP?: boolean // Whether to use a random Chinese IP address filled in X-Real-IP
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MultiPageConfig {
|
export interface MultiPageConfig {
|
||||||
@@ -1165,6 +1166,13 @@ export function song_url_v1(
|
|||||||
params: { id: string | number; level: SoundQualityType } & RequestBaseConfig,
|
params: { id: string | number; level: SoundQualityType } & RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_copyright_rcmd(
|
||||||
|
params: {
|
||||||
|
songid?: string | number
|
||||||
|
id?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
export function top_album(
|
export function top_album(
|
||||||
params: {
|
params: {
|
||||||
area?: AlbumListArea
|
area?: AlbumListArea
|
||||||
@@ -1466,6 +1474,20 @@ export function artist_new_song(
|
|||||||
} & RequestBaseConfig,
|
} & RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function artist_new_song_playall(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function artist_new_song_mv_list_v2(
|
||||||
|
params: {
|
||||||
|
startTimestamp?: number | string
|
||||||
|
before?: number | string
|
||||||
|
sourceType?: number | string
|
||||||
|
limit?: number | string
|
||||||
|
firstRequest?: boolean | string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
export function artist_detail(
|
export function artist_detail(
|
||||||
params: {
|
params: {
|
||||||
id: number | string
|
id: number | string
|
||||||
@@ -1586,6 +1608,10 @@ export function vip_growthpoint_get(
|
|||||||
} & RequestBaseConfig,
|
} & RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function vip_growthpoint_getall(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
export function artist_fans(
|
export function artist_fans(
|
||||||
params: { id: number | string } & MultiPageConfig & RequestBaseConfig,
|
params: { id: number | string } & MultiPageConfig & RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
@@ -1714,6 +1740,8 @@ export function nickname_check(
|
|||||||
|
|
||||||
export function musician_tasks_new(params: RequestBaseConfig): Promise<Response>
|
export function musician_tasks_new(params: RequestBaseConfig): Promise<Response>
|
||||||
|
|
||||||
|
export function musician_vip_tasks(params: RequestBaseConfig): Promise<Response>
|
||||||
|
|
||||||
export function playlist_update_playcount(
|
export function playlist_update_playcount(
|
||||||
params: {
|
params: {
|
||||||
id?: number | string
|
id?: number | string
|
||||||
@@ -1831,3 +1859,731 @@ export function voice_lyric(
|
|||||||
id: number | string
|
id: number | string
|
||||||
} & RequestBaseConfig,
|
} & RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function aidj_content_rcmd(
|
||||||
|
params: {
|
||||||
|
latitude?: string | number
|
||||||
|
longitude?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function album_privilege(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function artist_detail_dynamic(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function artist_follow_count(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function broadcast_category_region_get(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function broadcast_channel_collect_list(
|
||||||
|
params: MultiPageConfig & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function broadcast_channel_currentinfo(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function broadcast_channel_list(
|
||||||
|
params: {
|
||||||
|
categoryId?: string | number
|
||||||
|
regionId?: string | number
|
||||||
|
lastId?: string | number
|
||||||
|
score?: string | number
|
||||||
|
} & MultiPageConfig &
|
||||||
|
RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function broadcast_sub(
|
||||||
|
params: {
|
||||||
|
t: SubAction
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function chart_detail(
|
||||||
|
params: {
|
||||||
|
chartCode: string | number
|
||||||
|
targetId: string | number
|
||||||
|
targetType: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function chart_song_detail(
|
||||||
|
params: {
|
||||||
|
chartCode: string | number
|
||||||
|
targetId: string | number
|
||||||
|
targetType: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function cloud_import(
|
||||||
|
params: {
|
||||||
|
md5: string
|
||||||
|
id?: string | number
|
||||||
|
bitrate?: string | number
|
||||||
|
fileSize?: string | number
|
||||||
|
artist?: string
|
||||||
|
album?: string
|
||||||
|
song?: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function cloud_lyric_get(
|
||||||
|
params: {
|
||||||
|
uid: string | number
|
||||||
|
sid: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function cloud_upload_complete(
|
||||||
|
params: {
|
||||||
|
songId: string | number
|
||||||
|
resourceId: string | number
|
||||||
|
md5: string
|
||||||
|
filename: string
|
||||||
|
song?: string
|
||||||
|
artist?: string
|
||||||
|
album?: string
|
||||||
|
bitrate?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function cloud_upload_token(
|
||||||
|
params: {
|
||||||
|
md5: string
|
||||||
|
fileSize: string | number
|
||||||
|
filename: string
|
||||||
|
bitrate?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function comment_info_list(
|
||||||
|
params: {
|
||||||
|
ids?: string
|
||||||
|
type?: CommentType
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function comment_report(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
cid: string | number
|
||||||
|
reason: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function creator_authinfo_get(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function dj_difm_all_style_channel(
|
||||||
|
params: {
|
||||||
|
sources?: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function dj_difm_channel_subscribe(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function dj_difm_channel_unsubscribe(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function dj_difm_playing_tracks_list(
|
||||||
|
params: {
|
||||||
|
channelId: string | number
|
||||||
|
limit?: string | number
|
||||||
|
source?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function dj_difm_subscribe_channels_get(
|
||||||
|
params: {
|
||||||
|
sources?: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function fanscenter_basicinfo_age_get(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function fanscenter_basicinfo_gender_get(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function fanscenter_basicinfo_province_get(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function fanscenter_overview_get(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function fanscenter_trend_list(
|
||||||
|
params: {
|
||||||
|
startTime?: string | number
|
||||||
|
endTime?: string | number
|
||||||
|
type?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function lbs_city_code(
|
||||||
|
params: {
|
||||||
|
bizCode?: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function listen_data_realtime_report(
|
||||||
|
params: {
|
||||||
|
type?: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function listen_data_report(
|
||||||
|
params: {
|
||||||
|
type?: string
|
||||||
|
endTime?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function listen_data_song_play_rank(
|
||||||
|
params: {
|
||||||
|
type?: string
|
||||||
|
endTime?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function listen_data_today_song(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function listen_data_total(params: RequestBaseConfig): Promise<Response>
|
||||||
|
|
||||||
|
export function listen_data_year_report(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function listentogether_accept(
|
||||||
|
params: {
|
||||||
|
roomId: string | number
|
||||||
|
inviterId: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function listentogether_end(
|
||||||
|
params: {
|
||||||
|
roomId: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function listentogether_heatbeat(
|
||||||
|
params: {
|
||||||
|
roomId: string | number
|
||||||
|
songId: string | number
|
||||||
|
playStatus: string | number
|
||||||
|
progress: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function listentogether_play_command(
|
||||||
|
params: {
|
||||||
|
roomId: string | number
|
||||||
|
commandType: string | number
|
||||||
|
progress?: string | number
|
||||||
|
playStatus: string | number
|
||||||
|
formerSongId: string | number
|
||||||
|
targetSongId: string | number
|
||||||
|
clientSeq: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function listentogether_room_check(
|
||||||
|
params: {
|
||||||
|
roomId: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function listentogether_room_create(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function listentogether_sync_list_command(
|
||||||
|
params: {
|
||||||
|
roomId: string | number
|
||||||
|
commandType: string | number
|
||||||
|
userId: string | number
|
||||||
|
version: string | number
|
||||||
|
randomList: string
|
||||||
|
displayList: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function listentogether_sync_playlist_get(
|
||||||
|
params: {
|
||||||
|
roomId: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function mlog_music_rcmd(
|
||||||
|
params: {
|
||||||
|
mvid?: string | number
|
||||||
|
songid?: string | number
|
||||||
|
limit?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function music_first_listen_info(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function personal_fm_mode(
|
||||||
|
params: {
|
||||||
|
mode: string
|
||||||
|
submode?: string
|
||||||
|
limit?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function playlist_category_list(
|
||||||
|
params: {
|
||||||
|
cat?: string
|
||||||
|
limit?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function playlist_detail_rcmd_get(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function playlist_import_name_task_create(
|
||||||
|
params: {
|
||||||
|
local?: string
|
||||||
|
importStarPlaylist?: boolean
|
||||||
|
['name']?: string
|
||||||
|
['description']?: string
|
||||||
|
['id']?: string | number
|
||||||
|
['url']?: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function playlist_import_task_status(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function playlist_privacy(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function playmode_song_vector(
|
||||||
|
params: {
|
||||||
|
ids: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function radio_sport_get(
|
||||||
|
params: {
|
||||||
|
bpm?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function recent_listen_list(params: RequestBaseConfig): Promise<Response>
|
||||||
|
|
||||||
|
export function recommend_songs_dislike(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function register_anonimous(params: RequestBaseConfig): Promise<Response>
|
||||||
|
|
||||||
|
export function sati_resource_list(
|
||||||
|
params: {
|
||||||
|
tag: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function sati_resource_list_more(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function sati_resource_sub(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
cancel?: string | boolean
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function sati_resource_sub_list(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function sati_tag_list(params: RequestBaseConfig): Promise<Response>
|
||||||
|
|
||||||
|
export function sati_timescene_resources_get(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function search_match(
|
||||||
|
params: {
|
||||||
|
title?: string
|
||||||
|
album?: string
|
||||||
|
artist?: string
|
||||||
|
duration?: string | number
|
||||||
|
md5?: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function search_suggest_pc(
|
||||||
|
params: {
|
||||||
|
keyword: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function send_album(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
msg?: string
|
||||||
|
user_ids: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function send_song(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
msg?: string
|
||||||
|
user_ids: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_chorus(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_creators(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_downlist(
|
||||||
|
params: MultiPageConfig & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_download_url_v1(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
level: SoundQualityType
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_dynamic_cover(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_like(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
like?: string | boolean
|
||||||
|
uid?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_like_check(
|
||||||
|
params: {
|
||||||
|
ids: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_lyrics_mark(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_lyrics_mark_add(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
markId?: string
|
||||||
|
data?: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_lyrics_mark_del(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_lyrics_mark_user_page(
|
||||||
|
params: MultiPageConfig & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_monthdownlist(
|
||||||
|
params: MultiPageConfig & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_music_detail(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_red_count(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_singledownlist(
|
||||||
|
params: MultiPageConfig & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_url_match(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
source?: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function song_url_ncmget(params: RequestBaseConfig): Promise<Response>
|
||||||
|
|
||||||
|
export function song_url_v1_302(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
level: SoundQualityType
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function starpick_comments_summary(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function summary_annual(
|
||||||
|
params: {
|
||||||
|
year: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function threshold_detail_get(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function toplist_detail_v2(params: RequestBaseConfig): Promise<Response>
|
||||||
|
|
||||||
|
export function ugc_album_get(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function ugc_artist_get(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function ugc_artist_search(
|
||||||
|
params: {
|
||||||
|
keyword: string
|
||||||
|
limit?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function ugc_detail(
|
||||||
|
params: {
|
||||||
|
auditStatus?: string
|
||||||
|
type?: string | number
|
||||||
|
sortBy?: string
|
||||||
|
order?: string
|
||||||
|
} & MultiPageConfig &
|
||||||
|
RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function ugc_mv_get(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function ugc_song_get(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function ugc_user_devote(params: RequestBaseConfig): Promise<Response>
|
||||||
|
|
||||||
|
export function user_detail_new(
|
||||||
|
params: {
|
||||||
|
uid: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function user_follow_mixed(
|
||||||
|
params: {
|
||||||
|
size?: string | number
|
||||||
|
cursor?: string | number
|
||||||
|
scene?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function user_medal(
|
||||||
|
params: {
|
||||||
|
uid: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function user_mutualfollow_get(
|
||||||
|
params: {
|
||||||
|
uid: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function user_playlist_collect(
|
||||||
|
params: {
|
||||||
|
uid: string | number
|
||||||
|
} & MultiPageConfig &
|
||||||
|
RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function user_playlist_create(
|
||||||
|
params: {
|
||||||
|
uid: string | number
|
||||||
|
} & MultiPageConfig &
|
||||||
|
RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function user_social_status(
|
||||||
|
params: {
|
||||||
|
uid: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function user_social_status_edit(
|
||||||
|
params: {
|
||||||
|
type: string | number
|
||||||
|
iconUrl?: string
|
||||||
|
content?: string
|
||||||
|
actionUrl?: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function user_social_status_rcmd(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function user_social_status_support(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function verify_getQr(
|
||||||
|
params: {
|
||||||
|
vid: string | number
|
||||||
|
type: string | number
|
||||||
|
token: string
|
||||||
|
evid: string
|
||||||
|
sign: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function verify_qrcodestatus(
|
||||||
|
params: {
|
||||||
|
qr: string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function vip_sign(params: RequestBaseConfig): Promise<Response>
|
||||||
|
|
||||||
|
export function vip_sign_info(params: RequestBaseConfig): Promise<Response>
|
||||||
|
|
||||||
|
export function vip_tasks_v1(
|
||||||
|
params: {
|
||||||
|
id?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function voice_detail(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function voice_upload(
|
||||||
|
params: {
|
||||||
|
songFile: {
|
||||||
|
name: string
|
||||||
|
data: string | Buffer
|
||||||
|
}
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function voicelist_detail(
|
||||||
|
params: {
|
||||||
|
id: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function voicelist_list(
|
||||||
|
params: {
|
||||||
|
voiceListId: string | number
|
||||||
|
} & MultiPageConfig &
|
||||||
|
RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function voicelist_my_created(
|
||||||
|
params: {
|
||||||
|
limit?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function voicelist_search(
|
||||||
|
params: {
|
||||||
|
keyword?: string
|
||||||
|
limit?: string | number
|
||||||
|
offset?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function voicelist_trans(
|
||||||
|
params: {
|
||||||
|
radioId?: string | number
|
||||||
|
programId?: string | number
|
||||||
|
position?: string | number
|
||||||
|
} & MultiPageConfig &
|
||||||
|
RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
## 环境
|
|
||||||
- 系统/平台: <你的系统和平台>
|
|
||||||
|
|
||||||
- nodejs 版本: <你的 NodeJS 版本号>
|
|
||||||
|
|
||||||
- API版本:<运行的云音乐 API 的版本号, 对应 package.json 里面的 version>
|
|
||||||
|
|
||||||
## 出现问题
|
|
||||||
<出现的问题>
|
|
||||||
|
|
||||||
## 重现步骤
|
|
||||||
<重现步骤>
|
|
||||||
|
|
||||||
## 期待效果
|
|
||||||
<现在的效果,期待的效果>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
>先看文档有没有相关说明,调用前须知必看
|
|
||||||
|
|
||||||
>先在 issues 搜一下是否有相似问题,没有再发,否则直接关闭
|
|
||||||
|
|
||||||
>不处理别人搭建的线上服务的问题,此项目不提供任何线上服务,请自行搭建
|
|
||||||
|
|
||||||
>重现步骤尽量详细,不能含糊不清,包含请求地址和对应参数以及操作过程描述,不是每个人都喜欢猜别人遇到了什么问题和找参数一个个试,也比较浪费时间
|
|
||||||
|
|
||||||
>如果不是提建议,提 issues 如果不照着模版来将不会优先处理或直接关闭
|
|
||||||
|
|
||||||
>460 cheating 的问题把 `util/request.js` 里面的 `headers['X-Real-IP']` 的注释取消掉就好
|
|
||||||
@@ -3,47 +3,61 @@ const path = require('path')
|
|||||||
const tmpPath = require('os').tmpdir()
|
const tmpPath = require('os').tmpdir()
|
||||||
const { cookieToJson } = require('./util')
|
const { cookieToJson } = require('./util')
|
||||||
|
|
||||||
if (!fs.existsSync(path.resolve(tmpPath, 'anonymous_token'))) {
|
const anonymousTokenPath = path.resolve(tmpPath, 'anonymous_token')
|
||||||
fs.writeFileSync(path.resolve(tmpPath, 'anonymous_token'), '', 'utf-8')
|
if (!fs.existsSync(anonymousTokenPath)) {
|
||||||
|
fs.writeFileSync(anonymousTokenPath, '', 'utf-8')
|
||||||
}
|
}
|
||||||
|
|
||||||
let firstRun = true
|
|
||||||
/** @type {Record<string, any>} */
|
/** @type {Record<string, any>} */
|
||||||
let obj = {}
|
let obj = {}
|
||||||
fs.readdirSync(path.join(__dirname, 'module'))
|
|
||||||
.reverse()
|
|
||||||
.forEach((file) => {
|
|
||||||
if (!file.endsWith('.js')) return
|
|
||||||
let fileModule = require(path.join(__dirname, 'module', file))
|
|
||||||
let fn = file.split('.').shift() || ''
|
|
||||||
obj[fn] = function (data = {}) {
|
|
||||||
if (typeof data.cookie === 'string') {
|
|
||||||
data.cookie = cookieToJson(data.cookie)
|
|
||||||
}
|
|
||||||
return fileModule(
|
|
||||||
{
|
|
||||||
...data,
|
|
||||||
cookie: data.cookie ? data.cookie : {},
|
|
||||||
},
|
|
||||||
async (...args) => {
|
|
||||||
if (firstRun) {
|
|
||||||
firstRun = false
|
|
||||||
const generateConfig = require('./generateConfig')
|
|
||||||
await generateConfig()
|
|
||||||
}
|
|
||||||
// 待优化
|
|
||||||
const request = require('./util/request')
|
|
||||||
|
|
||||||
return request(...args)
|
const modulePath = path.join(__dirname, 'module')
|
||||||
},
|
const moduleFiles = fs.readdirSync(modulePath).reverse()
|
||||||
)
|
|
||||||
}
|
let requestModule = null
|
||||||
})
|
|
||||||
|
moduleFiles.forEach((file) => {
|
||||||
|
if (!file.endsWith('.js')) return
|
||||||
|
|
||||||
|
const filePath = path.join(modulePath, file)
|
||||||
|
let fileModule = require(filePath)
|
||||||
|
let fn = file.split('.').shift() || ''
|
||||||
|
|
||||||
|
obj[fn] = function (data = {}) {
|
||||||
|
const cookie =
|
||||||
|
typeof data.cookie === 'string'
|
||||||
|
? cookieToJson(data.cookie)
|
||||||
|
: data.cookie || {}
|
||||||
|
|
||||||
|
return fileModule(
|
||||||
|
{
|
||||||
|
...data,
|
||||||
|
cookie,
|
||||||
|
},
|
||||||
|
async (...args) => {
|
||||||
|
if (!requestModule) {
|
||||||
|
requestModule = require('./util/request')
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestModule(...args)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let serverModule = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Record<string, any> & import("./server")}
|
* @type {Record<string, any> & import("./server")}
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
...require('./server'),
|
get server() {
|
||||||
|
if (!serverModule) {
|
||||||
|
serverModule = require('./server')
|
||||||
|
}
|
||||||
|
return serverModule
|
||||||
|
},
|
||||||
...obj,
|
...obj,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object.assign(module.exports, require('./server'))
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// 获取广告
|
||||||
|
// 基于逆向网易云音乐 v9.5.45 RN Hermes 源码:
|
||||||
|
// - adService.getAd() → Network.nativeRequest → /api/ad/get
|
||||||
|
// - 需要 X-antiCheatToken 易盾反作弊头
|
||||||
|
// - 使用 xeapi 加密
|
||||||
|
//
|
||||||
|
// 返回 extra.reqId 可用于 ad_listening_rights_gain 的 reqUid
|
||||||
|
//
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
|
||||||
|
module.exports = async (query, request) => {
|
||||||
|
const data = {
|
||||||
|
type_ids: query.type_ids || '["400002_0"]',
|
||||||
|
}
|
||||||
|
|
||||||
|
const option = createOption(query, 'xeapi')
|
||||||
|
option.checkToken = true
|
||||||
|
|
||||||
|
const res = await request(`/api/ad/get`, data, option)
|
||||||
|
const raw = res.body
|
||||||
|
|
||||||
|
// 提取广告中的 req_id (用于领取权益)
|
||||||
|
let reqId = ''
|
||||||
|
try {
|
||||||
|
if (raw?.ads) {
|
||||||
|
const ad = Object.values(raw.ads)[0]
|
||||||
|
if (ad?.extJson) {
|
||||||
|
const ext = JSON.parse(ad.extJson)
|
||||||
|
reqId = ext?.contextInfo?.req_id || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
code: 200,
|
||||||
|
ads: raw?.ads || null,
|
||||||
|
message: raw?.message || null,
|
||||||
|
extra: { reqId },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// 看广告免费听歌 - 领取免费听权益
|
||||||
|
// 请求流程(基于逆向网易云音乐 v9.5.45 RN Hermes 源码):
|
||||||
|
// 1. 从广告平台拉广告 → 用户看完/点击广告 → 获取 ad 对象的 extJson.contextInfo.req_id 作为 reqUid
|
||||||
|
// 2. 调用本接口传入 reqUid 及相关权益参数,领取免费听权益
|
||||||
|
// 3. 服务器返回 gainFlag 等标识,用于展示领取结果
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
const adGet = require('./ad_get.js')
|
||||||
|
|
||||||
|
module.exports = async (query, request) => {
|
||||||
|
const time = Date.now()
|
||||||
|
|
||||||
|
// 如果未传入 reqUid,自动从 ad_get 获取最新广告的 req_id
|
||||||
|
let reqUid = query.reqUid || ''
|
||||||
|
if (!reqUid) {
|
||||||
|
try {
|
||||||
|
const adRes = await adGet(
|
||||||
|
{ ...query, type_ids: query.type_ids || '["400002_0"]' },
|
||||||
|
request,
|
||||||
|
)
|
||||||
|
reqUid = adRes?.body?.extra?.reqId || ''
|
||||||
|
console.log(`自动获取 reqUid: ${reqUid}`)
|
||||||
|
} catch (e) {
|
||||||
|
// 获取广告失败,后续请求会因缺少 reqUid 被拒绝
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rightsParam = {
|
||||||
|
// 必填: 广告请求 ID,自动从 ad_get 获取
|
||||||
|
reqUid,
|
||||||
|
|
||||||
|
// 广告创意类型 (默认 1)
|
||||||
|
creativeType: parseInt(query.creativeType || 2),
|
||||||
|
|
||||||
|
// 时间戳
|
||||||
|
exposureTime: query.exposureTime || time,
|
||||||
|
clickTime: query.clickTime || time,
|
||||||
|
|
||||||
|
// 权益领取方式
|
||||||
|
rightsGainMethod: parseInt(query.rightsGainMethod || 2),
|
||||||
|
|
||||||
|
// 权益时长相关
|
||||||
|
rightsGainDuration: query.rightsGainDuration
|
||||||
|
? parseInt(query.rightsGainDuration)
|
||||||
|
: undefined,
|
||||||
|
extraRightsGainMethod: query.extraRightsGainMethod
|
||||||
|
? parseInt(query.extraRightsGainMethod)
|
||||||
|
: undefined,
|
||||||
|
extraRightsGainDuration: query.extraRightsGainDuration
|
||||||
|
? parseInt(query.extraRightsGainDuration)
|
||||||
|
: undefined,
|
||||||
|
nextRightsGainDuration: query.nextRightsGainDuration
|
||||||
|
? parseInt(query.nextRightsGainDuration)
|
||||||
|
: undefined,
|
||||||
|
|
||||||
|
// 来源标识 | 权益扩展信息
|
||||||
|
source: query.source || undefined,
|
||||||
|
rightsExtJson: query.rightsExtJson || undefined,
|
||||||
|
|
||||||
|
// 应用信息(下载类广告)
|
||||||
|
appInfo: query.appInfo ? JSON.parse(query.appInfo) : undefined,
|
||||||
|
installed: query.installed ? parseInt(query.installed) : undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理 undefined 字段
|
||||||
|
Object.keys(rightsParam).forEach((key) => {
|
||||||
|
if (rightsParam[key] === undefined) delete rightsParam[key]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 将参数序列化为 reqParam 字符串 (与 RN 源码完全一致)
|
||||||
|
const data = {
|
||||||
|
reqParam: JSON.stringify(rightsParam),
|
||||||
|
}
|
||||||
|
|
||||||
|
const option = createOption(query, 'xeapi')
|
||||||
|
// 关键: 开启 X-antiCheatToken 头
|
||||||
|
option.checkToken = true
|
||||||
|
|
||||||
|
const res = await request(`/api/ad/listening/rights/gain`, data, option)
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
code: 200,
|
||||||
|
data: res.body,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// 获取关注歌手的新歌曲和 MV
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
startTimestamp: query.startTimestamp || query.before || Date.now(),
|
||||||
|
sourceType: query.sourceType || 1,
|
||||||
|
limit: query.limit || 10,
|
||||||
|
firstRequest: query.firstRequest ?? true,
|
||||||
|
}
|
||||||
|
return request(
|
||||||
|
`/api/sub/artist/new/works/song-mv/list/v2`,
|
||||||
|
data,
|
||||||
|
createOption(query, 'eapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// 获取所有关注歌手最近的 50 首新歌
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
return request(
|
||||||
|
`/api/sub/artist/new/works/song/playall`,
|
||||||
|
{},
|
||||||
|
createOption(query, 'eapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ const createOption = require('../util/option.js')
|
|||||||
module.exports = (query, request) => {
|
module.exports = (query, request) => {
|
||||||
const data = {
|
const data = {
|
||||||
ctcode: query.ctcode || '86',
|
ctcode: query.ctcode || '86',
|
||||||
|
secrete: 'music_middleuser_pclogin',
|
||||||
cellphone: query.phone,
|
cellphone: query.phone,
|
||||||
}
|
}
|
||||||
return request(`/api/sms/captcha/sent`, data, createOption(query, 'weapi'))
|
return request(`/api/sms/captcha/sent`, data, createOption(query, 'weapi'))
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// 获取指定维度音乐排行榜详情
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
chartCode: query.chartCode,
|
||||||
|
targetId: query.targetId,
|
||||||
|
targetType: query.targetType,
|
||||||
|
}
|
||||||
|
return request(`/api/chart/detail`, data, createOption(query))
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// 获取指定维度音乐排行榜列表
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
chartCode: query.chartCode,
|
||||||
|
targetId: query.targetId,
|
||||||
|
targetType: query.targetType,
|
||||||
|
}
|
||||||
|
return request(`/api/chart/song/detail`, data, createOption(query))
|
||||||
|
}
|
||||||
+141
-123
@@ -1,24 +1,26 @@
|
|||||||
const mm = require('music-metadata')
|
|
||||||
const uploadPlugin = require('../plugins/songUpload')
|
const uploadPlugin = require('../plugins/songUpload')
|
||||||
const md5 = require('md5')
|
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
const logger = require('../util/logger.js')
|
const logger = require('../util/logger.js')
|
||||||
|
const {
|
||||||
|
isTempFile,
|
||||||
|
getFileSize,
|
||||||
|
getFileMd5,
|
||||||
|
cleanupTempFile,
|
||||||
|
getFileExtension,
|
||||||
|
sanitizeFilename,
|
||||||
|
} = require('../util/fileHelper')
|
||||||
|
|
||||||
|
let mm
|
||||||
module.exports = async (query, request) => {
|
module.exports = async (query, request) => {
|
||||||
let ext = 'mp3'
|
mm = require('music-metadata')
|
||||||
// if (query.songFile.name.indexOf('flac') > -1) {
|
|
||||||
// ext = 'flac'
|
|
||||||
// }
|
|
||||||
if (query.songFile.name.includes('.')) {
|
|
||||||
ext = query.songFile.name.split('.').pop()
|
|
||||||
}
|
|
||||||
query.songFile.name = Buffer.from(query.songFile.name, 'latin1').toString(
|
query.songFile.name = Buffer.from(query.songFile.name, 'latin1').toString(
|
||||||
'utf-8',
|
'utf-8',
|
||||||
)
|
)
|
||||||
const filename = query.songFile.name
|
const ext = getFileExtension(query.songFile.name)
|
||||||
.replace('.' + ext, '')
|
const filename = sanitizeFilename(query.songFile.name)
|
||||||
.replace(/\s/g, '')
|
|
||||||
.replace(/\./g, '_')
|
|
||||||
const bitrate = 999000
|
const bitrate = 999000
|
||||||
|
|
||||||
if (!query.songFile) {
|
if (!query.songFile) {
|
||||||
return Promise.reject({
|
return Promise.reject({
|
||||||
status: 500,
|
status: 500,
|
||||||
@@ -28,119 +30,135 @@ module.exports = async (query, request) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (!query.songFile.md5) {
|
|
||||||
// 命令行上传没有md5和size信息,需要填充
|
const useTemp = isTempFile(query.songFile)
|
||||||
query.songFile.md5 = md5(query.songFile.data)
|
let fileSize = await getFileSize(query.songFile)
|
||||||
query.songFile.size = query.songFile.data.byteLength
|
let fileMd5 = await getFileMd5(query.songFile)
|
||||||
}
|
|
||||||
const res = await request(
|
query.songFile.md5 = fileMd5
|
||||||
`/api/cloud/upload/check`,
|
query.songFile.size = fileSize
|
||||||
{
|
|
||||||
bitrate: String(bitrate),
|
|
||||||
ext: '',
|
|
||||||
length: query.songFile.size,
|
|
||||||
md5: query.songFile.md5,
|
|
||||||
songId: '0',
|
|
||||||
version: 1,
|
|
||||||
},
|
|
||||||
createOption(query),
|
|
||||||
)
|
|
||||||
let artist = ''
|
|
||||||
let album = ''
|
|
||||||
let songName = ''
|
|
||||||
try {
|
try {
|
||||||
const metadata = await mm.parseBuffer(
|
const res = await request(
|
||||||
query.songFile.data,
|
`/api/cloud/upload/check`,
|
||||||
query.songFile.mimetype,
|
{
|
||||||
|
bitrate: String(bitrate),
|
||||||
|
ext: '',
|
||||||
|
length: fileSize,
|
||||||
|
md5: fileMd5,
|
||||||
|
songId: '0',
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
createOption(query),
|
||||||
)
|
)
|
||||||
const info = metadata.common
|
|
||||||
|
|
||||||
if (info.title) {
|
let artist = ''
|
||||||
songName = info.title
|
let album = ''
|
||||||
}
|
let songName = ''
|
||||||
if (info.album) {
|
|
||||||
album = info.album
|
|
||||||
}
|
|
||||||
if (info.artist) {
|
|
||||||
artist = info.artist
|
|
||||||
}
|
|
||||||
// if (metadata.native.ID3v1) {
|
|
||||||
// metadata.native.ID3v1.forEach((item) => {
|
|
||||||
// // logger.info(item.id, item.value)
|
|
||||||
// if (item.id === 'title') {
|
|
||||||
// songName = item.value
|
|
||||||
// }
|
|
||||||
// if (item.id === 'artist') {
|
|
||||||
// artist = item.value
|
|
||||||
// }
|
|
||||||
// if (item.id === 'album') {
|
|
||||||
// album = item.value
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// // logger.info({
|
|
||||||
// // songName,
|
|
||||||
// // album,
|
|
||||||
// // songName,
|
|
||||||
// // })
|
|
||||||
// }
|
|
||||||
// logger.info({
|
|
||||||
// songName,
|
|
||||||
// album,
|
|
||||||
// songName,
|
|
||||||
// })
|
|
||||||
} catch (error) {
|
|
||||||
logger.info(error)
|
|
||||||
}
|
|
||||||
const tokenRes = await request(
|
|
||||||
`/api/nos/token/alloc`,
|
|
||||||
{
|
|
||||||
bucket: '',
|
|
||||||
ext: ext,
|
|
||||||
filename: filename,
|
|
||||||
local: false,
|
|
||||||
nos_product: 3,
|
|
||||||
type: 'audio',
|
|
||||||
md5: query.songFile.md5,
|
|
||||||
},
|
|
||||||
createOption(query),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (res.body.needUpload) {
|
try {
|
||||||
const uploadInfo = await uploadPlugin(query, request)
|
let metadata
|
||||||
// logger.info('uploadInfo', uploadInfo.body.result.resourceId)
|
if (useTemp) {
|
||||||
}
|
metadata = await mm.parseFile(query.songFile.tempFilePath)
|
||||||
// logger.info(tokenRes.body.result)
|
} else {
|
||||||
const res2 = await request(
|
metadata = await mm.parseBuffer(
|
||||||
`/api/upload/cloud/info/v2`,
|
query.songFile.data,
|
||||||
{
|
query.songFile.mimetype,
|
||||||
md5: query.songFile.md5,
|
)
|
||||||
songid: res.body.songId,
|
}
|
||||||
filename: query.songFile.name,
|
const info = metadata.common
|
||||||
song: songName || filename,
|
if (info.title) songName = info.title
|
||||||
album: album || '未知专辑',
|
if (info.album) album = info.album
|
||||||
artist: artist || '未知艺术家',
|
if (info.artist) artist = info.artist
|
||||||
bitrate: String(bitrate),
|
} catch (error) {
|
||||||
resourceId: tokenRes.body.result.resourceId,
|
logger.info('元数据解析错误:', error.message)
|
||||||
},
|
}
|
||||||
createOption(query),
|
|
||||||
)
|
const tokenRes = await request(
|
||||||
// logger.info({ res2, privateCloud: res2.body.privateCloud })
|
`/api/nos/token/alloc`,
|
||||||
// logger.info(res.body.songId, 'songid')
|
{
|
||||||
const res3 = await request(
|
bucket: '',
|
||||||
`/api/cloud/pub/v2`,
|
ext: ext,
|
||||||
{
|
filename: filename,
|
||||||
songid: res2.body.songId,
|
local: false,
|
||||||
},
|
nos_product: 3,
|
||||||
createOption(query),
|
type: 'audio',
|
||||||
)
|
md5: fileMd5,
|
||||||
// logger.info({ res3 })
|
},
|
||||||
return {
|
createOption(query),
|
||||||
status: 200,
|
)
|
||||||
body: {
|
|
||||||
...res.body,
|
if (!tokenRes.body.result || !tokenRes.body.result.resourceId) {
|
||||||
...res3.body,
|
logger.error('Token分配失败:', tokenRes.body)
|
||||||
// ...uploadInfo,
|
return Promise.reject({
|
||||||
},
|
status: 500,
|
||||||
cookie: res.cookie,
|
body: {
|
||||||
|
code: 500,
|
||||||
|
msg: '获取上传token失败',
|
||||||
|
detail: tokenRes.body,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.body.needUpload) {
|
||||||
|
logger.info('需要上传,开始上传流程...')
|
||||||
|
try {
|
||||||
|
const uploadInfo = await uploadPlugin(query, request)
|
||||||
|
logger.info('上传完成:', uploadInfo?.body?.result?.resourceId)
|
||||||
|
} catch (uploadError) {
|
||||||
|
logger.error('上传失败:', uploadError)
|
||||||
|
return Promise.reject(uploadError)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.info('文件已存在,跳过上传')
|
||||||
|
}
|
||||||
|
|
||||||
|
const res2 = await request(
|
||||||
|
`/api/upload/cloud/info/v2`,
|
||||||
|
{
|
||||||
|
md5: fileMd5,
|
||||||
|
songid: res.body.songId,
|
||||||
|
filename: query.songFile.name,
|
||||||
|
song: songName || filename,
|
||||||
|
album: album || '未知专辑',
|
||||||
|
artist: artist || '未知艺术家',
|
||||||
|
bitrate: String(bitrate),
|
||||||
|
resourceId: tokenRes.body.result.resourceId,
|
||||||
|
},
|
||||||
|
createOption(query),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (res2.body.code !== 200) {
|
||||||
|
logger.error('云盘信息上传失败:', res2.body)
|
||||||
|
return Promise.reject({
|
||||||
|
status: res2.status || 500,
|
||||||
|
body: {
|
||||||
|
code: res2.body.code || 500,
|
||||||
|
msg: res2.body.msg || '上传云盘信息失败',
|
||||||
|
detail: res2.body,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const res3 = await request(
|
||||||
|
`/api/cloud/pub/v2`,
|
||||||
|
{
|
||||||
|
songid: res2.body.songId,
|
||||||
|
},
|
||||||
|
createOption(query),
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
...res.body,
|
||||||
|
...res3.body,
|
||||||
|
},
|
||||||
|
cookie: res.cookie,
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (useTemp) {
|
||||||
|
await cleanupTempFile(query.songFile.tempFilePath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// 获取云盘歌词
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
userId: query.uid,
|
||||||
|
songId: query.sid,
|
||||||
|
lv: -1,
|
||||||
|
kv: -1,
|
||||||
|
}
|
||||||
|
return request(`/api/cloud/lyric/get`, data, createOption(query, 'eapi'))
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
const createOption = require('../util/option.js')
|
||||||
|
|
||||||
|
module.exports = async (query, request) => {
|
||||||
|
const {
|
||||||
|
songId,
|
||||||
|
resourceId,
|
||||||
|
md5,
|
||||||
|
filename,
|
||||||
|
song,
|
||||||
|
artist,
|
||||||
|
album,
|
||||||
|
bitrate = 999000,
|
||||||
|
} = query
|
||||||
|
|
||||||
|
if (!songId || !resourceId || !md5 || !filename) {
|
||||||
|
return Promise.reject({
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
code: 400,
|
||||||
|
msg: '缺少必要参数: songId, resourceId, md5, filename',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const songName = song || filename.replace(/\.[^.]+$/, '')
|
||||||
|
|
||||||
|
const res2 = await request(
|
||||||
|
`/api/upload/cloud/info/v2`,
|
||||||
|
{
|
||||||
|
md5: md5,
|
||||||
|
songid: songId,
|
||||||
|
filename: filename,
|
||||||
|
song: songName,
|
||||||
|
album: album || '未知专辑',
|
||||||
|
artist: artist || '未知艺术家',
|
||||||
|
bitrate: String(bitrate),
|
||||||
|
resourceId: resourceId,
|
||||||
|
},
|
||||||
|
createOption(query),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (res2.body.code !== 200) {
|
||||||
|
return Promise.reject({
|
||||||
|
status: res2.status || 500,
|
||||||
|
body: {
|
||||||
|
code: res2.body.code || 500,
|
||||||
|
msg: res2.body.msg || '上传云盘信息失败',
|
||||||
|
detail: res2.body,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const res3 = await request(
|
||||||
|
`/api/cloud/pub/v2`,
|
||||||
|
{
|
||||||
|
songid: res2.body.songId,
|
||||||
|
},
|
||||||
|
createOption(query),
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
songId: res2.body.songId,
|
||||||
|
...res3.body,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cookie: res2.cookie,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
const { default: axios } = require('axios')
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
|
||||||
|
module.exports = async (query, request) => {
|
||||||
|
const { md5, fileSize, filename, bitrate = 999000 } = query
|
||||||
|
|
||||||
|
if (!md5 || !fileSize || !filename) {
|
||||||
|
return Promise.reject({
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
code: 400,
|
||||||
|
msg: '缺少必要参数: md5, fileSize, filename',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const ext = filename.includes('.') ? filename.split('.').pop() : 'mp3'
|
||||||
|
|
||||||
|
const checkRes = await request(
|
||||||
|
`/api/cloud/upload/check`,
|
||||||
|
{
|
||||||
|
bitrate: String(bitrate),
|
||||||
|
ext: '',
|
||||||
|
length: fileSize,
|
||||||
|
md5: md5,
|
||||||
|
songId: '0',
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
createOption(query),
|
||||||
|
)
|
||||||
|
|
||||||
|
const bucket = 'jd-musicrep-privatecloud-audio-public'
|
||||||
|
const tokenRes = await request(
|
||||||
|
`/api/nos/token/alloc`,
|
||||||
|
{
|
||||||
|
bucket: bucket,
|
||||||
|
ext: ext,
|
||||||
|
filename: filename
|
||||||
|
.replace(/\.[^.]+$/, '')
|
||||||
|
.replace(/\s/g, '')
|
||||||
|
.replace(/\./g, '_'),
|
||||||
|
local: false,
|
||||||
|
nos_product: 3,
|
||||||
|
type: 'audio',
|
||||||
|
md5: md5,
|
||||||
|
},
|
||||||
|
createOption(query, 'weapi'),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!tokenRes.body.result || !tokenRes.body.result.objectKey) {
|
||||||
|
return Promise.reject({
|
||||||
|
status: 500,
|
||||||
|
body: {
|
||||||
|
code: 500,
|
||||||
|
msg: '获取上传token失败',
|
||||||
|
detail: tokenRes.body,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let lbs
|
||||||
|
try {
|
||||||
|
lbs = (
|
||||||
|
await axios({
|
||||||
|
method: 'get',
|
||||||
|
url: `https://wanproxy.127.net/lbs?version=1.0&bucketname=${bucket}`,
|
||||||
|
timeout: 10000,
|
||||||
|
})
|
||||||
|
).data
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject({
|
||||||
|
status: 500,
|
||||||
|
body: {
|
||||||
|
code: 500,
|
||||||
|
msg: '获取上传服务器地址失败',
|
||||||
|
detail: error.message,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lbs || !lbs.upload || !lbs.upload[0]) {
|
||||||
|
return Promise.reject({
|
||||||
|
status: 500,
|
||||||
|
body: {
|
||||||
|
code: 500,
|
||||||
|
msg: '获取上传服务器地址无效',
|
||||||
|
detail: lbs,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
needUpload: checkRes.body.needUpload,
|
||||||
|
songId: checkRes.body.songId,
|
||||||
|
uploadToken: tokenRes.body.result.token,
|
||||||
|
objectKey: tokenRes.body.result.objectKey,
|
||||||
|
resourceId: tokenRes.body.result.resourceId,
|
||||||
|
uploadUrl: `${lbs.upload[0]}/${bucket}/${tokenRes.body.result.objectKey.replace(/\//g, '%2F')}?offset=0&complete=true&version=1.0`,
|
||||||
|
bucket: bucket,
|
||||||
|
md5: md5,
|
||||||
|
fileSize: fileSize,
|
||||||
|
filename: filename,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cookie: checkRes.cookie,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// 评论统计数据
|
||||||
|
// type: 0=歌曲 1=MV 2=歌单 3=专辑 4=电台节目 5=视频 6=动态 7=电台
|
||||||
|
// ids: 资源 ID 列表,多个用逗号分隔,如 "123,456"
|
||||||
|
const { resourceTypeMap } = require('../util/config.json')
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
|
||||||
|
// 从 resourceTypeMap 的前缀值中提取网易云内部资源类型编号
|
||||||
|
// 例如 "R_SO_4_" -> "4", "A_DR_14_" -> "14"
|
||||||
|
const resourceTypeIdMap = Object.fromEntries(
|
||||||
|
Object.entries(resourceTypeMap).map(([key, prefix]) => [
|
||||||
|
key,
|
||||||
|
prefix.replace(/_$/, '').split('_').pop(),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const ids = String(query.ids || query.id || '')
|
||||||
|
.split(',')
|
||||||
|
.map((id) => id.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
return request(
|
||||||
|
`/api/resource/commentInfo/list`,
|
||||||
|
{
|
||||||
|
resourceType: resourceTypeIdMap[String(query.type || 0)],
|
||||||
|
resourceIds: JSON.stringify(ids),
|
||||||
|
},
|
||||||
|
createOption(query, 'weapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// 举报评论
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
threadId: 'R_SO_4_' + query.id,
|
||||||
|
commentId: query.cid,
|
||||||
|
reason: query.reason,
|
||||||
|
}
|
||||||
|
return request(`/api/report/reportcomment`, data, createOption(query))
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
const {
|
||||||
|
eapiResDecrypt,
|
||||||
|
eapiReqDecrypt,
|
||||||
|
aesDecrypt,
|
||||||
|
xeapiResDecrypt,
|
||||||
|
} = require('../util/crypto')
|
||||||
|
const CryptoJS = require('crypto-js')
|
||||||
|
|
||||||
|
const linuxapiKey = 'rFgB&h#%2?^eDg:Q'
|
||||||
|
|
||||||
|
module.exports = async (query, request) => {
|
||||||
|
const crypto = query.crypto || 'eapi'
|
||||||
|
const data = query.data || query.hexString || ''
|
||||||
|
const isReq = query.isReq !== 'false'
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
body: { code: 400, message: 'data is required' },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let result
|
||||||
|
switch (crypto) {
|
||||||
|
case 'eapi': {
|
||||||
|
const pureHex = data.replace(/\s/g, '')
|
||||||
|
result = isReq ? eapiReqDecrypt(pureHex) : eapiResDecrypt(pureHex)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'weapi': {
|
||||||
|
if (isReq) {
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
code: 400,
|
||||||
|
message:
|
||||||
|
'weapi 请求解密需要 RSA 私钥,暂不支持;仅支持 weapi 返回数据解密(e_r=true 时与 eapi 相同)',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const pureHex = data.replace(/\s/g, '')
|
||||||
|
result = eapiResDecrypt(pureHex)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'linuxapi': {
|
||||||
|
if (isReq) {
|
||||||
|
const pureHex = data.replace(/\s/g, '')
|
||||||
|
const decrypted = aesDecrypt(pureHex, linuxapiKey, '', 'hex')
|
||||||
|
result = JSON.parse(decrypted.toString(CryptoJS.enc.Utf8))
|
||||||
|
} else {
|
||||||
|
result = typeof data === 'string' ? JSON.parse(data) : data
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'xeapi': {
|
||||||
|
if (isReq) {
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
code: 400,
|
||||||
|
message:
|
||||||
|
'xeapi 请求解密涉及 X25519 ECDH 密钥交换,流程复杂,暂不支持;仅支持 xeapi 返回数据解密',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const buf = Buffer.from(data, 'base64')
|
||||||
|
result = xeapiResDecrypt(buf)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'api': {
|
||||||
|
result = typeof data === 'string' ? JSON.parse(data) : data
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
body: { code: 400, message: `未知加密方式: ${crypto}` },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: { code: 200, data: result },
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
body: { code: 400, message: `解密失败: ${error.message}` },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// DIFM电台 - 分类
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
sources: query.sources || '[0]',
|
||||||
|
}
|
||||||
|
return request(`/api/dj/difm/all/style/channel/v2`, data, createOption(query))
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// DIFM电台 - 收藏频道
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
id: query.id,
|
||||||
|
}
|
||||||
|
return request(`/api/dj/difm/channel/subscribe`, data, createOption(query))
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// DIFM电台 - 取消收藏频道
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
id: query.id,
|
||||||
|
}
|
||||||
|
return request(`/api/dj/difm/channel/unsubscribe`, data, createOption(query))
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// DIFM电台 - 播放列表
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
limit: query.limit || 5,
|
||||||
|
source: query.source || 0,
|
||||||
|
channelId: query.channelId,
|
||||||
|
}
|
||||||
|
return request(`/api/dj/difm/playing/tracks/list`, data, createOption(query))
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// DIFM电台 - 收藏列表
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
sources: query.sources || '[0]',
|
||||||
|
}
|
||||||
|
return request(
|
||||||
|
`/api/dj/difm/subscribe/channels/get/v2`,
|
||||||
|
data,
|
||||||
|
createOption(query),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// 多级行政区划数据获取接口
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
bizCode: query.bizCode || '',
|
||||||
|
}
|
||||||
|
return request(`/api/lbs/city/code`, data, createOption(query))
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// 听歌足迹 - 歌曲播放排行 (Top20)
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
return request(
|
||||||
|
`/api/content/activity/listen/data/song/play/rank`,
|
||||||
|
{
|
||||||
|
type: query.type || 'month', //周 week 月 month
|
||||||
|
endTime: query.endTime, // 不填就是本周/月的
|
||||||
|
},
|
||||||
|
createOption(query),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -13,12 +13,12 @@ module.exports = async (query, request) => {
|
|||||||
[query.captcha ? 'captcha' : 'password']: query.captcha
|
[query.captcha ? 'captcha' : 'password']: query.captcha
|
||||||
? query.captcha
|
? query.captcha
|
||||||
: query.md5_password || CryptoJS.MD5(query.password).toString(),
|
: query.md5_password || CryptoJS.MD5(query.password).toString(),
|
||||||
rememberLogin: 'true',
|
remember: 'true',
|
||||||
}
|
}
|
||||||
let result = await request(
|
let result = await request(
|
||||||
`/api/w/login/cellphone`,
|
`/api/w/login/cellphone`,
|
||||||
data,
|
data,
|
||||||
createOption(query),
|
createOption(query, 'weapi'),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (result.body.code === 200) {
|
if (result.body.code === 200) {
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
const QRCode = require('qrcode')
|
const QRCode = require('qrcode')
|
||||||
|
const { generateChainId } = require('../util/index')
|
||||||
|
|
||||||
const createOption = require('../util/option.js')
|
module.exports = (query) => {
|
||||||
module.exports = (query, request) => {
|
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
const url = `https://music.163.com/login?codekey=${query.key}`
|
const platform = query.platform || 'pc'
|
||||||
|
const cookie = query.cookie || ''
|
||||||
|
|
||||||
|
// 构建基础URL
|
||||||
|
let url = `https://music.163.com/login?codekey=${query.key}`
|
||||||
|
|
||||||
|
// 如果是web平台,则添加chainId参数
|
||||||
|
|
||||||
|
if (platform === 'web') {
|
||||||
|
const chainId = generateChainId(cookie)
|
||||||
|
url += `&chainId=${chainId}`
|
||||||
|
}
|
||||||
return resolve({
|
return resolve({
|
||||||
code: 200,
|
code: 200,
|
||||||
status: 200,
|
status: 200,
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// 获取音乐人任务
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {}
|
||||||
|
return request(
|
||||||
|
`/api/nmusician/workbench/special/right/vip/info`,
|
||||||
|
data,
|
||||||
|
createOption(query, 'eapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
// 私信和通知接口
|
// 私信和通知接口
|
||||||
|
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
module.exports = (query, request) => {
|
module.exports = (query, request) => {
|
||||||
const data = {}
|
const data = {}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// 歌单分类列表
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
cat: query.cat || '全部',
|
||||||
|
limit: query.limit || 24,
|
||||||
|
newStyle: true,
|
||||||
|
}
|
||||||
|
return request(`/api/playlist/category/list`, data, createOption(query))
|
||||||
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
module.exports = (query, request) => {
|
module.exports = (query, request) => {
|
||||||
return request(`/api/playlist/catalogue`, {}, createOption(query, 'weapi'))
|
return request(`/api/playlist/catalogue`, {}, createOption(query, 'eapi'))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
// 收藏与取消收藏歌单
|
// 收藏与取消收藏歌单
|
||||||
|
const { APP_CONF } = require('../util/config.json')
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
module.exports = (query, request) => {
|
module.exports = (query, request) => {
|
||||||
query.t = query.t == 1 ? 'subscribe' : 'unsubscribe'
|
const path = query.t == 1 ? 'subscribe' : 'unsubscribe'
|
||||||
const data = {
|
const data = {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
|
...(query.t === 1
|
||||||
|
? { checkToken: query.checkToken || APP_CONF.checkToken }
|
||||||
|
: {}),
|
||||||
}
|
}
|
||||||
return request(`/api/playlist/${query.t}`, data, createOption(query, 'weapi'))
|
query.checkToken = true // 强制开启checkToken
|
||||||
|
return request(`/api/playlist/${path}`, data, createOption(query, 'eapi'))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// 跑步漫游
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
bpm: query.bpm || 50,
|
||||||
|
}
|
||||||
|
return request(`/api/radio/sport/get`, data, createOption(query))
|
||||||
|
}
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
module.exports = (query, request) => {
|
module.exports = (query, request) => {
|
||||||
const data = {}
|
const data = {
|
||||||
|
afresh: query.afresh,
|
||||||
|
}
|
||||||
return request(
|
return request(
|
||||||
`/api/v3/discovery/recommend/songs`,
|
`/api/v3/discovery/recommend/songs`,
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
|
// 获取游客cookie
|
||||||
|
|
||||||
const CryptoJS = require('crypto-js')
|
const CryptoJS = require('crypto-js')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const ID_XOR_KEY_1 = '3go8&$8*3*3h0k(2)2'
|
const ID_XOR_KEY_1 = '3go8&$8*3*3h0k(2)2'
|
||||||
const deviceidText = fs.readFileSync(
|
const logger = require('../util/logger.js')
|
||||||
path.resolve(__dirname, '../data/deviceid.txt'),
|
|
||||||
'utf-8',
|
|
||||||
)
|
|
||||||
|
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
const deviceidList = deviceidText.split('\n')
|
const { generateDeviceId } = require('../util/index')
|
||||||
|
|
||||||
function getRandomFromList(list) {
|
// function getRandomFromList(list) {
|
||||||
return list[Math.floor(Math.random() * list.length)]
|
// return list[Math.floor(Math.random() * list.length)]
|
||||||
}
|
// }
|
||||||
function cloudmusic_dll_encode_id(some_id) {
|
function cloudmusic_dll_encode_id(some_id) {
|
||||||
let xoredString = ''
|
let xoredString = ''
|
||||||
for (let i = 0; i < some_id.length; i++) {
|
for (let i = 0; i < some_id.length; i++) {
|
||||||
@@ -26,7 +25,8 @@ function cloudmusic_dll_encode_id(some_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = async (query, request) => {
|
module.exports = async (query, request) => {
|
||||||
const deviceId = getRandomFromList(deviceidList)
|
const deviceId = generateDeviceId()
|
||||||
|
logger.info(`Successfully registered anonimous token, deviceId: ${deviceId}`)
|
||||||
global.deviceId = deviceId
|
global.deviceId = deviceId
|
||||||
const encodedId = CryptoJS.enc.Base64.stringify(
|
const encodedId = CryptoJS.enc.Base64.stringify(
|
||||||
CryptoJS.enc.Utf8.parse(
|
CryptoJS.enc.Utf8.parse(
|
||||||
@@ -39,7 +39,7 @@ module.exports = async (query, request) => {
|
|||||||
let result = await request(
|
let result = await request(
|
||||||
`/api/register/anonimous`,
|
`/api/register/anonimous`,
|
||||||
data,
|
data,
|
||||||
createOption(query, 'weapi'),
|
createOption(query, 'xeapi'),
|
||||||
)
|
)
|
||||||
if (result.body.code === 200) {
|
if (result.body.code === 200) {
|
||||||
result = {
|
result = {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ module.exports = (query, request) => {
|
|||||||
password: CryptoJS.MD5(query.password).toString(),
|
password: CryptoJS.MD5(query.password).toString(),
|
||||||
nickname: query.nickname,
|
nickname: query.nickname,
|
||||||
countrycode: query.countrycode || '86',
|
countrycode: query.countrycode || '86',
|
||||||
|
force: 'false',
|
||||||
}
|
}
|
||||||
return request(`/api/register/cellphone`, data, createOption(query))
|
return request(`/api/w/register/cellphone`, data, createOption(query))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// 易盾反作弊 Token 注册端点
|
||||||
|
// 调用后获取实时 token 并存入共享存储,供后续带 checkToken 的请求使用
|
||||||
|
//
|
||||||
|
// GET /register/checktoken → 返回当前 token(尚无则自动获取)
|
||||||
|
// POST /register/checktoken → 强制刷新 token
|
||||||
|
// GET /register/checktoken?refresh=1 → 强制刷新
|
||||||
|
//
|
||||||
|
const { default: axios } = require('axios')
|
||||||
|
const { APP_CONF } = require('../util/config.json')
|
||||||
|
|
||||||
|
const URL = APP_CONF.dunDomain + '/v3/b?pn=YD00000558929251'
|
||||||
|
let _token = ''
|
||||||
|
|
||||||
|
async function fetch() {
|
||||||
|
const res = await axios.get(URL, { timeout: 10000 })
|
||||||
|
const body = String(res.data)
|
||||||
|
const m = body.match(/null\(\[(\d+),\d+,\"([^\"]+)\"\]\)/)
|
||||||
|
if (m && m[1] === '200') return m[2]
|
||||||
|
throw new Error('易盾返回异常: ' + body.substring(0, 100))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 端点处理
|
||||||
|
module.exports = async (query) => {
|
||||||
|
const refresh = query.refresh === '1' || query.refresh === 'true'
|
||||||
|
let token = refresh ? null : _token
|
||||||
|
if (!token) {
|
||||||
|
token = await fetch()
|
||||||
|
_token = token
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: { code: 200, token, registered: !!token },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 给 request.js 读取用
|
||||||
|
module.exports.getToken = () => _token
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
const { default: axios } = require('axios')
|
||||||
|
const encrypt = require('../util/crypto')
|
||||||
|
const { APP_CONF } = require('../util/config.json')
|
||||||
|
|
||||||
|
const generateNonce = () => {
|
||||||
|
let nonce = ''
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
nonce += Math.floor(Math.random() * 10).toString()
|
||||||
|
}
|
||||||
|
return nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = async (query, request) => {
|
||||||
|
const nonce = generateNonce()
|
||||||
|
const timestamp = String(Date.now())
|
||||||
|
const deviceId = query.deviceId || global.deviceId || ''
|
||||||
|
const currentKeyVersion = query.currentKeyVersion || ''
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
appVersion: '9.1.65',
|
||||||
|
currentKeyVersion,
|
||||||
|
deviceId,
|
||||||
|
nonce,
|
||||||
|
os: 'android',
|
||||||
|
requestType: 'active',
|
||||||
|
signature: encrypt.xeapiSign(timestamp, nonce),
|
||||||
|
t1: '',
|
||||||
|
t2: '',
|
||||||
|
timestamp,
|
||||||
|
uid: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await axios({
|
||||||
|
method: 'POST',
|
||||||
|
url: APP_CONF.apiDomain + '/api/gorilla/anti/crawler/security/key/get',
|
||||||
|
headers: {
|
||||||
|
'User-Agent':
|
||||||
|
'NeteaseMusic/9.1.65.240927161425(9001065);Dalvik/2.1.0 (Linux; U; Android 14; 23013RK75C Build/UKQ1.230804.001)',
|
||||||
|
Cookie: deviceId ? `deviceId=${encodeURIComponent(deviceId)}` : '',
|
||||||
|
},
|
||||||
|
data: new URLSearchParams(data).toString(),
|
||||||
|
proxy: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (
|
||||||
|
!res.data ||
|
||||||
|
res.data.code !== 200 ||
|
||||||
|
!res.data.data ||
|
||||||
|
!res.data.data.encryptedData
|
||||||
|
) {
|
||||||
|
throw new Error('xeapi public key request failed')
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!res.data.data.signature ||
|
||||||
|
encrypt.xeapiSign(res.data.data.timestamp, nonce) !==
|
||||||
|
res.data.data.signature
|
||||||
|
) {
|
||||||
|
throw new Error('xeapi public key response signature mismatch')
|
||||||
|
}
|
||||||
|
|
||||||
|
const publicKey = encrypt.xeapiDecryptPublicKey(res.data.data.encryptedData)
|
||||||
|
if (!publicKey.sk) {
|
||||||
|
throw new Error('xeapi public key response missing sk')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
...publicKey,
|
||||||
|
deviceId,
|
||||||
|
},
|
||||||
|
cookie: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// 提交歌曲播放状态
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
const generateSessionId = () =>
|
||||||
|
Array.from(
|
||||||
|
{ length: 12 },
|
||||||
|
() =>
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'[Math.floor(Math.random() * 36)],
|
||||||
|
).join('')
|
||||||
|
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
sessionId,
|
||||||
|
progress = 0,
|
||||||
|
playMode = 'list_loop',
|
||||||
|
type = 'song',
|
||||||
|
} = query
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return Promise.reject({
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
code: 400,
|
||||||
|
msg: '缺少必要参数:id',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const playStateSubmitReq = JSON.stringify({
|
||||||
|
resource: {
|
||||||
|
id: String(id),
|
||||||
|
type: type,
|
||||||
|
},
|
||||||
|
progress: Number(progress) || 0,
|
||||||
|
sessionId: sessionId || generateSessionId(),
|
||||||
|
playMode: playMode,
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
playStateSubmitReq: playStateSubmitReq,
|
||||||
|
}
|
||||||
|
|
||||||
|
return request(
|
||||||
|
'/api/relay/play/state/submit',
|
||||||
|
data,
|
||||||
|
createOption(query, 'weapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// 助眠解压 - 获取标签下资源列表
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
tag: query.tag,
|
||||||
|
firstQuery: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
return request(`/api/voice/sati/resource/list`, data, createOption(query))
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// 助眠解压 - 查看同类推荐
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
id: query.id,
|
||||||
|
}
|
||||||
|
return request(
|
||||||
|
`/api/voice/sati/resource/list/more/v1`,
|
||||||
|
data,
|
||||||
|
createOption(query),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// 助眠解压 - 收藏
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
id: query.id,
|
||||||
|
cancel: query.cancel || false,
|
||||||
|
}
|
||||||
|
return request(`/api/voice/sati/resource/sub`, data, createOption(query))
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// 助眠解压 - 收藏列表
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {}
|
||||||
|
return request(`/api/voice/sati/resource/sub/list`, data, createOption(query))
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// 助眠解压 - 标签列表
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {}
|
||||||
|
return request(`/api/voice/sati/tag/list`, data, createOption(query))
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// 助眠解压 - 特定时间场景下的推荐资源
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
firstQuery: false,
|
||||||
|
}
|
||||||
|
return request(
|
||||||
|
`/api/voice/sati/timescene/resources/get`,
|
||||||
|
data,
|
||||||
|
createOption(query),
|
||||||
|
)
|
||||||
|
}
|
||||||
+57
-5
@@ -1,8 +1,42 @@
|
|||||||
// 听歌打卡
|
// 听歌打卡
|
||||||
|
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
module.exports = (query, request) => {
|
const { APP_CONF } = require('../util/config.json')
|
||||||
const data = {
|
const DOMAIN = APP_CONF.clDomian
|
||||||
|
module.exports = async (query, request) => {
|
||||||
|
// 注入 os=osx 的 cookie
|
||||||
|
let cookie = query.cookie || ''
|
||||||
|
if (typeof cookie === 'object') {
|
||||||
|
cookie = Object.assign({ os: 'osx' }, cookie)
|
||||||
|
} else if (typeof cookie === 'string') {
|
||||||
|
if (cookie.indexOf('os=') > -1) {
|
||||||
|
cookie = cookie.replace(/os=[^;]+/g, 'os=osx')
|
||||||
|
} else {
|
||||||
|
cookie = cookie + '; os=osx'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cookie = 'os=osx'
|
||||||
|
}
|
||||||
|
query.cookie = cookie
|
||||||
|
|
||||||
|
// 1) startplay → 进「最近播放」
|
||||||
|
const startplayData = {
|
||||||
|
logs: JSON.stringify([
|
||||||
|
{
|
||||||
|
action: 'startplay',
|
||||||
|
json: {
|
||||||
|
id: query.id,
|
||||||
|
type: 'song',
|
||||||
|
mainsite: '1',
|
||||||
|
mainsiteWeb: '1',
|
||||||
|
content: `id=${query.sourceid}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) play → 涨「听歌排行」计数
|
||||||
|
const playData = {
|
||||||
logs: JSON.stringify([
|
logs: JSON.stringify([
|
||||||
{
|
{
|
||||||
action: 'play',
|
action: 'play',
|
||||||
@@ -15,12 +49,30 @@ module.exports = (query, request) => {
|
|||||||
type: 'song',
|
type: 'song',
|
||||||
wifi: 0,
|
wifi: 0,
|
||||||
source: 'list',
|
source: 'list',
|
||||||
mainsite: 1,
|
mainsite: '1',
|
||||||
content: '',
|
mainsiteWeb: '1',
|
||||||
|
content: `id=${query.sourceid}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
}
|
}
|
||||||
|
|
||||||
return request(`/api/feedback/weblog`, data, createOption(query, 'weapi'))
|
const option = createOption(query, 'eapi')
|
||||||
|
option.domain = DOMAIN
|
||||||
|
|
||||||
|
// 发送两次请求
|
||||||
|
const res1 = await request(`/api/feedback/weblog`, startplayData, option)
|
||||||
|
const res2 = await request(`/api/feedback/weblog`, playData, option)
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
code: 200,
|
||||||
|
data: 'success',
|
||||||
|
details: {
|
||||||
|
startplay: res1.body,
|
||||||
|
play: res2.body,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
// 听歌打卡 - NCBL 加密版 (仿桌面客户端 PLV/PLD 上报)
|
||||||
|
// 复制自 https://github.com/folltoshe/netease-report-listen-song 的 PC 端日志上报方式
|
||||||
|
//
|
||||||
|
// PLV 和 PLD 分两次独立上传
|
||||||
|
|
||||||
|
const {
|
||||||
|
buildPlv,
|
||||||
|
buildPld,
|
||||||
|
buildRecords,
|
||||||
|
extractContext,
|
||||||
|
parseCookie,
|
||||||
|
buildCookieStr,
|
||||||
|
buildMetaJson,
|
||||||
|
doUpload,
|
||||||
|
} = require('../util/ncbl')
|
||||||
|
|
||||||
|
module.exports = async (query, request) => {
|
||||||
|
// --- 参数校验 ---
|
||||||
|
const songId = Number(query.id)
|
||||||
|
if (!songId || isNaN(songId)) {
|
||||||
|
return { status: 400, body: { code: 400, msg: '缺少有效的 id (歌曲ID)' } }
|
||||||
|
}
|
||||||
|
const playTime = Number(query.time)
|
||||||
|
if (isNaN(playTime) || playTime <= 0) {
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
body: { code: 400, msg: '缺少有效的 time (播放时长)' },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const totalTime = Number(query.total) || playTime
|
||||||
|
const sourceId = String(query.sourceid || query.sourceId || '')
|
||||||
|
const sourceName = query.source || 'list'
|
||||||
|
|
||||||
|
// --- 解析认证上下文 ---
|
||||||
|
const rawCookie = query.cookie || ''
|
||||||
|
const cookieObj = parseCookie(rawCookie)
|
||||||
|
cookieObj.os = 'pc'
|
||||||
|
const ctx = extractContext(cookieObj)
|
||||||
|
|
||||||
|
// 兜底取 token
|
||||||
|
if (!ctx.auth.token && rawCookie) {
|
||||||
|
const parsed =
|
||||||
|
typeof rawCookie === 'string' ? parseCookie(rawCookie) : rawCookie
|
||||||
|
ctx.auth.token = parsed.MUSIC_U || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx.auth.token) {
|
||||||
|
return { status: 401, body: { code: 401, msg: '缺少 MUSIC_U 鉴权令牌' } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 构建歌曲和来源 ---
|
||||||
|
const song = {
|
||||||
|
id: songId,
|
||||||
|
name: query.name || '',
|
||||||
|
artist: query.artist || '',
|
||||||
|
bitrate: Number(query.bitrate) || 320,
|
||||||
|
level: query.level || 'exhigh',
|
||||||
|
vip: query.vip === 'true' || query.vip === true,
|
||||||
|
time: totalTime,
|
||||||
|
}
|
||||||
|
const source = {
|
||||||
|
id: sourceId || String(songId),
|
||||||
|
type: 'track',
|
||||||
|
name: sourceName,
|
||||||
|
}
|
||||||
|
|
||||||
|
const metaJson = buildMetaJson(ctx)
|
||||||
|
const cookieStr = buildCookieStr(ctx)
|
||||||
|
|
||||||
|
const ts = Math.floor(Date.now() / 1000)
|
||||||
|
const played = Math.min(playTime, totalTime)
|
||||||
|
|
||||||
|
const plvBody = buildRecords([
|
||||||
|
{ time: ts, action: '_plv', data: buildPlv(ctx, song, source) },
|
||||||
|
])
|
||||||
|
const pldBody = buildRecords([
|
||||||
|
{ time: ts, action: '_pld', data: buildPld(ctx, song, source, played) },
|
||||||
|
])
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1) 上传 PLV
|
||||||
|
const plv = await doUpload(ctx, metaJson, plvBody, cookieStr, 'PLV')
|
||||||
|
if (!plv.success) {
|
||||||
|
const rateMsg =
|
||||||
|
plv.respBody?.data?.rate != null
|
||||||
|
? ` (rate=${plv.respBody.data.rate})`
|
||||||
|
: ''
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
code: plv.respBody?.code || -1,
|
||||||
|
msg: `PLV 上报失败${rateMsg}`,
|
||||||
|
details: plv.respBody,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 上传 PLD
|
||||||
|
const pld = await doUpload(ctx, metaJson, pldBody, cookieStr, 'PLD')
|
||||||
|
if (!pld.success) {
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
code: pld.respBody?.code || -1,
|
||||||
|
msg: 'PLV 成功但 PLD 失败',
|
||||||
|
details: { plv: plv.respBody, pld: pld.respBody },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
code: 200,
|
||||||
|
data: 'scrobble_v1 上报成功',
|
||||||
|
details: {
|
||||||
|
plv: { fileName: plv.fileName, payloadSize: plv.payload.length },
|
||||||
|
pld: { fileName: pld.fileName, payloadSize: pld.payload.length },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
status: 502,
|
||||||
|
body: { code: 502, msg: `请求异常: ${err.message || err}` },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// 搜索建议pc端
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
keyword: query.keyword || '',
|
||||||
|
}
|
||||||
|
return request(
|
||||||
|
`/api/search/pc/suggest/keyword/get`,
|
||||||
|
data,
|
||||||
|
createOption(query),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// 从云盘获取歌曲下载链接
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
songId: query.id,
|
||||||
|
}
|
||||||
|
return request(`/api/cloud/dowonload`, data, createOption(query, 'eapi'))
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// 灰色歌曲的其他版本推荐
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
songid: query.songid || query.id,
|
||||||
|
}
|
||||||
|
return request(`/api/song/copyright/rcmd`, data, createOption(query, 'eapi'))
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// 歌曲创作者信息
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
songId: query.id,
|
||||||
|
}
|
||||||
|
return request(`/api/song/creators`, data, createOption(query))
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// 喜欢歌曲
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const like = query.like !== 'false'
|
||||||
|
const data = {
|
||||||
|
trackId: query.id,
|
||||||
|
userid: query.uid,
|
||||||
|
like: like,
|
||||||
|
}
|
||||||
|
return request(`/api/song/like`, data, createOption(query))
|
||||||
|
}
|
||||||
+29
-26
@@ -5,31 +5,34 @@ const createOption = require('../util/option.js')
|
|||||||
const logger = require('../util/logger.js')
|
const logger = require('../util/logger.js')
|
||||||
|
|
||||||
module.exports = async (query, request) => {
|
module.exports = async (query, request) => {
|
||||||
try {
|
try {
|
||||||
const match = require("@unblockneteasemusic/server")
|
const {
|
||||||
const source = query.source
|
matchID,
|
||||||
? query.source.split(',') : ['pyncmd', 'kuwo', 'qq', 'migu', 'kugou']
|
} = require('@neteasecloudmusicapienhanced/unblockmusic-utils')
|
||||||
const server = query.server ? query.server.split(',') : query.server
|
const result = await matchID(query.id, query.source)
|
||||||
const result = await match(query.id, !server? source : server)
|
const proxy = process.env.PROXY_URL
|
||||||
const proxy = process.env.PROXY_URL;
|
logger.info('开始解灰', query.id, result)
|
||||||
logger.info("开始解灰", query.id, result)
|
const useProxy = process.env.ENABLE_PROXY || 'false'
|
||||||
const useProxy = process.env.ENABLE_PROXY || "false"
|
if (result.data.url && result.data.url.includes('kuwo')) {
|
||||||
if (result.url.includes('kuwo')) { result.proxyUrl = useProxy === 'true' ? proxy + result.url : result.url }
|
result.proxyUrl =
|
||||||
return {
|
useProxy === 'true' ? proxy + result.data.url : result.data.url
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
code: 200,
|
|
||||||
data: result,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return {
|
|
||||||
status: 500,
|
|
||||||
body: {
|
|
||||||
code: 500,
|
|
||||||
msg: e.message || 'unblock error',
|
|
||||||
data: [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
code: 200,
|
||||||
|
data: result.data.url,
|
||||||
|
proxyUrl: result.proxyUrl || '',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
status: 500,
|
||||||
|
body: {
|
||||||
|
code: 500,
|
||||||
|
msg: e.message || 'unblock error',
|
||||||
|
data: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,75 +1,5 @@
|
|||||||
// GD音乐台get(适配SPlayer的UNM-Server)
|
// 夹带私货的东西就不要放在这里了
|
||||||
// 感谢来自GD Studio的开发API
|
|
||||||
// https://music.gdstudio.xyz/
|
|
||||||
|
|
||||||
const createOption = require('../util/option.js')
|
|
||||||
|
|
||||||
module.exports = async (query, request) => {
|
module.exports = async (query, request) => {
|
||||||
try {
|
return { status: 200, body: { code: 200, data: [] } }
|
||||||
const { id, br = "320" } = query;
|
|
||||||
if (!id) {
|
|
||||||
return {
|
|
||||||
status: 400,
|
|
||||||
body: {
|
|
||||||
code: 400,
|
|
||||||
message: "缺少必要参数 id",
|
|
||||||
data: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const validBR = ["128", "192", "320", "740", "999"];
|
|
||||||
// const covertBR = ["128000", "192000", "320000","740000", "999000"];
|
|
||||||
if (!validBR.includes(br)) {
|
|
||||||
return {
|
|
||||||
status: 400,
|
|
||||||
body: {
|
|
||||||
code: 400,
|
|
||||||
message: "无效音质参数",
|
|
||||||
allowed_values: validBR,
|
|
||||||
data: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const apiUrl = new URL("https://music-api.gdstudio.xyz/api.php");
|
|
||||||
apiUrl.searchParams.append("types", "url");
|
|
||||||
apiUrl.searchParams.append("id", id);
|
|
||||||
apiUrl.searchParams.append("br", br);
|
|
||||||
|
|
||||||
const response = await fetch(apiUrl.toString());
|
|
||||||
if (!response.ok) throw new Error(`API 响应状态: ${response.status}`);
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
// 代理逻辑
|
|
||||||
const useProxy = process.env.ENABLE_PROXY || false;
|
|
||||||
const proxy = process.env.PROXY_URL;
|
|
||||||
if (useProxy && result.url && result.url.includes("kuwo")) {
|
|
||||||
result.proxyUrl = proxy + result.url.replace(/^http:\/\//, "http/");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
code: 200,
|
|
||||||
message: "请求成功",
|
|
||||||
data: {
|
|
||||||
id,
|
|
||||||
br,
|
|
||||||
url: result.url,
|
|
||||||
...(proxy && result.proxyUrl ? { proxyUrl: result.proxyUrl } : {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error in song_url_ncmget:", error);
|
|
||||||
return {
|
|
||||||
status: 500,
|
|
||||||
body: {
|
|
||||||
code: 500,
|
|
||||||
message: "服务器处理请求失败",
|
|
||||||
...(process.env.NODE_ENV === "development" ? { error: error.message } : {}),
|
|
||||||
data: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
// 网易云歌曲解灰
|
|
||||||
// 支持qq音乐、酷狗音乐、酷我音乐、咪咕音乐、第三方网易云API等等(来自GD音乐台)
|
|
||||||
|
|
||||||
const createOption = require('../util/option.js')
|
|
||||||
const logger = require('../util/logger.js')
|
|
||||||
|
|
||||||
module.exports = async (query, request) => {
|
|
||||||
try {
|
|
||||||
const match = require("@unblockneteasemusic/server")
|
|
||||||
const source = query.source
|
|
||||||
? query.source.split(',') : ['pyncmd', 'kuwo', 'qq', 'migu', 'kugou']
|
|
||||||
const server = query.server ? query.server.split(',') : query.server
|
|
||||||
const result = await match(query.id, !server? source : server)
|
|
||||||
const proxy = process.env.PROXY_URL;
|
|
||||||
logger.info("开始解灰", query.id, result)
|
|
||||||
const useProxy = process.env.ENABLE_PROXY || "false"
|
|
||||||
if (result.url.includes('kuwo') && useProxy === "true") { result.proxyUrl = proxy + result.url }
|
|
||||||
return {
|
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
code: 200,
|
|
||||||
data: result,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return {
|
|
||||||
status: 500,
|
|
||||||
body: {
|
|
||||||
code: 500,
|
|
||||||
msg: e.message || 'unblock error',
|
|
||||||
data: [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+37
-30
@@ -1,13 +1,14 @@
|
|||||||
// 歌曲链接 - v1
|
// 歌曲链接 - v1
|
||||||
// 此版本不再采用 br 作为音质区分的标准
|
// 此版本不再采用 br 作为音质区分的标准
|
||||||
// 而是采用 standard, exhigh, lossless, hires, jyeffect(高清环绕声), sky(沉浸环绕声), jymaster(超清母带) 进行音质判断
|
// 而是采用 standard, exhigh, lossless, hires, jyeffect(高清环绕声), sky(沉浸环绕声), jymaster(超清母带) 进行音质判断
|
||||||
// 当unblock为true时, 会尝试使用unblockneteasemusic进行解锁, 同时音质设置不会生效, 但仍然为必须传入参数
|
// 当unblock为true时, 会尝试使用unblockmusic-utils进行解锁, 同时音质设置不会生效, 但仍然为必须传入参数
|
||||||
|
|
||||||
const logger = require('../util/logger.js')
|
const logger = require('../util/logger.js')
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
module.exports = async (query, request) => {
|
module.exports = async (query, request) => {
|
||||||
const match = require('@unblockneteasemusic/server')
|
const {
|
||||||
const source = ['pyncmd','kuwo', 'qq', 'migu', 'kugou']
|
matchID,
|
||||||
|
} = require('@neteasecloudmusicapienhanced/unblockmusic-utils')
|
||||||
require('dotenv').config()
|
require('dotenv').config()
|
||||||
const data = {
|
const data = {
|
||||||
ids: '[' + query.id + ']',
|
ids: '[' + query.id + ']',
|
||||||
@@ -16,39 +17,45 @@ module.exports = async (query, request) => {
|
|||||||
}
|
}
|
||||||
if (query.unblock === 'true') {
|
if (query.unblock === 'true') {
|
||||||
try {
|
try {
|
||||||
const result = await match(query.id, source)
|
const result = await matchID(query.id, query.source)
|
||||||
logger.info('开始解灰', query.id, result)
|
logger.info('Starting unblock(uses modules unblock):', query.id, result)
|
||||||
if (result.url.includes('kuwo')) {
|
const useProxy = process.env.ENABLE_PROXY || 'false'
|
||||||
const useProxy = process.env.ENABLE_PROXY || 'false'
|
let proxyUrl = ''
|
||||||
var proxyUrl = useProxy === 'true' ? process.env.PROXY_URL + result.url : result.url
|
if (result.data.url && result.data.url.includes('kuwo')) {
|
||||||
|
proxyUrl =
|
||||||
|
useProxy === 'true' && process.env.PROXY_URL
|
||||||
|
? process.env.PROXY_URL + result.data.url
|
||||||
|
: result.data.url
|
||||||
}
|
}
|
||||||
let url = Array.isArray(result) ? (result[0]?.url || result[0]) : (result.url || result)
|
return {
|
||||||
if (url) {
|
status: 200,
|
||||||
return {
|
body: {
|
||||||
status: 200,
|
code: 200,
|
||||||
body: {
|
msg: 'Warning: Customizing unblock sources is not supported on this endpoint. Please use `/song/url/match` instead.',
|
||||||
code: 200,
|
data: [
|
||||||
data: [
|
{
|
||||||
{
|
id: Number(query.id),
|
||||||
id: Number(query.id),
|
url: result.data.url,
|
||||||
url,
|
type: 'flac',
|
||||||
type: 'flac',
|
level: query.level,
|
||||||
level: query.level,
|
freeTrialInfo: 'null',
|
||||||
freeTrialInfo: 'null',
|
fee: 0,
|
||||||
fee: 0,
|
proxyUrl: proxyUrl || '',
|
||||||
proxyUrl: proxyUrl || '',
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
},
|
cookie: [],
|
||||||
cookie: [],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error in unblockneteasemusic:', e)
|
console.error('Error in unblocking music:', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (data.level == 'sky') {
|
if (data.level == 'sky') {
|
||||||
data.immerseType = 'c51'
|
data.immerseType = 'c51'
|
||||||
}
|
}
|
||||||
return request(`/api/song/enhance/player/url/v1`, data, createOption(query))
|
return request(
|
||||||
|
`/api/song/enhance/player/url/v1`,
|
||||||
|
data,
|
||||||
|
createOption(query, 'xeapi'),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// 获取客户端歌曲下载链接 - v1
|
||||||
|
// 此版本不再采用 br 作为音质区分的标准
|
||||||
|
// 而是采用 standard, exhigh, lossless, hires, jyeffect(高清环绕声), sky(沉浸环绕声), jymaster(超清母带) 进行音质判断
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = async (query, request) => {
|
||||||
|
const data = {
|
||||||
|
id: query.id,
|
||||||
|
immerseType: 'c51',
|
||||||
|
level: query.level,
|
||||||
|
}
|
||||||
|
const response = await request(
|
||||||
|
`/api/song/enhance/download/url/v1`,
|
||||||
|
data,
|
||||||
|
createOption(query),
|
||||||
|
)
|
||||||
|
let url = response?.body?.data?.[0]?.url
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
const fallbackData = {
|
||||||
|
ids: `[${query.id}]`,
|
||||||
|
level: query.level,
|
||||||
|
encodeType: 'flac',
|
||||||
|
}
|
||||||
|
if (query.level === 'sky') {
|
||||||
|
fallbackData.immerseType = 'c51'
|
||||||
|
}
|
||||||
|
const fallback = await request(
|
||||||
|
`/api/song/enhance/player/url/v1`,
|
||||||
|
fallbackData,
|
||||||
|
createOption(query),
|
||||||
|
)
|
||||||
|
url = fallback?.body?.data?.[0]?.url
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 302,
|
||||||
|
body: '',
|
||||||
|
cookie: fallback.cookie || [],
|
||||||
|
redirectUrl: url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 302,
|
||||||
|
body: '',
|
||||||
|
cookie: response.cookie || [],
|
||||||
|
redirectUrl: url,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// 所有榜单内容摘要v2
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
return request(`/api/toplist/detail/v2`, {}, createOption(query, 'weapi'))
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// 用户详情
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = async (query, request) => {
|
||||||
|
const data = {
|
||||||
|
all: 'true',
|
||||||
|
userId: query.uid,
|
||||||
|
}
|
||||||
|
const res = await request(
|
||||||
|
`/api/w/v1/user/detail/${query.uid}`,
|
||||||
|
data,
|
||||||
|
createOption(query, 'eapi'),
|
||||||
|
)
|
||||||
|
// const result = JSON.stringify(res).replace(
|
||||||
|
// /avatarImgId_str/g,
|
||||||
|
// "avatarImgIdStr"
|
||||||
|
// );
|
||||||
|
// return JSON.parse(result);
|
||||||
|
return res
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ module.exports = (query, request) => {
|
|||||||
const data = {
|
const data = {
|
||||||
userId: query.uid,
|
userId: query.uid,
|
||||||
time: '0',
|
time: '0',
|
||||||
limit: query.limit || 30,
|
limit: query.limit || 20,
|
||||||
offset: query.offset || 0,
|
offset: query.offset || 0,
|
||||||
getcounts: 'true',
|
getcounts: 'true',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// 获取用户的收藏歌单列表
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
limit: query.limit || '100',
|
||||||
|
offset: query.offset || '0',
|
||||||
|
userId: query.uid,
|
||||||
|
isWebview: 'true',
|
||||||
|
includeRedHeart: 'true',
|
||||||
|
includeTop: 'true',
|
||||||
|
}
|
||||||
|
return request(`/api/user/playlist/collect`, data, createOption(query))
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// 获取用户的创建歌单列表
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
limit: query.limit || '100',
|
||||||
|
offset: query.offset || '0',
|
||||||
|
userId: query.uid,
|
||||||
|
isWebview: 'true',
|
||||||
|
includeRedHeart: 'true',
|
||||||
|
includeTop: 'true',
|
||||||
|
}
|
||||||
|
return request(`/api/user/playlist/create`, data, createOption(query))
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// 一键领取所有会员成长值
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {}
|
||||||
|
return request(
|
||||||
|
`/api/vipnewcenter/app/level/task/reward/getall`,
|
||||||
|
data,
|
||||||
|
createOption(query, 'xeapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// 黑胶乐签打卡
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
|
||||||
|
module.exports = async (query, request) => {
|
||||||
|
const results = {}
|
||||||
|
|
||||||
|
const taskSign = await request(
|
||||||
|
'/api/vip-center-bff/task/sign',
|
||||||
|
{},
|
||||||
|
createOption(query, 'weapi'),
|
||||||
|
)
|
||||||
|
results.taskSign = taskSign.body
|
||||||
|
|
||||||
|
const checkinDetail = await request(
|
||||||
|
'/api/vipnewcenter/app/level/user/checkin/history/detail',
|
||||||
|
{
|
||||||
|
signDayTime: Date.now(),
|
||||||
|
type: 1,
|
||||||
|
},
|
||||||
|
createOption(query, 'eapi'),
|
||||||
|
)
|
||||||
|
results.checkinDetail = checkinDetail.body
|
||||||
|
|
||||||
|
// 两个接口都返回 code=200 即视为打卡成功
|
||||||
|
const signed =
|
||||||
|
Number(results.taskSign?.code) === 200 &&
|
||||||
|
Number(results.checkinDetail?.code) === 200
|
||||||
|
|
||||||
|
return {
|
||||||
|
body: {
|
||||||
|
code: 200,
|
||||||
|
...results,
|
||||||
|
signed,
|
||||||
|
message: signed ? '黑胶乐签打卡成功' : '黑胶乐签打卡失败',
|
||||||
|
},
|
||||||
|
cookie: taskSign.cookie,
|
||||||
|
status: 200,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// 黑胶乐签打卡详情
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
signDayTime: query.timestamp,
|
||||||
|
type: '1',
|
||||||
|
}
|
||||||
|
return request(
|
||||||
|
`/api/vipnewcenter/app/level/user/checkin/history/detail`,
|
||||||
|
data,
|
||||||
|
createOption(query, 'eapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// 黑胶乐签打卡历史 / 状态查询
|
||||||
|
// 支持传入 type=0(用户信息栏)或 type=1(黑胶乐签)
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
type: query.type || '0',
|
||||||
|
}
|
||||||
|
return request(
|
||||||
|
`/api/vipnewcenter/app/minidesk/music/sign/pc`,
|
||||||
|
data,
|
||||||
|
createOption(query, 'eapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// 黑胶乐签未来签到信息
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {}
|
||||||
|
return request(
|
||||||
|
`/api/vipnewcenter/app/user/sign/info`,
|
||||||
|
data,
|
||||||
|
createOption(query, 'weapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// 会员任务 - 新版
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
taskType: 'app_vip_task_center',
|
||||||
|
userId: query.id,
|
||||||
|
}
|
||||||
|
return request(
|
||||||
|
`/api/middle/vip/mission/user/progress/list`,
|
||||||
|
data,
|
||||||
|
createOption(query, 'xeapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
+36
-22
@@ -1,25 +1,26 @@
|
|||||||
const { default: axios } = require('axios')
|
const { default: axios } = require('axios')
|
||||||
|
const fs = require('fs')
|
||||||
var xml2js = require('xml2js')
|
var xml2js = require('xml2js')
|
||||||
|
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
var parser = new xml2js.Parser(/* options */)
|
const { getFileExtension, readFileChunk } = require('../util/fileHelper')
|
||||||
|
|
||||||
|
var parser = new xml2js.Parser()
|
||||||
|
|
||||||
function createDupkey() {
|
function createDupkey() {
|
||||||
// 格式:3b443c7c-a87f-468d-ba38-46d407aaf23a
|
|
||||||
var s = []
|
var s = []
|
||||||
var hexDigits = '0123456789abcdef'
|
var hexDigits = '0123456789abcdef'
|
||||||
for (var i = 0; i < 36; i++) {
|
for (var i = 0; i < 36; i++) {
|
||||||
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
|
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
|
||||||
}
|
}
|
||||||
s[14] = '4' // bits 12-15 of the time_hi_and_version field to 0010
|
s[14] = '4'
|
||||||
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1) // bits 6-7 of the clock_seq_hi_and_reserved to 01
|
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1)
|
||||||
s[8] = s[13] = s[18] = s[23] = '-'
|
s[8] = s[13] = s[18] = s[23] = '-'
|
||||||
return s.join('')
|
return s.join('')
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = async (query, request) => {
|
module.exports = async (query, request) => {
|
||||||
let ext = 'mp3'
|
const ext = getFileExtension(query.songFile.name)
|
||||||
if (query.songFile.name.indexOf('flac') > -1) {
|
|
||||||
ext = 'flac'
|
|
||||||
}
|
|
||||||
const filename =
|
const filename =
|
||||||
query.songName ||
|
query.songName ||
|
||||||
query.songFile.name
|
query.songFile.name
|
||||||
@@ -50,43 +51,58 @@ module.exports = async (query, request) => {
|
|||||||
createOption(query, 'weapi'),
|
createOption(query, 'weapi'),
|
||||||
)
|
)
|
||||||
|
|
||||||
const objectKey = tokenRes.body.result.objectKey.replace('/', '%2F')
|
const objectKey = tokenRes.body.result.objectKey.replace(/\//g, '%2F')
|
||||||
const docId = tokenRes.body.result.docId
|
const docId = tokenRes.body.result.docId
|
||||||
const res = await axios({
|
const res = await axios({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: `https://ymusic.nos-hz.163yun.com/${objectKey}?uploads`,
|
url: `https://ymusic.nos-hz.163yun.com/${objectKey}?uploads`,
|
||||||
headers: {
|
headers: {
|
||||||
'x-nos-token': tokenRes.body.result.token,
|
'x-nos-token': tokenRes.body.result.token,
|
||||||
'X-Nos-Meta-Content-Type': 'audio/mpeg',
|
'X-Nos-Meta-Content-Type': query.songFile.mimetype || 'audio/mpeg',
|
||||||
},
|
},
|
||||||
data: null,
|
data: null,
|
||||||
})
|
})
|
||||||
// return xml
|
|
||||||
const res2 = await parser.parseStringPromise(res.data)
|
const res2 = await parser.parseStringPromise(res.data)
|
||||||
|
|
||||||
const fileSize = query.songFile.data.length
|
const useTempFile = !!query.songFile.tempFilePath
|
||||||
const blockSize = 10 * 1024 * 1024 // 10MB
|
let fileSize = query.songFile.size
|
||||||
|
|
||||||
|
if (useTempFile) {
|
||||||
|
const stats = await fs.promises.stat(query.songFile.tempFilePath)
|
||||||
|
fileSize = stats.size
|
||||||
|
}
|
||||||
|
|
||||||
|
const blockSize = 10 * 1024 * 1024
|
||||||
let offset = 0
|
let offset = 0
|
||||||
let blockIndex = 1
|
let blockIndex = 1
|
||||||
|
|
||||||
let etags = []
|
let etags = []
|
||||||
|
|
||||||
while (offset < fileSize) {
|
while (offset < fileSize) {
|
||||||
const chunk = query.songFile.data.slice(
|
let chunk
|
||||||
offset,
|
if (useTempFile) {
|
||||||
Math.min(offset + blockSize, fileSize),
|
chunk = await readFileChunk(
|
||||||
)
|
query.songFile.tempFilePath,
|
||||||
|
offset,
|
||||||
|
Math.min(blockSize, fileSize - offset),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
chunk = query.songFile.data.slice(
|
||||||
|
offset,
|
||||||
|
Math.min(offset + blockSize, fileSize),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const res3 = await axios({
|
const res3 = await axios({
|
||||||
method: 'put',
|
method: 'put',
|
||||||
url: `https://ymusic.nos-hz.163yun.com/${objectKey}?partNumber=${blockIndex}&uploadId=${res2.InitiateMultipartUploadResult.UploadId[0]}`,
|
url: `https://ymusic.nos-hz.163yun.com/${objectKey}?partNumber=${blockIndex}&uploadId=${res2.InitiateMultipartUploadResult.UploadId[0]}`,
|
||||||
headers: {
|
headers: {
|
||||||
'x-nos-token': tokenRes.body.result.token,
|
'x-nos-token': tokenRes.body.result.token,
|
||||||
'Content-Type': 'audio/mpeg',
|
'Content-Type': query.songFile.mimetype || 'audio/mpeg',
|
||||||
},
|
},
|
||||||
data: chunk,
|
data: chunk,
|
||||||
})
|
})
|
||||||
// get etag
|
|
||||||
const etag = res3.headers.etag
|
const etag = res3.headers.etag
|
||||||
etags.push(etag)
|
etags.push(etag)
|
||||||
offset += blockSize
|
offset += blockSize
|
||||||
@@ -101,19 +117,17 @@ module.exports = async (query, request) => {
|
|||||||
}
|
}
|
||||||
completeStr += '</CompleteMultipartUpload>'
|
completeStr += '</CompleteMultipartUpload>'
|
||||||
|
|
||||||
// 文件处理
|
|
||||||
await axios({
|
await axios({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: `https://ymusic.nos-hz.163yun.com/${objectKey}?uploadId=${res2.InitiateMultipartUploadResult.UploadId[0]}`,
|
url: `https://ymusic.nos-hz.163yun.com/${objectKey}?uploadId=${res2.InitiateMultipartUploadResult.UploadId[0]}`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/plain;charset=UTF-8',
|
'Content-Type': 'text/plain;charset=UTF-8',
|
||||||
'X-Nos-Meta-Content-Type': 'audio/mpeg',
|
'X-Nos-Meta-Content-Type': query.songFile.mimetype || 'audio/mpeg',
|
||||||
'x-nos-token': tokenRes.body.result.token,
|
'x-nos-token': tokenRes.body.result.token,
|
||||||
},
|
},
|
||||||
data: completeStr,
|
data: completeStr,
|
||||||
})
|
})
|
||||||
|
|
||||||
// preCheck
|
|
||||||
await request(
|
await request(
|
||||||
`/api/voice/workbench/voice/batch/upload/preCheck`,
|
`/api/voice/workbench/voice/batch/upload/preCheck`,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// 我创建的播客声音
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
limit: query.limit || 20,
|
||||||
|
}
|
||||||
|
return request(
|
||||||
|
`/api/social/my/created/voicelist/v1`,
|
||||||
|
data,
|
||||||
|
createOption(query, 'weapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
module.exports = (query, request) => {
|
module.exports = (query, request) => {
|
||||||
const data = {
|
const data = {
|
||||||
fee: '-1',
|
keyword: query.keyword || '',
|
||||||
limit: query.limit || '200',
|
scene: 'normal',
|
||||||
offset: query.offset || '0',
|
limit: query.limit || '10',
|
||||||
podcastName: query.podcastName || '',
|
offset: query.offset || '30',
|
||||||
|
e_r: true,
|
||||||
}
|
}
|
||||||
return request(
|
return request(`/api/search/voicelist/get`, data, createOption(query))
|
||||||
`/api/voice/workbench/voicelist/search`,
|
|
||||||
data,
|
|
||||||
createOption(query),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { banner, lyric } from '@neteaseapireborn/api'
|
import { banner, lyric } from '@neteasecloudmusicapienhanced/api'
|
||||||
import { logger } from '../util/logger.js'
|
import { logger } from '../util/logger.js'
|
||||||
banner({ type: 0 }).then((res) => {
|
banner({ type: 0 }).then((res) => {
|
||||||
logger.info(res)
|
logger.info(res)
|
||||||
|
|||||||
Generated
-11051
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user