@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.
Files changed (121) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/README.md +2 -0
  3. package/dist/api/index.js +9 -0
  4. package/dist/app.js +30 -45
  5. package/dist/config/service-config.js +37 -0
  6. package/dist/index.js +13 -1
  7. package/dist/jest.config.js +14 -2
  8. package/dist/koaApp.js +45 -0
  9. package/dist/middlewares/fallback-middleware.js +29 -0
  10. package/dist/module/apis/music/getLyric.js +220 -16
  11. package/dist/module/apis/music/getMusicPlay.js +187 -0
  12. package/dist/module/apis/u_common.js +8 -2
  13. package/dist/module/apis/user/getUserPlaylists.js +5 -5
  14. package/dist/module/index.js +3 -1
  15. package/dist/package.json +5 -2
  16. package/dist/routers/context/batchGetSongInfo.js +11 -16
  17. package/dist/routers/context/batchGetSongLists.js +18 -20
  18. package/dist/routers/context/getAlbumInfo.js +10 -17
  19. package/dist/routers/context/getComments.js +12 -19
  20. package/dist/routers/context/getDailyRecommend.js +5 -17
  21. package/dist/routers/context/getHotkey.js +7 -8
  22. package/dist/routers/context/getLyric.js +12 -2
  23. package/dist/routers/context/getMusicPlay.js +23 -81
  24. package/dist/routers/context/getMv.js +16 -22
  25. package/dist/routers/context/getMvPlay.js +48 -49
  26. package/dist/routers/context/getPersonalRecommend.js +13 -25
  27. package/dist/routers/context/getPlaylistTags.js +20 -26
  28. package/dist/routers/context/getRanks.js +9 -16
  29. package/dist/routers/context/getSingerAlbum.js +16 -22
  30. package/dist/routers/context/getSingerHotsong.js +16 -22
  31. package/dist/routers/context/getSingerList.js +9 -21
  32. package/dist/routers/context/getSongInfo.js +9 -16
  33. package/dist/routers/context/getSongListCategories.js +6 -7
  34. package/dist/routers/context/getSongListDetail.js +6 -8
  35. package/dist/routers/context/getUserAvatar.js +20 -33
  36. package/dist/routers/context/getUserPlaylists.js +6 -3
  37. package/dist/routers/context/index.js +94 -103
  38. package/dist/routers/util.js +31 -0
  39. package/dist/scripts/run-tests-with-flags.js +139 -0
  40. package/dist/util/cookie.js +15 -7
  41. package/dist/util/cookieResolver.js +66 -0
  42. package/dist/util/request.js +15 -6
  43. package/docs-dist/404.html +2 -2
  44. package/docs-dist/CHANGELOG-ARCHITECTURE.html +6 -6
  45. package/docs-dist/COOKIE_CONFIG_GUIDE.html +6 -6
  46. package/docs-dist/FALLBACK_MODE_GUIDE.html +101 -0
  47. package/docs-dist/README.html +6 -6
  48. package/docs-dist/TEST_USER_PLAYLISTS.html +6 -6
  49. package/docs-dist/USER_AVATAR_GUIDE.html +6 -6
  50. package/docs-dist/api/comments.html +6 -6
  51. package/docs-dist/api/index.html +6 -6
  52. package/docs-dist/api/music.html +6 -6
  53. package/docs-dist/api/other.html +6 -6
  54. package/docs-dist/api/playlist.html +6 -6
  55. package/docs-dist/api/rank.html +6 -6
  56. package/docs-dist/api/search.html +6 -6
  57. package/docs-dist/api/singer.html +6 -6
  58. package/docs-dist/api/user.html +6 -6
  59. package/docs-dist/assets/{CHANGELOG-ARCHITECTURE.md.BOe0ZtyR.js → CHANGELOG-ARCHITECTURE.md.DV9Xr7ve.js} +1 -1
  60. package/docs-dist/assets/{CHANGELOG-ARCHITECTURE.md.BOe0ZtyR.lean.js → CHANGELOG-ARCHITECTURE.md.DV9Xr7ve.lean.js} +1 -1
  61. package/docs-dist/assets/{COOKIE_CONFIG_GUIDE.md.D68AwXR2.js → COOKIE_CONFIG_GUIDE.md.B2-aTdcH.js} +1 -1
  62. package/docs-dist/assets/{COOKIE_CONFIG_GUIDE.md.D68AwXR2.lean.js → COOKIE_CONFIG_GUIDE.md.B2-aTdcH.lean.js} +1 -1
  63. package/docs-dist/assets/FALLBACK_MODE_GUIDE.md.0wqXqYxw.js +75 -0
  64. package/docs-dist/assets/FALLBACK_MODE_GUIDE.md.0wqXqYxw.lean.js +1 -0
  65. package/docs-dist/assets/{README.md.ZJQGJ1Gb.js → README.md.DFCMeLFa.js} +1 -1
  66. package/docs-dist/assets/{README.md.ZJQGJ1Gb.lean.js → README.md.DFCMeLFa.lean.js} +1 -1
  67. package/docs-dist/assets/{TEST_USER_PLAYLISTS.md.C02575X2.js → TEST_USER_PLAYLISTS.md.Bj0AVpHw.js} +1 -1
  68. package/docs-dist/assets/{TEST_USER_PLAYLISTS.md.C02575X2.lean.js → TEST_USER_PLAYLISTS.md.Bj0AVpHw.lean.js} +1 -1
  69. package/docs-dist/assets/{USER_AVATAR_GUIDE.md.BOqjn5Cm.js → USER_AVATAR_GUIDE.md.CGPI9GUj.js} +1 -1
  70. package/docs-dist/assets/{USER_AVATAR_GUIDE.md.BOqjn5Cm.lean.js → USER_AVATAR_GUIDE.md.CGPI9GUj.lean.js} +1 -1
  71. package/docs-dist/assets/{api_comments.md.DADvndEA.js → api_comments.md.CATvWhrg.js} +1 -1
  72. package/docs-dist/assets/{api_comments.md.DADvndEA.lean.js → api_comments.md.CATvWhrg.lean.js} +1 -1
  73. package/docs-dist/assets/{api_index.md.D5IASxxG.js → api_index.md.Dqx3qXyO.js} +1 -1
  74. package/docs-dist/assets/{api_index.md.D5IASxxG.lean.js → api_index.md.Dqx3qXyO.lean.js} +1 -1
  75. package/docs-dist/assets/{api_music.md.BgB8NmZq.js → api_music.md.D20_neZB.js} +1 -1
  76. package/docs-dist/assets/{api_music.md.BgB8NmZq.lean.js → api_music.md.D20_neZB.lean.js} +1 -1
  77. package/docs-dist/assets/{api_other.md.BkRWXX2z.js → api_other.md.CXyEsl8R.js} +1 -1
  78. package/docs-dist/assets/{api_other.md.BkRWXX2z.lean.js → api_other.md.CXyEsl8R.lean.js} +1 -1
  79. package/docs-dist/assets/{api_playlist.md.Dc0hTrZ4.js → api_playlist.md.CyLdLRR9.js} +1 -1
  80. package/docs-dist/assets/{api_playlist.md.Dc0hTrZ4.lean.js → api_playlist.md.CyLdLRR9.lean.js} +1 -1
  81. package/docs-dist/assets/{api_rank.md.DRisCFyT.js → api_rank.md.Z3xyYG_S.js} +1 -1
  82. package/docs-dist/assets/{api_rank.md.DRisCFyT.lean.js → api_rank.md.Z3xyYG_S.lean.js} +1 -1
  83. package/docs-dist/assets/{api_search.md.DNnMUZK0.js → api_search.md.D_lbFmYo.js} +1 -1
  84. package/docs-dist/assets/{api_search.md.DNnMUZK0.lean.js → api_search.md.D_lbFmYo.lean.js} +1 -1
  85. package/docs-dist/assets/{api_singer.md.DCmuxQkk.js → api_singer.md.BbyYE88D.js} +1 -1
  86. package/docs-dist/assets/{api_singer.md.DCmuxQkk.lean.js → api_singer.md.BbyYE88D.lean.js} +1 -1
  87. package/docs-dist/assets/{api_user.md.Cjm9GG3z.js → api_user.md.4WdmTXIB.js} +1 -1
  88. package/docs-dist/assets/{api_user.md.Cjm9GG3z.lean.js → api_user.md.4WdmTXIB.lean.js} +1 -1
  89. package/docs-dist/assets/{app.Dx_1wB58.js → app.2f7gcITE.js} +1 -1
  90. package/docs-dist/assets/chunks/@localSearchIndexroot.D461xa5C.js +1 -0
  91. package/docs-dist/assets/chunks/{VPLocalSearchBox.DwKWtsdX.js → VPLocalSearchBox.BiPSl83v.js} +1 -1
  92. package/docs-dist/assets/chunks/framework.aJbMEiY9.js +19 -0
  93. package/docs-dist/assets/chunks/{theme.pGVgJ9Cx.js → theme.BrMPT0hE.js} +2 -2
  94. package/docs-dist/assets/{guide_architecture.md.DGtNyuMH.js → guide_architecture.md.D_46khUI.js} +1 -1
  95. package/docs-dist/assets/{guide_architecture.md.DGtNyuMH.lean.js → guide_architecture.md.D_46khUI.lean.js} +1 -1
  96. package/docs-dist/assets/{guide_authentication.md.mtI5LfCw.js → guide_authentication.md.nCiAu07w.js} +1 -1
  97. package/docs-dist/assets/{guide_authentication.md.mtI5LfCw.lean.js → guide_authentication.md.nCiAu07w.lean.js} +1 -1
  98. package/docs-dist/assets/{guide_index.md.B-0SG46T.js → guide_index.md.gLozHqz5.js} +1 -1
  99. package/docs-dist/assets/{guide_index.md.B-0SG46T.lean.js → guide_index.md.gLozHqz5.lean.js} +1 -1
  100. package/docs-dist/assets/{guide_installation.md.k-KpAfxv.js → guide_installation.md.BUDl8zk1.js} +1 -1
  101. package/docs-dist/assets/guide_installation.md.BUDl8zk1.lean.js +1 -0
  102. package/docs-dist/assets/{guide_quickstart.md.Bff_KFOD.js → guide_quickstart.md.COQUzUN9.js} +1 -1
  103. package/docs-dist/assets/guide_quickstart.md.COQUzUN9.lean.js +1 -0
  104. package/docs-dist/assets/{index.md.xrs-uIyo.js → index.md.DBZfQ2kF.js} +1 -1
  105. package/docs-dist/assets/{index.md.xrs-uIyo.lean.js → index.md.DBZfQ2kF.lean.js} +1 -1
  106. package/docs-dist/assets/{reference_response-format.md.DKYTK6uJ.js → reference_response-format.md.yrdeqFUN.js} +1 -1
  107. package/docs-dist/assets/{reference_response-format.md.DKYTK6uJ.lean.js → reference_response-format.md.yrdeqFUN.lean.js} +1 -1
  108. package/docs-dist/guide/architecture.html +6 -6
  109. package/docs-dist/guide/authentication.html +6 -6
  110. package/docs-dist/guide/index.html +6 -6
  111. package/docs-dist/guide/installation.html +6 -6
  112. package/docs-dist/guide/quickstart.html +6 -6
  113. package/docs-dist/hashmap.json +1 -1
  114. package/docs-dist/index.html +5 -5
  115. package/docs-dist/reference/response-format.html +6 -6
  116. package/docs-dist/version.json +3 -3
  117. package/package.json +5 -2
  118. package/docs-dist/assets/chunks/@localSearchIndexroot.CMY5EIwU.js +0 -1
  119. package/docs-dist/assets/chunks/framework.o40iizuP.js +0 -19
  120. package/docs-dist/assets/guide_installation.md.k-KpAfxv.lean.js +0 -1
  121. package/docs-dist/assets/guide_quickstart.md.Bff_KFOD.lean.js +0 -1
package/CHANGELOG.md CHANGED
@@ -1,4 +1,26 @@
1
- ## 2.2.7 (2026-03-08)
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
  ![node](https://img.shields.io/badge/node-%3E%3D20.0.0-green?style=flat-square)
10
10
  <br />
11
11
  ![GitHub repo size](https://img.shields.io/github/repo-size/sansenjian/qq-music-api?style=flat-square) ![GitHub package.json version](https://img.shields.io/github/package-json/v/sansenjian/qq-music-api?style=flat-square) ![GitHub](https://img.shields.io/github/license/sansenjian/qq-music-api?style=flat-square) ![GitHub open issues](https://img.shields.io/github/issues/sansenjian/qq-music-api?style=flat-square) ![GitHub closed issues](https://img.shields.io/github/issues-closed/sansenjian/qq-music-api) ![GitHub last commit](https://img.shields.io/github/last-commit/sansenjian/qq-music-api?style=flat-square) ![GitHub top language](https://img.shields.io/github/languages/top/sansenjian/qq-music-api?style=flat-square)
12
+ <br />
13
+ [![Codecov](https://img.shields.io/codecov/c/github/sansenjian/qq-music-api?style=flat-square&logo=codecov)](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 user_info_1 = __importDefault(require("./config/user-info"));
9
+ const service_config_1 = __importDefault(require("./config/service-config"));
17
10
  const package_json_1 = __importDefault(require("./package.json"));
18
- const app = new koa_1.default();
19
- global.userInfo = user_info_1.default;
20
- console.log(chalk_1.default.green('\n🥳🎉 We had supported config the user cookies. \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
- if (!(global.userInfo.loginUin || global.userInfo.uin)) {
23
- 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`));
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 (!global.userInfo.cookie) {
26
- 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`));
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
- app.listen(PORT, () => {
57
- console.log(colors_1.default.prompt(`server running @ http://localhost:${PORT}`));
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;
@@ -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 request_1 = __importDefault(require("../../../util/request"));
8
- const apiResponse_1 = require("../../../util/apiResponse");
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
- return (0, apiResponse_1.handleApi)((0, request_1.default)({
19
- url: '/lyric/fcgi-bin/fcg_query_lyric_new.fcg',
20
- method: method,
21
- options
22
- }), {
23
- transformData: (resData) => {
24
- const lyricString = resData && resData.lyric && Buffer.from(resData.lyric, 'base64').toString();
25
- const lyric = isFormat && lyricString ? (0, lyricParse_1.lyricParse)(lyricString) : lyricString;
26
- return {
27
- ...resData,
28
- lyric
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
  };