qqmusic-api-enhanced 1.0.0 → 1.0.2

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.
@@ -1,5 +1,5 @@
1
1
  import { request, type ApiResponse } from "../util/request.js";
2
- import { cookieToJson } from "../util/cookie.js";
2
+ import { cookieToJson, cookieToString, parseSetCookie } from "../util/cookie.js";
3
3
 
4
4
  /**
5
5
  * 刷新登录凭证.
@@ -16,15 +16,18 @@ export default async function login_refresh(
16
16
  refresh_key?: string;
17
17
  } = {},
18
18
  req = request
19
- ): Promise<ApiResponse> {
19
+ ): Promise<ApiResponse<{ code: number; data: any; cookieStr: string }>> {
20
20
  const cookieObj = cookieToJson(query.cookie);
21
21
  const uin = cookieObj.uin || cookieObj.qqmusic_uin || cookieObj.wxuin || "0";
22
22
  const musickey = cookieObj.qm_keyst || cookieObj.qqmusic_key || "";
23
23
 
24
+ const loginType = cookieObj.tmeLoginType || (musickey.startsWith("W_X") ? "1" : "2");
25
+
24
26
  const param = {
25
27
  openid: query.openid || cookieObj.openid || "",
26
28
  refresh_token: query.refresh_token || cookieObj.refresh_token || "",
27
29
  refresh_key: query.refresh_key || cookieObj.refresh_key || "",
30
+ access_token: cookieObj.access_token || "",
28
31
  musicid: Number(uin) || 0,
29
32
  str_musicid: uin,
30
33
  musickey,
@@ -37,15 +40,40 @@ export default async function login_refresh(
37
40
  param,
38
41
  {
39
42
  cookie: query.cookie,
40
- platform: "web",
43
+ platform: "android",
44
+ comm: { tmeLoginType: Number(loginType) || 2 },
41
45
  }
42
46
  );
43
47
 
48
+ const resBody = (cgiRes.body || {}) as any;
49
+ const newCookies = parseSetCookie(cgiRes.cookie || []);
50
+ const mergedCookies = { ...cookieObj, ...newCookies };
51
+
52
+ if (resBody.musickey) {
53
+ mergedCookies.qm_keyst = resBody.musickey;
54
+ mergedCookies.qqmusic_key = resBody.musickey;
55
+ mergedCookies.musickey = resBody.musickey;
56
+ }
57
+ if (resBody.musicid) {
58
+ mergedCookies.uin = String(resBody.musicid);
59
+ mergedCookies.qqmusic_uin = String(resBody.musicid);
60
+ mergedCookies.musicid = String(resBody.musicid);
61
+ }
62
+ if (resBody.encryptUin) {
63
+ mergedCookies.euin = resBody.encryptUin;
64
+ mergedCookies.encryptUin = resBody.encryptUin;
65
+ }
66
+ if (resBody.refresh_token) {
67
+ mergedCookies.refresh_token = resBody.refresh_token;
68
+ }
69
+ const cookieStr = cookieToString(mergedCookies);
70
+
44
71
  return {
45
72
  status: cgiRes.status,
46
73
  body: {
47
- code: cgiRes.body?.code ?? 200,
48
- data: cgiRes.body,
74
+ code: resBody.code ?? (cgiRes.status === 200 ? 200 : 500),
75
+ data: resBody,
76
+ cookieStr,
49
77
  },
50
78
  cookie: cgiRes.cookie,
51
79
  };
@@ -1,9 +1,30 @@
1
1
  import { request, type ApiResponse } from "../util/request.js";
2
+ import { type NormalizedVipInfo } from "./user_vip_info.js";
3
+ export interface NormalizedLoginAccount {
4
+ userId: string;
5
+ nickname: string;
6
+ avatarUrl?: string;
7
+ vipStatus: "vip" | "nonVip" | "unknown";
8
+ vipTier?: "svip" | "vip";
9
+ vipLevel?: number;
10
+ vipYear?: boolean;
11
+ vipExpireTime?: number;
12
+ }
13
+ export interface LoginStatusResult {
14
+ code: number;
15
+ isLogin: boolean;
16
+ uin: string;
17
+ nickname: string;
18
+ avatarUrl?: string;
19
+ vip: NormalizedVipInfo;
20
+ account: NormalizedLoginAccount;
21
+ data: any;
22
+ }
2
23
  /**
3
- * 校验当前 Cookie 登录状态及获取用户信息.
24
+ * 校验当前 Cookie 登录状态及获取用户信息与 VIP 等级.
4
25
  *
5
26
  * @param query.cookie 登录凭据 Cookie
6
27
  */
7
28
  export default function login_status(query?: {
8
29
  cookie?: any;
9
- }, req?: typeof request): Promise<ApiResponse>;
30
+ }, req?: typeof request): Promise<ApiResponse<LoginStatusResult>>;
@@ -1,21 +1,85 @@
1
1
  import { request } from "../util/request.js";
2
+ import { cookieToJson } from "../util/cookie.js";
3
+ import user_detail_fn from "./user_detail.js";
4
+ import user_vip_info_fn from "./user_vip_info.js";
2
5
  /**
3
- * 校验当前 Cookie 登录状态及获取用户信息.
6
+ * 校验当前 Cookie 登录状态及获取用户信息与 VIP 等级.
4
7
  *
5
8
  * @param query.cookie 登录凭据 Cookie
6
9
  */
7
10
  export default async function login_status(query = {}, req = request) {
8
- const cgiRes = await req("music.UserInfo.userInfoServer", "GetLoginUserInfo", {}, {
9
- cookie: query.cookie,
10
- platform: "web",
11
- });
12
- const data = cgiRes.body || {};
13
- const isLogin = data.code === 0 && Boolean(data.user?.uin || data.uin || data.encryptUin);
11
+ const cookieObj = cookieToJson(query.cookie);
12
+ let uin = cookieObj.uin || cookieObj.qqmusic_uin || cookieObj.musicid || cookieObj.wxuin || "";
13
+ const musickey = cookieObj.qm_keyst || cookieObj.qqmusic_key || cookieObj.musickey || "";
14
+ let cgiRes = { status: 200, body: {} };
15
+ try {
16
+ cgiRes = await req("music.UserInfo.userInfoServer", "GetLoginUserInfo", {}, {
17
+ cookie: query.cookie,
18
+ platform: "android",
19
+ });
20
+ }
21
+ catch { }
22
+ const data = (cgiRes.body || {});
23
+ const userData = data.user || data.userInfo || data;
24
+ let isLogin = (data.code === 0 || (data.code === undefined && Boolean(userData.uin || userData.encryptUin))) &&
25
+ Boolean(userData.uin || userData.encryptUin || uin);
26
+ if (userData.uin) {
27
+ uin = String(userData.uin).trim();
28
+ }
29
+ else if (!uin && userData.encryptUin) {
30
+ uin = String(userData.encryptUin).trim();
31
+ }
32
+ let nickname = userData.nick || userData.nickname || "";
33
+ let avatarUrl = userData.headurl || userData.avatar;
34
+ // 若基本信息不全,只要有 uin,调用 user_detail 补全真实昵称与高清头像
35
+ if (uin && (!nickname || !avatarUrl)) {
36
+ try {
37
+ const detailRes = await user_detail_fn({ uin, cookie: query.cookie }, req);
38
+ const detailData = detailRes.body?.data || {};
39
+ if (detailData.nick && !nickname)
40
+ nickname = detailData.nick;
41
+ if (detailData.headurl && !avatarUrl)
42
+ avatarUrl = detailData.headurl;
43
+ if (detailData.nick && Boolean(musickey || uin)) {
44
+ isLogin = true;
45
+ }
46
+ }
47
+ catch { }
48
+ }
49
+ if (!nickname) {
50
+ nickname = uin ? `QQ用户_${uin.slice(-4)}` : "QQ音乐用户";
51
+ }
52
+ // 补全 VIP 会员信息
53
+ let vip = { vipStatus: "nonVip" };
54
+ if (isLogin || uin) {
55
+ try {
56
+ const vipRes = await user_vip_info_fn({ cookie: query.cookie, uin }, req);
57
+ vip = vipRes.body.vip;
58
+ }
59
+ catch {
60
+ vip = { vipStatus: "unknown" };
61
+ }
62
+ }
63
+ const account = {
64
+ userId: uin,
65
+ nickname,
66
+ avatarUrl,
67
+ vipStatus: vip.vipStatus,
68
+ vipTier: vip.vipTier,
69
+ vipLevel: vip.vipLevel,
70
+ vipYear: vip.vipYear,
71
+ vipExpireTime: vip.vipExpireTime,
72
+ };
14
73
  return {
15
- status: cgiRes.status,
74
+ status: cgiRes.status || 200,
16
75
  body: {
17
76
  code: isLogin ? 200 : 301,
18
77
  isLogin,
78
+ uin,
79
+ nickname,
80
+ avatarUrl,
81
+ vip,
82
+ account,
19
83
  data,
20
84
  },
21
85
  cookie: cgiRes.cookie,
@@ -1,32 +1,120 @@
1
1
  import { request, type ApiResponse } from "../util/request.js";
2
+ import { cookieToJson } from "../util/cookie.js";
3
+ import user_detail_fn from "./user_detail.js";
4
+ import user_vip_info_fn, { type NormalizedVipInfo } from "./user_vip_info.js";
5
+
6
+ export interface NormalizedLoginAccount {
7
+ userId: string;
8
+ nickname: string;
9
+ avatarUrl?: string;
10
+ vipStatus: "vip" | "nonVip" | "unknown";
11
+ vipTier?: "svip" | "vip";
12
+ vipLevel?: number;
13
+ vipYear?: boolean;
14
+ vipExpireTime?: number;
15
+ }
16
+
17
+ export interface LoginStatusResult {
18
+ code: number;
19
+ isLogin: boolean;
20
+ uin: string;
21
+ nickname: string;
22
+ avatarUrl?: string;
23
+ vip: NormalizedVipInfo;
24
+ account: NormalizedLoginAccount;
25
+ data: any;
26
+ }
2
27
 
3
28
  /**
4
- * 校验当前 Cookie 登录状态及获取用户信息.
29
+ * 校验当前 Cookie 登录状态及获取用户信息与 VIP 等级.
5
30
  *
6
31
  * @param query.cookie 登录凭据 Cookie
7
32
  */
8
33
  export default async function login_status(
9
34
  query: { cookie?: any } = {},
10
35
  req = request
11
- ): Promise<ApiResponse> {
12
- const cgiRes = await req(
13
- "music.UserInfo.userInfoServer",
14
- "GetLoginUserInfo",
15
- {},
16
- {
17
- cookie: query.cookie,
18
- platform: "web",
36
+ ): Promise<ApiResponse<LoginStatusResult>> {
37
+ const cookieObj = cookieToJson(query.cookie);
38
+ let uin = cookieObj.uin || cookieObj.qqmusic_uin || cookieObj.musicid || cookieObj.wxuin || "";
39
+ const musickey = cookieObj.qm_keyst || cookieObj.qqmusic_key || cookieObj.musickey || "";
40
+
41
+ let cgiRes: any = { status: 200, body: {} };
42
+ try {
43
+ cgiRes = await req(
44
+ "music.UserInfo.userInfoServer",
45
+ "GetLoginUserInfo",
46
+ {},
47
+ {
48
+ cookie: query.cookie,
49
+ platform: "android",
50
+ }
51
+ );
52
+ } catch {}
53
+
54
+ const data = (cgiRes.body || {}) as any;
55
+ const userData = data.user || data.userInfo || data;
56
+ let isLogin =
57
+ (data.code === 0 || (data.code === undefined && Boolean(userData.uin || userData.encryptUin))) &&
58
+ Boolean(userData.uin || userData.encryptUin || uin);
59
+
60
+ if (userData.uin) {
61
+ uin = String(userData.uin).trim();
62
+ } else if (!uin && userData.encryptUin) {
63
+ uin = String(userData.encryptUin).trim();
64
+ }
65
+
66
+ let nickname = userData.nick || userData.nickname || "";
67
+ let avatarUrl: string | undefined = userData.headurl || userData.avatar;
68
+
69
+ // 若基本信息不全,只要有 uin,调用 user_detail 补全真实昵称与高清头像
70
+ if (uin && (!nickname || !avatarUrl)) {
71
+ try {
72
+ const detailRes = await user_detail_fn({ uin, cookie: query.cookie }, req);
73
+ const detailData = detailRes.body?.data || {};
74
+ if (detailData.nick && !nickname) nickname = detailData.nick;
75
+ if (detailData.headurl && !avatarUrl) avatarUrl = detailData.headurl;
76
+ if (detailData.nick && Boolean(musickey || uin)) {
77
+ isLogin = true;
78
+ }
79
+ } catch {}
80
+ }
81
+
82
+ if (!nickname) {
83
+ nickname = uin ? `QQ用户_${uin.slice(-4)}` : "QQ音乐用户";
84
+ }
85
+
86
+ // 补全 VIP 会员信息
87
+ let vip: NormalizedVipInfo = { vipStatus: "nonVip" };
88
+ if (isLogin || uin) {
89
+ try {
90
+ const vipRes = await user_vip_info_fn({ cookie: query.cookie, uin }, req);
91
+ vip = vipRes.body.vip;
92
+ } catch {
93
+ vip = { vipStatus: "unknown" };
19
94
  }
20
- );
95
+ }
21
96
 
22
- const data = cgiRes.body || {};
23
- const isLogin = data.code === 0 && Boolean(data.user?.uin || data.uin || data.encryptUin);
97
+ const account: NormalizedLoginAccount = {
98
+ userId: uin,
99
+ nickname,
100
+ avatarUrl,
101
+ vipStatus: vip.vipStatus,
102
+ vipTier: vip.vipTier,
103
+ vipLevel: vip.vipLevel,
104
+ vipYear: vip.vipYear,
105
+ vipExpireTime: vip.vipExpireTime,
106
+ };
24
107
 
25
108
  return {
26
- status: cgiRes.status,
109
+ status: cgiRes.status || 200,
27
110
  body: {
28
111
  code: isLogin ? 200 : 301,
29
112
  isLogin,
113
+ uin,
114
+ nickname,
115
+ avatarUrl,
116
+ vip,
117
+ account,
30
118
  data,
31
119
  },
32
120
  cookie: cgiRes.cookie,
@@ -11,19 +11,42 @@ export default async function user_detail(query = {}, req = request) {
11
11
  const targetUin = query.uin?.toString() ||
12
12
  cookieObj.uin ||
13
13
  cookieObj.qqmusic_uin ||
14
+ cookieObj.musicid ||
14
15
  cookieObj.wxuin ||
16
+ cookieObj.euin ||
15
17
  "0";
16
- const cgiRes = await req("music.userHomepage.UserHomepage", "GetHomepageHeader", {
17
- uin: Number(targetUin) || 0,
18
+ const cgiRes = await req("music.UnifiedHomepage.UnifiedHomepageSrv", "GetHomepageHeader", {
19
+ uin: targetUin,
20
+ IsQueryTabDetail: 1,
18
21
  }, {
22
+ uin: targetUin,
19
23
  cookie: query.cookie,
20
- platform: "web",
24
+ platform: "android",
21
25
  });
26
+ const rawData = cgiRes.body?.data || cgiRes.body || {};
27
+ const info = rawData.Info || rawData.info || {};
28
+ const baseInfo = info.BaseInfo || info.base_info || {};
29
+ const newIconInfo = info.NewIconInfo || info.new_icon_info || {};
30
+ const icons = info.Icons || info.icons || [];
31
+ const nick = String(baseInfo.Name || baseInfo.name || "").trim();
32
+ const avatarUrl = String(baseInfo.BigAvatar || baseInfo.Avatar || baseInfo.avatar || "").trim();
33
+ const formattedData = {
34
+ ...rawData,
35
+ nick,
36
+ headurl: avatarUrl,
37
+ avatar: avatarUrl,
38
+ BaseInfo: baseInfo,
39
+ NewIconInfo: newIconInfo,
40
+ Icons: icons,
41
+ Info: info,
42
+ };
22
43
  return {
23
44
  status: cgiRes.status,
24
45
  body: {
25
46
  code: cgiRes.body?.code ?? 200,
26
- data: cgiRes.body,
47
+ data: formattedData,
48
+ nick,
49
+ avatarUrl,
27
50
  },
28
51
  cookie: cgiRes.cookie,
29
52
  };
@@ -16,26 +16,52 @@ export default async function user_detail(
16
16
  query.uin?.toString() ||
17
17
  cookieObj.uin ||
18
18
  cookieObj.qqmusic_uin ||
19
+ cookieObj.musicid ||
19
20
  cookieObj.wxuin ||
21
+ cookieObj.euin ||
20
22
  "0";
21
23
 
22
24
  const cgiRes = await req(
23
- "music.userHomepage.UserHomepage",
25
+ "music.UnifiedHomepage.UnifiedHomepageSrv",
24
26
  "GetHomepageHeader",
25
27
  {
26
- uin: Number(targetUin) || 0,
28
+ uin: targetUin,
29
+ IsQueryTabDetail: 1,
27
30
  },
28
31
  {
32
+ uin: targetUin,
29
33
  cookie: query.cookie,
30
- platform: "web",
34
+ platform: "android",
31
35
  }
32
36
  );
33
37
 
38
+ const rawData = cgiRes.body?.data || cgiRes.body || {};
39
+ const info = rawData.Info || rawData.info || {};
40
+ const baseInfo = info.BaseInfo || info.base_info || {};
41
+ const newIconInfo = info.NewIconInfo || info.new_icon_info || {};
42
+ const icons = info.Icons || info.icons || [];
43
+
44
+ const nick = String(baseInfo.Name || baseInfo.name || "").trim();
45
+ const avatarUrl = String(baseInfo.BigAvatar || baseInfo.Avatar || baseInfo.avatar || "").trim();
46
+
47
+ const formattedData = {
48
+ ...rawData,
49
+ nick,
50
+ headurl: avatarUrl,
51
+ avatar: avatarUrl,
52
+ BaseInfo: baseInfo,
53
+ NewIconInfo: newIconInfo,
54
+ Icons: icons,
55
+ Info: info,
56
+ };
57
+
34
58
  return {
35
59
  status: cgiRes.status,
36
60
  body: {
37
61
  code: cgiRes.body?.code ?? 200,
38
- data: cgiRes.body,
62
+ data: formattedData,
63
+ nick,
64
+ avatarUrl,
39
65
  },
40
66
  cookie: cgiRes.cookie,
41
67
  };
@@ -1,9 +1,24 @@
1
1
  import { request, type ApiResponse } from "../util/request.js";
2
+ export interface NormalizedVipInfo {
3
+ vipStatus: "vip" | "nonVip" | "unknown";
4
+ vipTier?: "svip" | "vip";
5
+ vipLevel?: number;
6
+ vipYear?: boolean;
7
+ vipExpireTime?: number;
8
+ }
9
+ export declare function parseVipFromIcons(iconList?: any[], icons?: any[]): Partial<NormalizedVipInfo> | null;
10
+ export declare function deriveVipInfo(body: any): NormalizedVipInfo;
2
11
  /**
3
12
  * 获取当前登录用户的 VIP 会员信息及身份明细.
4
13
  *
5
14
  * @param query.cookie 登录 Cookie
15
+ * @param query.uin 用户 QQ/UIN
6
16
  */
7
17
  export default function user_vip_info(query?: {
8
18
  cookie?: any;
9
- }, req?: typeof request): Promise<ApiResponse>;
19
+ uin?: string | number;
20
+ }, req?: typeof request): Promise<ApiResponse<{
21
+ code: number;
22
+ data: any;
23
+ vip: NormalizedVipInfo;
24
+ }>>;
@@ -1,19 +1,134 @@
1
1
  import { request } from "../util/request.js";
2
+ import { cookieToJson } from "../util/cookie.js";
3
+ import user_detail_fn from "./user_detail.js";
4
+ export function parseVipFromIcons(iconList = [], icons = []) {
5
+ for (const item of [...iconList, ...icons]) {
6
+ if (!item)
7
+ continue;
8
+ const url = String(item.srcUrl || item.IconURL || "");
9
+ const helptxt = String(item.Helptxt || item.Title || "");
10
+ const m = url.match(/(?:^|\/)(n)?(s?vip)(\d+)\.(?:png|webp)/i);
11
+ if (m) {
12
+ const isYear = Boolean(m[1]) || helptxt.includes("年费");
13
+ const tier = m[2].toLowerCase() === "svip" ? "svip" : "vip";
14
+ const level = parseInt(m[3], 10);
15
+ return {
16
+ vipStatus: "vip",
17
+ vipTier: tier,
18
+ vipLevel: level > 0 ? Math.min(Math.max(level, 1), 10) : 1,
19
+ vipYear: isYear,
20
+ };
21
+ }
22
+ }
23
+ return null;
24
+ }
25
+ export function deriveVipInfo(body) {
26
+ if (!body) {
27
+ return { vipStatus: "unknown" };
28
+ }
29
+ const vipData = body.data || body;
30
+ const identity = vipData.identity || {};
31
+ const svip = Number(vipData.svip || 0);
32
+ const isVip = Number(identity.HugeVip || identity.vip || 0);
33
+ const level = Number(identity.level || 0);
34
+ const isYear = Boolean(Number(identity.HugeYearFlag || identity.yearflag || identity.is_year || identity.year_vip || 0) > 0 ||
35
+ Number(vipData.ystar || vipData.year || 0) > 0);
36
+ let vipStatus = "nonVip";
37
+ let vipTier = undefined;
38
+ let vipLevel = undefined;
39
+ let vipYear = undefined;
40
+ let vipExpireTime = undefined;
41
+ if (svip > 0) {
42
+ vipStatus = "vip";
43
+ vipTier = "svip";
44
+ vipLevel = level > 0 ? Math.min(Math.max(level, 1), 10) : 1;
45
+ vipYear = isYear;
46
+ }
47
+ else if (isVip > 0) {
48
+ vipStatus = "vip";
49
+ vipTier = "vip";
50
+ vipLevel = level > 0 ? Math.min(Math.max(level, 1), 10) : 1;
51
+ vipYear = isYear;
52
+ }
53
+ // 若基本 identity 未命中 VIP,尝试从附加的 Info / NewIconInfo / Icons 解析
54
+ if (vipStatus === "nonVip") {
55
+ const info = vipData.Info || vipData.info || {};
56
+ const iconList = info.NewIconInfo?.iconlist || vipData.NewIconInfo?.iconlist || [];
57
+ const icons = info.Icons || vipData.Icons || [];
58
+ const iconVip = parseVipFromIcons(iconList, icons);
59
+ if (iconVip && iconVip.vipStatus === "vip") {
60
+ vipStatus = "vip";
61
+ vipTier = iconVip.vipTier;
62
+ vipLevel = iconVip.vipLevel;
63
+ vipYear = iconVip.vipYear;
64
+ }
65
+ }
66
+ const expireTs = Number(vipData.userinfo?.expire || 0);
67
+ if (expireTs > 0) {
68
+ vipExpireTime = expireTs * 1000;
69
+ }
70
+ else if (identity.HugeVipEnd) {
71
+ const parsed = Date.parse(identity.HugeVipEnd);
72
+ if (!isNaN(parsed))
73
+ vipExpireTime = parsed;
74
+ }
75
+ else if (identity.twelveEnd) {
76
+ const parsed = Date.parse(identity.twelveEnd);
77
+ if (!isNaN(parsed))
78
+ vipExpireTime = parsed;
79
+ }
80
+ return {
81
+ vipStatus,
82
+ vipTier,
83
+ vipLevel,
84
+ vipYear,
85
+ vipExpireTime,
86
+ };
87
+ }
2
88
  /**
3
89
  * 获取当前登录用户的 VIP 会员信息及身份明细.
4
90
  *
5
91
  * @param query.cookie 登录 Cookie
92
+ * @param query.uin 用户 QQ/UIN
6
93
  */
7
94
  export default async function user_vip_info(query = {}, req = request) {
8
- const cgiRes = await req("VipLogin.VipLoginInter", "vip_login_base", {}, {
9
- cookie: query.cookie,
10
- platform: "web",
11
- });
95
+ const cookieObj = cookieToJson(query.cookie);
96
+ const uin = query.uin?.toString() ||
97
+ cookieObj.uin ||
98
+ cookieObj.qqmusic_uin ||
99
+ cookieObj.musicid ||
100
+ "0";
101
+ let cgiRes = { status: 200, body: {} };
102
+ try {
103
+ cgiRes = await req("VipLogin.VipLoginInter", "vip_login_base", {}, {
104
+ uin,
105
+ cookie: query.cookie,
106
+ platform: "android",
107
+ });
108
+ }
109
+ catch { }
110
+ let vip = deriveVipInfo(cgiRes.body);
111
+ // 若 vip_login_base 无法推断出有效 VIP,自动通过主页头部接口获取勋章母版特征
112
+ if (vip.vipStatus === "nonVip" && uin && uin !== "0") {
113
+ try {
114
+ const detailRes = await user_detail_fn({ uin, cookie: query.cookie }, req);
115
+ const detailData = detailRes.body?.data || {};
116
+ const fallbackVip = deriveVipInfo(detailData);
117
+ if (fallbackVip.vipStatus === "vip") {
118
+ vip = {
119
+ ...vip,
120
+ ...fallbackVip,
121
+ };
122
+ }
123
+ }
124
+ catch { }
125
+ }
12
126
  return {
13
127
  status: cgiRes.status,
14
128
  body: {
15
129
  code: cgiRes.body?.code ?? 200,
16
130
  data: cgiRes.body,
131
+ vip,
17
132
  },
18
133
  cookie: cgiRes.cookie,
19
134
  };