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.
- package/interface.d.ts +57 -4
- package/main.js +6 -1
- package/main.ts +20 -1
- package/module/login_qr_check.d.ts +5 -2
- package/module/login_qr_check.js +112 -1
- package/module/login_qr_check.ts +124 -2
- package/module/login_qr_create.js +16 -5
- package/module/login_qr_create.ts +15 -5
- package/module/login_refresh.d.ts +5 -1
- package/module/login_refresh.js +29 -4
- package/module/login_refresh.ts +33 -5
- package/module/login_status.d.ts +23 -2
- package/module/login_status.js +72 -8
- package/module/login_status.ts +101 -13
- package/module/user_detail.js +27 -4
- package/module/user_detail.ts +30 -4
- package/module/user_vip_info.d.ts +16 -1
- package/module/user_vip_info.js +119 -4
- package/module/user_vip_info.ts +139 -11
- package/package.json +1 -1
- package/util/mobile_qr_session.d.ts +2 -0
- package/util/mobile_qr_session.js +63 -16
- package/util/mobile_qr_session.ts +74 -21
- package/util/option.d.ts +2 -0
- package/util/option.js +33 -18
- package/util/option.ts +34 -20
- package/util/request.js +50 -3
- package/util/request.ts +52 -3
package/module/login_refresh.ts
CHANGED
|
@@ -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: "
|
|
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:
|
|
48
|
-
data:
|
|
74
|
+
code: resBody.code ?? (cgiRes.status === 200 ? 200 : 500),
|
|
75
|
+
data: resBody,
|
|
76
|
+
cookieStr,
|
|
49
77
|
},
|
|
50
78
|
cookie: cgiRes.cookie,
|
|
51
79
|
};
|
package/module/login_status.d.ts
CHANGED
|
@@ -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>>;
|
package/module/login_status.js
CHANGED
|
@@ -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
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
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,
|
package/module/login_status.ts
CHANGED
|
@@ -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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
23
|
-
|
|
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,
|
package/module/user_detail.js
CHANGED
|
@@ -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.
|
|
17
|
-
uin:
|
|
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: "
|
|
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:
|
|
47
|
+
data: formattedData,
|
|
48
|
+
nick,
|
|
49
|
+
avatarUrl,
|
|
27
50
|
},
|
|
28
51
|
cookie: cgiRes.cookie,
|
|
29
52
|
};
|
package/module/user_detail.ts
CHANGED
|
@@ -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.
|
|
25
|
+
"music.UnifiedHomepage.UnifiedHomepageSrv",
|
|
24
26
|
"GetHomepageHeader",
|
|
25
27
|
{
|
|
26
|
-
uin:
|
|
28
|
+
uin: targetUin,
|
|
29
|
+
IsQueryTabDetail: 1,
|
|
27
30
|
},
|
|
28
31
|
{
|
|
32
|
+
uin: targetUin,
|
|
29
33
|
cookie: query.cookie,
|
|
30
|
-
platform: "
|
|
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:
|
|
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
|
-
|
|
19
|
+
uin?: string | number;
|
|
20
|
+
}, req?: typeof request): Promise<ApiResponse<{
|
|
21
|
+
code: number;
|
|
22
|
+
data: any;
|
|
23
|
+
vip: NormalizedVipInfo;
|
|
24
|
+
}>>;
|
package/module/user_vip_info.js
CHANGED
|
@@ -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
|
|
9
|
-
|
|
10
|
-
|
|
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
|
};
|