@sansenjian/qq-music-api 2.2.7 → 2.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -1
- package/README.md +2 -0
- package/dist/api/index.js +9 -0
- package/dist/app.js +30 -45
- package/dist/config/service-config.js +37 -0
- package/dist/index.js +13 -1
- package/dist/jest.config.js +14 -2
- package/dist/koaApp.js +45 -0
- package/dist/middlewares/fallback-middleware.js +29 -0
- package/dist/module/apis/music/getLyric.js +220 -16
- package/dist/module/apis/music/getMusicPlay.js +187 -0
- package/dist/module/apis/u_common.js +8 -2
- package/dist/module/apis/user/getUserPlaylists.js +5 -5
- package/dist/module/index.js +3 -1
- package/dist/package.json +5 -2
- package/dist/routers/context/batchGetSongInfo.js +11 -16
- package/dist/routers/context/batchGetSongLists.js +18 -20
- package/dist/routers/context/getAlbumInfo.js +10 -17
- package/dist/routers/context/getComments.js +12 -19
- package/dist/routers/context/getDailyRecommend.js +5 -17
- package/dist/routers/context/getHotkey.js +7 -8
- package/dist/routers/context/getLyric.js +12 -2
- package/dist/routers/context/getMusicPlay.js +23 -81
- package/dist/routers/context/getMv.js +16 -22
- package/dist/routers/context/getMvPlay.js +48 -49
- package/dist/routers/context/getPersonalRecommend.js +13 -25
- package/dist/routers/context/getPlaylistTags.js +20 -26
- package/dist/routers/context/getRanks.js +9 -16
- package/dist/routers/context/getSingerAlbum.js +16 -22
- package/dist/routers/context/getSingerHotsong.js +16 -22
- package/dist/routers/context/getSingerList.js +9 -21
- package/dist/routers/context/getSongInfo.js +9 -16
- package/dist/routers/context/getSongListCategories.js +6 -7
- package/dist/routers/context/getSongListDetail.js +6 -8
- package/dist/routers/context/getUserAvatar.js +20 -33
- package/dist/routers/context/getUserPlaylists.js +6 -3
- package/dist/routers/context/index.js +94 -103
- package/dist/routers/util.js +31 -0
- package/dist/scripts/run-tests-with-flags.js +139 -0
- package/dist/util/cookie.js +15 -7
- package/dist/util/cookieResolver.js +66 -0
- package/dist/util/request.js +15 -6
- package/docs-dist/404.html +2 -2
- package/docs-dist/CHANGELOG-ARCHITECTURE.html +6 -6
- package/docs-dist/COOKIE_CONFIG_GUIDE.html +6 -6
- package/docs-dist/FALLBACK_MODE_GUIDE.html +101 -0
- package/docs-dist/README.html +6 -6
- package/docs-dist/TEST_USER_PLAYLISTS.html +6 -6
- package/docs-dist/USER_AVATAR_GUIDE.html +6 -6
- package/docs-dist/api/comments.html +6 -6
- package/docs-dist/api/index.html +6 -6
- package/docs-dist/api/music.html +6 -6
- package/docs-dist/api/other.html +6 -6
- package/docs-dist/api/playlist.html +6 -6
- package/docs-dist/api/rank.html +6 -6
- package/docs-dist/api/search.html +6 -6
- package/docs-dist/api/singer.html +6 -6
- package/docs-dist/api/user.html +6 -6
- package/docs-dist/assets/{CHANGELOG-ARCHITECTURE.md.BOe0ZtyR.js → CHANGELOG-ARCHITECTURE.md.DV9Xr7ve.js} +1 -1
- package/docs-dist/assets/{CHANGELOG-ARCHITECTURE.md.BOe0ZtyR.lean.js → CHANGELOG-ARCHITECTURE.md.DV9Xr7ve.lean.js} +1 -1
- package/docs-dist/assets/{COOKIE_CONFIG_GUIDE.md.D68AwXR2.js → COOKIE_CONFIG_GUIDE.md.B2-aTdcH.js} +1 -1
- package/docs-dist/assets/{COOKIE_CONFIG_GUIDE.md.D68AwXR2.lean.js → COOKIE_CONFIG_GUIDE.md.B2-aTdcH.lean.js} +1 -1
- package/docs-dist/assets/FALLBACK_MODE_GUIDE.md.0wqXqYxw.js +75 -0
- package/docs-dist/assets/FALLBACK_MODE_GUIDE.md.0wqXqYxw.lean.js +1 -0
- package/docs-dist/assets/{README.md.ZJQGJ1Gb.js → README.md.DFCMeLFa.js} +1 -1
- package/docs-dist/assets/{README.md.ZJQGJ1Gb.lean.js → README.md.DFCMeLFa.lean.js} +1 -1
- package/docs-dist/assets/{TEST_USER_PLAYLISTS.md.C02575X2.js → TEST_USER_PLAYLISTS.md.Bj0AVpHw.js} +1 -1
- package/docs-dist/assets/{TEST_USER_PLAYLISTS.md.C02575X2.lean.js → TEST_USER_PLAYLISTS.md.Bj0AVpHw.lean.js} +1 -1
- package/docs-dist/assets/{USER_AVATAR_GUIDE.md.BOqjn5Cm.js → USER_AVATAR_GUIDE.md.CGPI9GUj.js} +1 -1
- package/docs-dist/assets/{USER_AVATAR_GUIDE.md.BOqjn5Cm.lean.js → USER_AVATAR_GUIDE.md.CGPI9GUj.lean.js} +1 -1
- package/docs-dist/assets/{api_comments.md.DADvndEA.js → api_comments.md.CATvWhrg.js} +1 -1
- package/docs-dist/assets/{api_comments.md.DADvndEA.lean.js → api_comments.md.CATvWhrg.lean.js} +1 -1
- package/docs-dist/assets/{api_index.md.D5IASxxG.js → api_index.md.Dqx3qXyO.js} +1 -1
- package/docs-dist/assets/{api_index.md.D5IASxxG.lean.js → api_index.md.Dqx3qXyO.lean.js} +1 -1
- package/docs-dist/assets/{api_music.md.BgB8NmZq.js → api_music.md.D20_neZB.js} +1 -1
- package/docs-dist/assets/{api_music.md.BgB8NmZq.lean.js → api_music.md.D20_neZB.lean.js} +1 -1
- package/docs-dist/assets/{api_other.md.BkRWXX2z.js → api_other.md.CXyEsl8R.js} +1 -1
- package/docs-dist/assets/{api_other.md.BkRWXX2z.lean.js → api_other.md.CXyEsl8R.lean.js} +1 -1
- package/docs-dist/assets/{api_playlist.md.Dc0hTrZ4.js → api_playlist.md.CyLdLRR9.js} +1 -1
- package/docs-dist/assets/{api_playlist.md.Dc0hTrZ4.lean.js → api_playlist.md.CyLdLRR9.lean.js} +1 -1
- package/docs-dist/assets/{api_rank.md.DRisCFyT.js → api_rank.md.Z3xyYG_S.js} +1 -1
- package/docs-dist/assets/{api_rank.md.DRisCFyT.lean.js → api_rank.md.Z3xyYG_S.lean.js} +1 -1
- package/docs-dist/assets/{api_search.md.DNnMUZK0.js → api_search.md.D_lbFmYo.js} +1 -1
- package/docs-dist/assets/{api_search.md.DNnMUZK0.lean.js → api_search.md.D_lbFmYo.lean.js} +1 -1
- package/docs-dist/assets/{api_singer.md.DCmuxQkk.js → api_singer.md.BbyYE88D.js} +1 -1
- package/docs-dist/assets/{api_singer.md.DCmuxQkk.lean.js → api_singer.md.BbyYE88D.lean.js} +1 -1
- package/docs-dist/assets/{api_user.md.Cjm9GG3z.js → api_user.md.4WdmTXIB.js} +1 -1
- package/docs-dist/assets/{api_user.md.Cjm9GG3z.lean.js → api_user.md.4WdmTXIB.lean.js} +1 -1
- package/docs-dist/assets/{app.Dx_1wB58.js → app.2f7gcITE.js} +1 -1
- package/docs-dist/assets/chunks/@localSearchIndexroot.D461xa5C.js +1 -0
- package/docs-dist/assets/chunks/{VPLocalSearchBox.DwKWtsdX.js → VPLocalSearchBox.BiPSl83v.js} +1 -1
- package/docs-dist/assets/chunks/framework.aJbMEiY9.js +19 -0
- package/docs-dist/assets/chunks/{theme.pGVgJ9Cx.js → theme.BrMPT0hE.js} +2 -2
- package/docs-dist/assets/{guide_architecture.md.DGtNyuMH.js → guide_architecture.md.D_46khUI.js} +1 -1
- package/docs-dist/assets/{guide_architecture.md.DGtNyuMH.lean.js → guide_architecture.md.D_46khUI.lean.js} +1 -1
- package/docs-dist/assets/{guide_authentication.md.mtI5LfCw.js → guide_authentication.md.nCiAu07w.js} +1 -1
- package/docs-dist/assets/{guide_authentication.md.mtI5LfCw.lean.js → guide_authentication.md.nCiAu07w.lean.js} +1 -1
- package/docs-dist/assets/{guide_index.md.B-0SG46T.js → guide_index.md.gLozHqz5.js} +1 -1
- package/docs-dist/assets/{guide_index.md.B-0SG46T.lean.js → guide_index.md.gLozHqz5.lean.js} +1 -1
- package/docs-dist/assets/{guide_installation.md.k-KpAfxv.js → guide_installation.md.BUDl8zk1.js} +1 -1
- package/docs-dist/assets/guide_installation.md.BUDl8zk1.lean.js +1 -0
- package/docs-dist/assets/{guide_quickstart.md.Bff_KFOD.js → guide_quickstart.md.COQUzUN9.js} +1 -1
- package/docs-dist/assets/guide_quickstart.md.COQUzUN9.lean.js +1 -0
- package/docs-dist/assets/{index.md.xrs-uIyo.js → index.md.DBZfQ2kF.js} +1 -1
- package/docs-dist/assets/{index.md.xrs-uIyo.lean.js → index.md.DBZfQ2kF.lean.js} +1 -1
- package/docs-dist/assets/{reference_response-format.md.DKYTK6uJ.js → reference_response-format.md.yrdeqFUN.js} +1 -1
- package/docs-dist/assets/{reference_response-format.md.DKYTK6uJ.lean.js → reference_response-format.md.yrdeqFUN.lean.js} +1 -1
- package/docs-dist/guide/architecture.html +6 -6
- package/docs-dist/guide/authentication.html +6 -6
- package/docs-dist/guide/index.html +6 -6
- package/docs-dist/guide/installation.html +6 -6
- package/docs-dist/guide/quickstart.html +6 -6
- package/docs-dist/hashmap.json +1 -1
- package/docs-dist/index.html +5 -5
- package/docs-dist/reference/response-format.html +6 -6
- package/docs-dist/version.json +3 -3
- package/package.json +5 -2
- package/docs-dist/assets/chunks/@localSearchIndexroot.CMY5EIwU.js +0 -1
- package/docs-dist/assets/chunks/framework.o40iizuP.js +0 -19
- package/docs-dist/assets/guide_installation.md.k-KpAfxv.lean.js +0 -1
- package/docs-dist/assets/guide_quickstart.md.Bff_KFOD.lean.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
|
-
## 2.2.
|
|
1
|
+
## 2.2.10 (2026-03-14)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
## 2.2.10 (2026-03-14)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* stabilize lyric flow and refresh CI/Vercel config ([#13](https://github.com/sansenjian/qq-music-api/issues/13)) ([34ed7b1](https://github.com/sansenjian/qq-music-api/commit/34ed7b164fa74e12d753867b5a7b49d3b5bf0200))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## 2.2.9 (2026-03-14)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* rewrite getMusicPlay url logic ([#12](https://github.com/sansenjian/qq-music-api/issues/12)) ([ec9823b](https://github.com/sansenjian/qq-music-api/commit/ec9823be7821f6c83427f1aada8848729936e1d2))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## 2.2.8 (2026-03-08)
|
|
2
24
|
|
|
3
25
|
|
|
4
26
|
|
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|

|
|
10
10
|
<br />
|
|
11
11
|
      
|
|
12
|
+
<br />
|
|
13
|
+
[](https://codecov.io/gh/sansenjian/qq-music-api)
|
|
12
14
|
|
|
13
15
|
</div>
|
|
14
16
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const koaApp_1 = __importDefault(require("../koaApp"));
|
|
7
|
+
const handler = koaApp_1.default.callback();
|
|
8
|
+
exports.default = handler;
|
|
9
|
+
module.exports = handler;
|
package/dist/app.js
CHANGED
|
@@ -4,55 +4,40 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
require("reflect-metadata");
|
|
7
|
-
const koa_1 = __importDefault(require("koa"));
|
|
8
|
-
const koa_bodyparser_1 = __importDefault(require("koa-bodyparser"));
|
|
9
|
-
const path_1 = __importDefault(require("path"));
|
|
10
|
-
const koa_static_1 = __importDefault(require("koa-static"));
|
|
11
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
-
const koa_cors_1 = __importDefault(require("./middlewares/koa-cors"));
|
|
13
|
-
const router_1 = __importDefault(require("./routers/router"));
|
|
14
|
-
const cookie_1 = __importDefault(require("./util/cookie"));
|
|
15
8
|
const colors_1 = __importDefault(require("./util/colors"));
|
|
16
|
-
const
|
|
9
|
+
const service_config_1 = __importDefault(require("./config/service-config"));
|
|
17
10
|
const package_json_1 = __importDefault(require("./package.json"));
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
console.log(chalk_1.default.green('\n
|
|
11
|
+
const koaApp_1 = __importDefault(require("./koaApp"));
|
|
12
|
+
// 输出服务配置信息
|
|
13
|
+
console.log(chalk_1.default.green('\n🎵 QQ Music API Service Starting...\n'));
|
|
21
14
|
console.log(colors_1.default.info(`Current Version: ${package_json_1.default.version}`));
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
console.log(colors_1.default.info(`Fallback Mode: ${service_config_1.default.fallbackMode ? 'Enabled' : 'Disabled'}`));
|
|
16
|
+
console.log(colors_1.default.info(`Use Global Cookie: ${service_config_1.default.useGlobalCookie ? 'Yes' : 'No'}`));
|
|
17
|
+
if (service_config_1.default.fallbackMode) {
|
|
18
|
+
console.log(chalk_1.default.green('\n✅ 降级模式已启用:支持手动传递 Cookie\n'));
|
|
19
|
+
console.log('使用方式:');
|
|
20
|
+
console.log(' 1. Query 参数:GET /api/endpoint?cookie=your_cookie');
|
|
21
|
+
console.log(' 2. Header: X-Custom-Cookie: your_cookie');
|
|
22
|
+
console.log(' 3. Header: Cookie: your_cookie\n');
|
|
24
23
|
}
|
|
25
|
-
if (!
|
|
26
|
-
console.log(chalk_1.default.yellow(
|
|
24
|
+
if (!service_config_1.default.useGlobalCookie) {
|
|
25
|
+
console.log(chalk_1.default.yellow('\n⚠️ 全局 Cookie 未启用:需要登录的接口请手动传递 Cookie\n'));
|
|
26
|
+
}
|
|
27
|
+
if (service_config_1.default.useGlobalCookie) {
|
|
28
|
+
console.log(chalk_1.default.green('\n✅ 全局 Cookie 已启用\n'));
|
|
29
|
+
if (!(global.userInfo.loginUin || global.userInfo.uin)) {
|
|
30
|
+
console.log(chalk_1.default.yellow(`😔 The configuration ${chalk_1.default.red('loginUin')} or your ${chalk_1.default.red('cookie')} in file ${chalk_1.default.green('config/user-info')} has not configured. \n`));
|
|
31
|
+
}
|
|
32
|
+
if (!global.userInfo.cookie) {
|
|
33
|
+
console.log(chalk_1.default.yellow(`😔 The configuration ${chalk_1.default.red('cookie')} in file ${chalk_1.default.green('config/user-info')} has not configured. \n`));
|
|
34
|
+
}
|
|
27
35
|
}
|
|
28
|
-
app.use((0, koa_bodyparser_1.default)());
|
|
29
|
-
app.use((0, cookie_1.default)());
|
|
30
|
-
app.use((0, koa_static_1.default)(path_1.default.join(__dirname, 'public')));
|
|
31
|
-
// logger
|
|
32
|
-
app.use(async (ctx, next) => {
|
|
33
|
-
await next();
|
|
34
|
-
const rt = ctx.response.get('X-Response-Time');
|
|
35
|
-
console.log(colors_1.default.prompt(`${ctx.method} ${ctx.url} - ${rt}`));
|
|
36
|
-
});
|
|
37
|
-
// cors
|
|
38
|
-
app.use((0, koa_cors_1.default)({
|
|
39
|
-
origin: ctx => ctx.request.header?.origin || '*',
|
|
40
|
-
exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
|
|
41
|
-
maxAge: 5,
|
|
42
|
-
credentials: true,
|
|
43
|
-
allowMethods: ['GET', 'POST', 'DELETE'],
|
|
44
|
-
allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
|
|
45
|
-
}));
|
|
46
|
-
// x-response-time
|
|
47
|
-
app.use(async (ctx, next) => {
|
|
48
|
-
const start = Date.now();
|
|
49
|
-
await next();
|
|
50
|
-
const ms = Date.now() - start;
|
|
51
|
-
ctx.set('X-Response-Time', `${ms}ms`);
|
|
52
|
-
});
|
|
53
|
-
app.use(router_1.default.routes())
|
|
54
|
-
.use(router_1.default.allowedMethods());
|
|
55
36
|
const PORT = typeof process.env.PORT === 'string' ? parseInt(process.env.PORT, 10) : (process.env.PORT || 3200);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
37
|
+
const isMainModule = require.main === module;
|
|
38
|
+
if (isMainModule) {
|
|
39
|
+
koaApp_1.default.listen(PORT, () => {
|
|
40
|
+
console.log(colors_1.default.prompt(`server running @ http://localhost:${PORT}`));
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
exports.default = koaApp_1.default;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const configPath = path_1.default.join(__dirname, './service-config.json');
|
|
9
|
+
// 创建默认配置
|
|
10
|
+
const defaultConfig = {
|
|
11
|
+
fallbackMode: true,
|
|
12
|
+
useGlobalCookie: false,
|
|
13
|
+
cookieParamName: 'cookie'
|
|
14
|
+
};
|
|
15
|
+
// 读取或创建配置文件
|
|
16
|
+
let config = defaultConfig;
|
|
17
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
18
|
+
try {
|
|
19
|
+
const content = fs_1.default.readFileSync(configPath, 'utf-8');
|
|
20
|
+
config = { ...defaultConfig, ...JSON.parse(content) };
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
console.warn('service-config.json 读取失败,使用默认配置');
|
|
24
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2), 'utf-8');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2), 'utf-8');
|
|
29
|
+
}
|
|
30
|
+
// 支持环境变量覆盖
|
|
31
|
+
if (process.env.FALLBACK_MODE === 'true') {
|
|
32
|
+
config.fallbackMode = true;
|
|
33
|
+
}
|
|
34
|
+
if (process.env.USE_GLOBAL_COOKIE === 'true') {
|
|
35
|
+
config.useGlobalCookie = true;
|
|
36
|
+
}
|
|
37
|
+
exports.default = config;
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
require("./app");
|
|
6
|
+
const app_1 = __importDefault(require("./app"));
|
|
7
|
+
const colors_1 = __importDefault(require("./util/colors"));
|
|
8
|
+
const parsedPort = Number.parseInt(process.env.PORT ?? '', 10);
|
|
9
|
+
const PORT = Number.isFinite(parsedPort) ? parsedPort : 3200;
|
|
10
|
+
if (require.main === module) {
|
|
11
|
+
app_1.default.listen(PORT, () => {
|
|
12
|
+
console.log(colors_1.default.prompt(`server running @ http://localhost:${PORT}`));
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
exports.default = app_1.default;
|
package/dist/jest.config.js
CHANGED
|
@@ -8,12 +8,22 @@ const config = {
|
|
|
8
8
|
'module/**/*.ts',
|
|
9
9
|
'routers/**/*.ts',
|
|
10
10
|
'util/**/*.ts',
|
|
11
|
+
'middlewares/**/*.ts',
|
|
11
12
|
'!**/node_modules/**',
|
|
12
13
|
'!**/tests/**',
|
|
13
|
-
'!**/dist/**'
|
|
14
|
+
'!**/dist/**',
|
|
15
|
+
'!**/*.d.ts',
|
|
16
|
+
'!**/types/**/*.ts'
|
|
14
17
|
],
|
|
15
18
|
coverageDirectory: 'coverage',
|
|
16
|
-
coverageReporters: ['text', 'lcov', 'html'],
|
|
19
|
+
coverageReporters: ['text', 'lcov', 'html', 'json-summary', 'json'],
|
|
20
|
+
coveragePathIgnorePatterns: [
|
|
21
|
+
'/node_modules/',
|
|
22
|
+
'/tests/',
|
|
23
|
+
'/dist/',
|
|
24
|
+
'\\.d\\.ts$',
|
|
25
|
+
'/types/'
|
|
26
|
+
],
|
|
17
27
|
coverageThreshold: {
|
|
18
28
|
global: {
|
|
19
29
|
branches: 35,
|
|
@@ -26,6 +36,8 @@ const config = {
|
|
|
26
36
|
modulePathIgnorePatterns: ['<rootDir>/dist/'],
|
|
27
37
|
testTimeout: 10000,
|
|
28
38
|
verbose: true,
|
|
39
|
+
collectCoverage: true, // 默认开启覆盖率
|
|
40
|
+
coverageProvider: 'v8', // 使用 v8 引擎,更准确
|
|
29
41
|
moduleNameMapper: {
|
|
30
42
|
'^@module/(.*)$': '<rootDir>/module/$1',
|
|
31
43
|
'^@routers/(.*)$': '<rootDir>/routers/$1',
|
package/dist/koaApp.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const koa_1 = __importDefault(require("koa"));
|
|
7
|
+
const koa_bodyparser_1 = __importDefault(require("koa-bodyparser"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const koa_static_1 = __importDefault(require("koa-static"));
|
|
10
|
+
const koa_cors_1 = __importDefault(require("./middlewares/koa-cors"));
|
|
11
|
+
const router_1 = __importDefault(require("./routers/router"));
|
|
12
|
+
const cookie_1 = __importDefault(require("./util/cookie"));
|
|
13
|
+
const fallback_middleware_1 = __importDefault(require("./middlewares/fallback-middleware"));
|
|
14
|
+
const colors_1 = __importDefault(require("./util/colors"));
|
|
15
|
+
const user_info_1 = __importDefault(require("./config/user-info"));
|
|
16
|
+
const app = new koa_1.default();
|
|
17
|
+
global.userInfo = user_info_1.default;
|
|
18
|
+
app.use((0, koa_bodyparser_1.default)());
|
|
19
|
+
app.use((0, fallback_middleware_1.default)());
|
|
20
|
+
app.use((0, cookie_1.default)());
|
|
21
|
+
app.use((0, koa_static_1.default)(path_1.default.join(__dirname, 'public')));
|
|
22
|
+
// logger
|
|
23
|
+
app.use(async (ctx, next) => {
|
|
24
|
+
await next();
|
|
25
|
+
const rt = ctx.response.get('X-Response-Time');
|
|
26
|
+
console.log(colors_1.default.prompt(`${ctx.method} ${ctx.url} - ${rt}`));
|
|
27
|
+
});
|
|
28
|
+
// cors
|
|
29
|
+
app.use((0, koa_cors_1.default)({
|
|
30
|
+
origin: ctx => ctx.request.header?.origin || '*',
|
|
31
|
+
exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
|
|
32
|
+
maxAge: 5,
|
|
33
|
+
credentials: true,
|
|
34
|
+
allowMethods: ['GET', 'POST', 'DELETE'],
|
|
35
|
+
allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
|
|
36
|
+
}));
|
|
37
|
+
// x-response-time
|
|
38
|
+
app.use(async (ctx, next) => {
|
|
39
|
+
const start = Date.now();
|
|
40
|
+
await next();
|
|
41
|
+
const ms = Date.now() - start;
|
|
42
|
+
ctx.set('X-Response-Time', `${ms}ms`);
|
|
43
|
+
});
|
|
44
|
+
app.use(router_1.default.routes()).use(router_1.default.allowedMethods());
|
|
45
|
+
exports.default = app;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const service_config_1 = __importDefault(require("../config/service-config"));
|
|
7
|
+
const cookieResolver_1 = require("../util/cookieResolver");
|
|
8
|
+
/**
|
|
9
|
+
* 降级模式中间件:支持通过 query/header 手动传递 Cookie。
|
|
10
|
+
*/
|
|
11
|
+
const fallbackMiddleware = () => async (ctx, next) => {
|
|
12
|
+
if (!service_config_1.default.fallbackMode) {
|
|
13
|
+
await next();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const { cookie, source } = (0, cookieResolver_1.resolveRequestCookie)(ctx, {
|
|
17
|
+
fallbackMode: true,
|
|
18
|
+
useGlobalCookie: false,
|
|
19
|
+
cookieParamName: service_config_1.default.cookieParamName
|
|
20
|
+
});
|
|
21
|
+
if (cookie) {
|
|
22
|
+
(0, cookieResolver_1.setRequestCookieContext)(ctx, cookie);
|
|
23
|
+
if (process.env.DEBUG === 'true') {
|
|
24
|
+
console.log(`[FallbackMode] use cookie from ${source}, length: ${cookie.length}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
await next();
|
|
28
|
+
};
|
|
29
|
+
exports.default = fallbackMiddleware;
|
|
@@ -4,29 +4,233 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const lyricParse_1 = require("../../../util/lyricParse");
|
|
7
|
-
const
|
|
8
|
-
const
|
|
7
|
+
const y_common_1 = __importDefault(require("../y_common"));
|
|
8
|
+
const cookieResolver_1 = require("../../../util/cookieResolver");
|
|
9
|
+
const UCommon_1 = __importDefault(require("../UCommon/UCommon"));
|
|
10
|
+
const MUSICU_TIMEOUT_MS = 10000;
|
|
11
|
+
const getCookieFromOptions = (option) => {
|
|
12
|
+
if (!option || typeof option !== 'object') {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
const headers = option.headers;
|
|
16
|
+
if (!headers || typeof headers !== 'object') {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const cookie = headers.Cookie || headers.cookie;
|
|
20
|
+
return typeof cookie === 'string' ? cookie : undefined;
|
|
21
|
+
};
|
|
22
|
+
const resolveUin = (cookie) => {
|
|
23
|
+
return (0, cookieResolver_1.extractUinFromCookie)(cookie) || String(global.userInfo?.loginUin || '0');
|
|
24
|
+
};
|
|
25
|
+
const decodeLyricField = (value) => {
|
|
26
|
+
if (typeof value !== 'string' || !value) {
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const decoded = Buffer.from(value, 'base64').toString();
|
|
31
|
+
return decoded || value;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const normalizeLyricResponse = (resData, isFormat) => {
|
|
38
|
+
const lyricString = decodeLyricField(resData?.lyric);
|
|
39
|
+
const lyric = isFormat && lyricString ? (0, lyricParse_1.lyricParse)(lyricString) : lyricString;
|
|
40
|
+
return {
|
|
41
|
+
...resData,
|
|
42
|
+
lyric
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
const hasNegativeBizCode = (data) => {
|
|
46
|
+
const codes = [data?.retcode, data?.code, data?.subcode]
|
|
47
|
+
.map(item => Number(item))
|
|
48
|
+
.filter(item => !Number.isNaN(item));
|
|
49
|
+
return codes.some(item => item < 0);
|
|
50
|
+
};
|
|
51
|
+
const normalizeSongId = (value) => {
|
|
52
|
+
const id = Number(value);
|
|
53
|
+
if (Number.isFinite(id) && id > 0) {
|
|
54
|
+
return String(id);
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
};
|
|
58
|
+
const mergePrimaryAndFallbackPayload = (primaryPayload, fallbackPayload) => {
|
|
59
|
+
const merged = {
|
|
60
|
+
...primaryPayload,
|
|
61
|
+
...fallbackPayload
|
|
62
|
+
};
|
|
63
|
+
// When fallback succeeds but does not include status fields, force success codes
|
|
64
|
+
// to avoid preserving negative primary codes in response.
|
|
65
|
+
if (!Object.prototype.hasOwnProperty.call(fallbackPayload, 'retcode')) {
|
|
66
|
+
merged.retcode = 0;
|
|
67
|
+
}
|
|
68
|
+
if (!Object.prototype.hasOwnProperty.call(fallbackPayload, 'code')) {
|
|
69
|
+
merged.code = 0;
|
|
70
|
+
}
|
|
71
|
+
if (!Object.prototype.hasOwnProperty.call(fallbackPayload, 'subcode')) {
|
|
72
|
+
merged.subcode = 0;
|
|
73
|
+
}
|
|
74
|
+
return merged;
|
|
75
|
+
};
|
|
76
|
+
const resolveSongIdBySongmid = async ({ songmid, loginUin }) => {
|
|
77
|
+
const response = await (0, UCommon_1.default)({
|
|
78
|
+
method: 'get',
|
|
79
|
+
params: {
|
|
80
|
+
format: 'json',
|
|
81
|
+
data: {
|
|
82
|
+
comm: {
|
|
83
|
+
uin: loginUin,
|
|
84
|
+
ct: 24,
|
|
85
|
+
cv: 0
|
|
86
|
+
},
|
|
87
|
+
songinfo: {
|
|
88
|
+
method: 'get_song_detail_yqq',
|
|
89
|
+
param: {
|
|
90
|
+
song_type: 0,
|
|
91
|
+
song_mid: songmid,
|
|
92
|
+
song_id: 0
|
|
93
|
+
},
|
|
94
|
+
module: 'music.pf_song_detail_svr'
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
option: {}
|
|
99
|
+
});
|
|
100
|
+
const data = (response?.data || {});
|
|
101
|
+
return normalizeSongId(data?.songinfo?.data?.track_info?.id ||
|
|
102
|
+
data?.songinfo?.data?.trackInfo?.id ||
|
|
103
|
+
data?.track_info?.id);
|
|
104
|
+
};
|
|
105
|
+
const fetchLyricByMusicu = async ({ songmid, songid, loginUin, cookie }) => {
|
|
106
|
+
const reqPayload = {
|
|
107
|
+
req_0: {
|
|
108
|
+
module: 'music.musichallSong.PlayLyricInfo',
|
|
109
|
+
method: 'GetPlayLyricInfo',
|
|
110
|
+
param: {
|
|
111
|
+
songMID: songmid || '',
|
|
112
|
+
songID: Number(songid || 0),
|
|
113
|
+
trans_t: 0,
|
|
114
|
+
roma_t: 0,
|
|
115
|
+
qrc_t: 0,
|
|
116
|
+
crypt: 1,
|
|
117
|
+
lrc_t: 0,
|
|
118
|
+
interval: 0
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
loginUin,
|
|
122
|
+
comm: {
|
|
123
|
+
uin: loginUin,
|
|
124
|
+
format: 'json',
|
|
125
|
+
ct: 24,
|
|
126
|
+
cv: 0
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const controller = new AbortController();
|
|
130
|
+
const timer = setTimeout(() => controller.abort(), MUSICU_TIMEOUT_MS);
|
|
131
|
+
const headers = {
|
|
132
|
+
'Content-Type': 'application/json',
|
|
133
|
+
Referer: 'https://y.qq.com/portal/player.html',
|
|
134
|
+
Origin: 'https://y.qq.com'
|
|
135
|
+
};
|
|
136
|
+
if (cookie) {
|
|
137
|
+
headers.Cookie = cookie;
|
|
138
|
+
}
|
|
139
|
+
const response = await (async () => {
|
|
140
|
+
try {
|
|
141
|
+
return await fetch('https://u.y.qq.com/cgi-bin/musicu.fcg', {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers,
|
|
144
|
+
body: JSON.stringify(reqPayload),
|
|
145
|
+
signal: controller.signal
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
}
|
|
151
|
+
})();
|
|
152
|
+
const raw = await response.text();
|
|
153
|
+
const upstream = JSON.parse(raw || '{}');
|
|
154
|
+
return (upstream?.req_0?.data || upstream?.PlayLyricInfo?.data || {});
|
|
155
|
+
};
|
|
9
156
|
exports.default = async ({ method = 'get', params = {}, option = {}, isFormat = false }) => {
|
|
157
|
+
const cookie = getCookieFromOptions(option);
|
|
158
|
+
const loginUin = resolveUin(cookie);
|
|
159
|
+
const songmid = typeof params.songmid === 'string'
|
|
160
|
+
? params.songmid
|
|
161
|
+
: undefined;
|
|
162
|
+
const songid = typeof params.songid === 'string'
|
|
163
|
+
? params.songid
|
|
164
|
+
: undefined;
|
|
10
165
|
const data = Object.assign(params, {
|
|
11
166
|
format: 'json',
|
|
12
167
|
outCharset: 'utf-8',
|
|
13
|
-
pcachetime: Date.now()
|
|
168
|
+
pcachetime: Date.now(),
|
|
169
|
+
loginUin
|
|
14
170
|
});
|
|
15
171
|
const options = Object.assign(option, {
|
|
172
|
+
headers: Object.assign({}, option?.headers || {}, {
|
|
173
|
+
referer: 'https://y.qq.com/portal/player.html',
|
|
174
|
+
host: 'c.y.qq.com'
|
|
175
|
+
}),
|
|
16
176
|
params: data
|
|
17
177
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
178
|
+
try {
|
|
179
|
+
const primaryResponse = await (0, y_common_1.default)({
|
|
180
|
+
url: '/lyric/fcgi-bin/fcg_query_lyric_new.fcg',
|
|
181
|
+
method: method,
|
|
182
|
+
options
|
|
183
|
+
});
|
|
184
|
+
let payload = (primaryResponse?.data || {});
|
|
185
|
+
if (hasNegativeBizCode(payload)) {
|
|
186
|
+
try {
|
|
187
|
+
let fallbackPayload = await fetchLyricByMusicu({
|
|
188
|
+
songmid,
|
|
189
|
+
songid,
|
|
190
|
+
loginUin,
|
|
191
|
+
cookie
|
|
192
|
+
});
|
|
193
|
+
if (hasNegativeBizCode(fallbackPayload) && !normalizeSongId(songid) && songmid) {
|
|
194
|
+
const resolvedSongId = await resolveSongIdBySongmid({ songmid, loginUin });
|
|
195
|
+
if (resolvedSongId) {
|
|
196
|
+
fallbackPayload = await fetchLyricByMusicu({
|
|
197
|
+
songmid,
|
|
198
|
+
songid: resolvedSongId,
|
|
199
|
+
loginUin,
|
|
200
|
+
cookie
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (Object.keys(fallbackPayload).length > 0 && !hasNegativeBizCode(fallbackPayload)) {
|
|
205
|
+
payload = mergePrimaryAndFallbackPayload(payload, fallbackPayload);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
// keep primary payload when fallback request fails
|
|
210
|
+
}
|
|
30
211
|
}
|
|
31
|
-
|
|
212
|
+
return {
|
|
213
|
+
status: 200,
|
|
214
|
+
body: {
|
|
215
|
+
response: normalizeLyricResponse(payload, Boolean(isFormat))
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
const normalizedError = error instanceof Error
|
|
221
|
+
? {
|
|
222
|
+
name: error.name,
|
|
223
|
+
message: error.message
|
|
224
|
+
}
|
|
225
|
+
: {
|
|
226
|
+
name: 'Error',
|
|
227
|
+
message: 'Internal server error'
|
|
228
|
+
};
|
|
229
|
+
return {
|
|
230
|
+
status: 500,
|
|
231
|
+
body: {
|
|
232
|
+
error: normalizedError
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
32
236
|
};
|