qqmusic-api-enhanced 1.0.1 → 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/main.js +3 -2
- package/main.ts +3 -0
- package/module/login_qr_check.js +80 -40
- package/module/login_qr_check.ts +85 -42
- package/module/login_qr_create.js +9 -1
- package/module/login_qr_create.ts +9 -1
- package/module/login_refresh.js +18 -4
- package/module/login_refresh.ts +20 -4
- package/module/login_status.js +25 -16
- package/module/login_status.ts +28 -19
- package/module/user_detail.js +27 -4
- package/module/user_detail.ts +30 -4
- package/module/user_vip_info.d.ts +3 -0
- package/module/user_vip_info.js +70 -7
- package/module/user_vip_info.ts +78 -13
- 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 +15 -1
- package/util/request.ts +13 -1
package/module/login_status.ts
CHANGED
|
@@ -35,21 +35,27 @@ export default async function login_status(
|
|
|
35
35
|
req = request
|
|
36
36
|
): Promise<ApiResponse<LoginStatusResult>> {
|
|
37
37
|
const cookieObj = cookieToJson(query.cookie);
|
|
38
|
-
let uin = cookieObj.uin || cookieObj.qqmusic_uin || cookieObj.wxuin || "";
|
|
38
|
+
let uin = cookieObj.uin || cookieObj.qqmusic_uin || cookieObj.musicid || cookieObj.wxuin || "";
|
|
39
|
+
const musickey = cookieObj.qm_keyst || cookieObj.qqmusic_key || cookieObj.musickey || "";
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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 {}
|
|
49
53
|
|
|
50
54
|
const data = (cgiRes.body || {}) as any;
|
|
51
55
|
const userData = data.user || data.userInfo || data;
|
|
52
|
-
|
|
56
|
+
let isLogin =
|
|
57
|
+
(data.code === 0 || (data.code === undefined && Boolean(userData.uin || userData.encryptUin))) &&
|
|
58
|
+
Boolean(userData.uin || userData.encryptUin || uin);
|
|
53
59
|
|
|
54
60
|
if (userData.uin) {
|
|
55
61
|
uin = String(userData.uin).trim();
|
|
@@ -60,13 +66,16 @@ export default async function login_status(
|
|
|
60
66
|
let nickname = userData.nick || userData.nickname || "";
|
|
61
67
|
let avatarUrl: string | undefined = userData.headurl || userData.avatar;
|
|
62
68
|
|
|
63
|
-
//
|
|
64
|
-
if (
|
|
69
|
+
// 若基本信息不全,只要有 uin,调用 user_detail 补全真实昵称与高清头像
|
|
70
|
+
if (uin && (!nickname || !avatarUrl)) {
|
|
65
71
|
try {
|
|
66
72
|
const detailRes = await user_detail_fn({ uin, cookie: query.cookie }, req);
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
69
|
-
if (
|
|
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
|
+
}
|
|
70
79
|
} catch {}
|
|
71
80
|
}
|
|
72
81
|
|
|
@@ -76,9 +85,9 @@ export default async function login_status(
|
|
|
76
85
|
|
|
77
86
|
// 补全 VIP 会员信息
|
|
78
87
|
let vip: NormalizedVipInfo = { vipStatus: "nonVip" };
|
|
79
|
-
if (isLogin) {
|
|
88
|
+
if (isLogin || uin) {
|
|
80
89
|
try {
|
|
81
|
-
const vipRes = await user_vip_info_fn({ cookie: query.cookie }, req);
|
|
90
|
+
const vipRes = await user_vip_info_fn({ cookie: query.cookie, uin }, req);
|
|
82
91
|
vip = vipRes.body.vip;
|
|
83
92
|
} catch {
|
|
84
93
|
vip = { vipStatus: "unknown" };
|
|
@@ -97,7 +106,7 @@ export default async function login_status(
|
|
|
97
106
|
};
|
|
98
107
|
|
|
99
108
|
return {
|
|
100
|
-
status: cgiRes.status,
|
|
109
|
+
status: cgiRes.status || 200,
|
|
101
110
|
body: {
|
|
102
111
|
code: isLogin ? 200 : 301,
|
|
103
112
|
isLogin,
|
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
|
};
|
|
@@ -6,14 +6,17 @@ export interface NormalizedVipInfo {
|
|
|
6
6
|
vipYear?: boolean;
|
|
7
7
|
vipExpireTime?: number;
|
|
8
8
|
}
|
|
9
|
+
export declare function parseVipFromIcons(iconList?: any[], icons?: any[]): Partial<NormalizedVipInfo> | null;
|
|
9
10
|
export declare function deriveVipInfo(body: any): NormalizedVipInfo;
|
|
10
11
|
/**
|
|
11
12
|
* 获取当前登录用户的 VIP 会员信息及身份明细.
|
|
12
13
|
*
|
|
13
14
|
* @param query.cookie 登录 Cookie
|
|
15
|
+
* @param query.uin 用户 QQ/UIN
|
|
14
16
|
*/
|
|
15
17
|
export default function user_vip_info(query?: {
|
|
16
18
|
cookie?: any;
|
|
19
|
+
uin?: string | number;
|
|
17
20
|
}, req?: typeof request): Promise<ApiResponse<{
|
|
18
21
|
code: number;
|
|
19
22
|
data: any;
|
package/module/user_vip_info.js
CHANGED
|
@@ -1,4 +1,27 @@
|
|
|
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
|
+
}
|
|
2
25
|
export function deriveVipInfo(body) {
|
|
3
26
|
if (!body) {
|
|
4
27
|
return { vipStatus: "unknown" };
|
|
@@ -8,8 +31,8 @@ export function deriveVipInfo(body) {
|
|
|
8
31
|
const svip = Number(vipData.svip || 0);
|
|
9
32
|
const isVip = Number(identity.HugeVip || identity.vip || 0);
|
|
10
33
|
const level = Number(identity.level || 0);
|
|
11
|
-
const isYear = Boolean(Number(identity.
|
|
12
|
-
Number(vipData.
|
|
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);
|
|
13
36
|
let vipStatus = "nonVip";
|
|
14
37
|
let vipTier = undefined;
|
|
15
38
|
let vipLevel = undefined;
|
|
@@ -27,6 +50,19 @@ export function deriveVipInfo(body) {
|
|
|
27
50
|
vipLevel = level > 0 ? Math.min(Math.max(level, 1), 10) : 1;
|
|
28
51
|
vipYear = isYear;
|
|
29
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
|
+
}
|
|
30
66
|
const expireTs = Number(vipData.userinfo?.expire || 0);
|
|
31
67
|
if (expireTs > 0) {
|
|
32
68
|
vipExpireTime = expireTs * 1000;
|
|
@@ -53,13 +89,40 @@ export function deriveVipInfo(body) {
|
|
|
53
89
|
* 获取当前登录用户的 VIP 会员信息及身份明细.
|
|
54
90
|
*
|
|
55
91
|
* @param query.cookie 登录 Cookie
|
|
92
|
+
* @param query.uin 用户 QQ/UIN
|
|
56
93
|
*/
|
|
57
94
|
export default async function user_vip_info(query = {}, req = request) {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
+
}
|
|
63
126
|
return {
|
|
64
127
|
status: cgiRes.status,
|
|
65
128
|
body: {
|
package/module/user_vip_info.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
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";
|
|
2
4
|
|
|
3
5
|
export interface NormalizedVipInfo {
|
|
4
6
|
vipStatus: "vip" | "nonVip" | "unknown";
|
|
@@ -8,6 +10,27 @@ export interface NormalizedVipInfo {
|
|
|
8
10
|
vipExpireTime?: number;
|
|
9
11
|
}
|
|
10
12
|
|
|
13
|
+
export function parseVipFromIcons(iconList: any[] = [], icons: any[] = []): Partial<NormalizedVipInfo> | null {
|
|
14
|
+
for (const item of [...iconList, ...icons]) {
|
|
15
|
+
if (!item) continue;
|
|
16
|
+
const url = String(item.srcUrl || item.IconURL || "");
|
|
17
|
+
const helptxt = String(item.Helptxt || item.Title || "");
|
|
18
|
+
const m = url.match(/(?:^|\/)(n)?(s?vip)(\d+)\.(?:png|webp)/i);
|
|
19
|
+
if (m) {
|
|
20
|
+
const isYear = Boolean(m[1]) || helptxt.includes("年费");
|
|
21
|
+
const tier = m[2].toLowerCase() === "svip" ? "svip" : "vip";
|
|
22
|
+
const level = parseInt(m[3], 10);
|
|
23
|
+
return {
|
|
24
|
+
vipStatus: "vip",
|
|
25
|
+
vipTier: tier,
|
|
26
|
+
vipLevel: level > 0 ? Math.min(Math.max(level, 1), 10) : 1,
|
|
27
|
+
vipYear: isYear,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
11
34
|
export function deriveVipInfo(body: any): NormalizedVipInfo {
|
|
12
35
|
if (!body) {
|
|
13
36
|
return { vipStatus: "unknown" };
|
|
@@ -18,8 +41,8 @@ export function deriveVipInfo(body: any): NormalizedVipInfo {
|
|
|
18
41
|
const isVip = Number(identity.HugeVip || identity.vip || 0);
|
|
19
42
|
const level = Number(identity.level || 0);
|
|
20
43
|
const isYear = Boolean(
|
|
21
|
-
Number(identity.
|
|
22
|
-
Number(vipData.
|
|
44
|
+
Number(identity.HugeYearFlag || identity.yearflag || identity.is_year || identity.year_vip || 0) > 0 ||
|
|
45
|
+
Number(vipData.ystar || vipData.year || 0) > 0
|
|
23
46
|
);
|
|
24
47
|
|
|
25
48
|
let vipStatus: "vip" | "nonVip" | "unknown" = "nonVip";
|
|
@@ -40,6 +63,20 @@ export function deriveVipInfo(body: any): NormalizedVipInfo {
|
|
|
40
63
|
vipYear = isYear;
|
|
41
64
|
}
|
|
42
65
|
|
|
66
|
+
// 若基本 identity 未命中 VIP,尝试从附加的 Info / NewIconInfo / Icons 解析
|
|
67
|
+
if (vipStatus === "nonVip") {
|
|
68
|
+
const info = vipData.Info || vipData.info || {};
|
|
69
|
+
const iconList = info.NewIconInfo?.iconlist || vipData.NewIconInfo?.iconlist || [];
|
|
70
|
+
const icons = info.Icons || vipData.Icons || [];
|
|
71
|
+
const iconVip = parseVipFromIcons(iconList, icons);
|
|
72
|
+
if (iconVip && iconVip.vipStatus === "vip") {
|
|
73
|
+
vipStatus = "vip";
|
|
74
|
+
vipTier = iconVip.vipTier;
|
|
75
|
+
vipLevel = iconVip.vipLevel;
|
|
76
|
+
vipYear = iconVip.vipYear;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
43
80
|
const expireTs = Number(vipData.userinfo?.expire || 0);
|
|
44
81
|
if (expireTs > 0) {
|
|
45
82
|
vipExpireTime = expireTs * 1000;
|
|
@@ -64,22 +101,50 @@ export function deriveVipInfo(body: any): NormalizedVipInfo {
|
|
|
64
101
|
* 获取当前登录用户的 VIP 会员信息及身份明细.
|
|
65
102
|
*
|
|
66
103
|
* @param query.cookie 登录 Cookie
|
|
104
|
+
* @param query.uin 用户 QQ/UIN
|
|
67
105
|
*/
|
|
68
106
|
export default async function user_vip_info(
|
|
69
|
-
query: { cookie?: any } = {},
|
|
107
|
+
query: { cookie?: any; uin?: string | number } = {},
|
|
70
108
|
req = request
|
|
71
109
|
): Promise<ApiResponse<{ code: number; data: any; vip: NormalizedVipInfo }>> {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
);
|
|
110
|
+
const cookieObj = cookieToJson(query.cookie);
|
|
111
|
+
const uin =
|
|
112
|
+
query.uin?.toString() ||
|
|
113
|
+
cookieObj.uin ||
|
|
114
|
+
cookieObj.qqmusic_uin ||
|
|
115
|
+
cookieObj.musicid ||
|
|
116
|
+
"0";
|
|
81
117
|
|
|
82
|
-
|
|
118
|
+
let cgiRes: ApiResponse<any> = { status: 200, body: {} };
|
|
119
|
+
try {
|
|
120
|
+
cgiRes = await req(
|
|
121
|
+
"VipLogin.VipLoginInter",
|
|
122
|
+
"vip_login_base",
|
|
123
|
+
{},
|
|
124
|
+
{
|
|
125
|
+
uin,
|
|
126
|
+
cookie: query.cookie,
|
|
127
|
+
platform: "android",
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
} catch {}
|
|
131
|
+
|
|
132
|
+
let vip = deriveVipInfo(cgiRes.body);
|
|
133
|
+
|
|
134
|
+
// 若 vip_login_base 无法推断出有效 VIP,自动通过主页头部接口获取勋章母版特征
|
|
135
|
+
if (vip.vipStatus === "nonVip" && uin && uin !== "0") {
|
|
136
|
+
try {
|
|
137
|
+
const detailRes = await user_detail_fn({ uin, cookie: query.cookie }, req);
|
|
138
|
+
const detailData = detailRes.body?.data || {};
|
|
139
|
+
const fallbackVip = deriveVipInfo(detailData);
|
|
140
|
+
if (fallbackVip.vipStatus === "vip") {
|
|
141
|
+
vip = {
|
|
142
|
+
...vip,
|
|
143
|
+
...fallbackVip,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
} catch {}
|
|
147
|
+
}
|
|
83
148
|
|
|
84
149
|
return {
|
|
85
150
|
status: cgiRes.status,
|
package/package.json
CHANGED
|
@@ -4,7 +4,8 @@ import mqtt from "mqtt";
|
|
|
4
4
|
import { requestCgi } from "./request.js";
|
|
5
5
|
import { cookieToString, parseSetCookie } from "./cookie.js";
|
|
6
6
|
// Polyfill ws.createWebSocketStream for Bun runtime
|
|
7
|
-
|
|
7
|
+
const isBun = typeof globalThis.Bun !== "undefined";
|
|
8
|
+
if (isBun || typeof ws.createWebSocketStream !== "function") {
|
|
8
9
|
ws.createWebSocketStream = function (socket) {
|
|
9
10
|
const duplex = new Duplex({
|
|
10
11
|
read() { },
|
|
@@ -27,6 +28,12 @@ const mobileSessions = new Map();
|
|
|
27
28
|
export function registerMobileSession(qrcodeId) {
|
|
28
29
|
if (!qrcodeId)
|
|
29
30
|
return;
|
|
31
|
+
const existing = mobileSessions.get(qrcodeId);
|
|
32
|
+
if (existing && existing.code === 200)
|
|
33
|
+
return;
|
|
34
|
+
if (existing && Date.now() - existing.createdAt < 120_000 && existing.client && !existing.client.disconnected) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
30
37
|
const now = Date.now();
|
|
31
38
|
for (const [key, s] of mobileSessions.entries()) {
|
|
32
39
|
if (now - s.createdAt > 300_000) {
|
|
@@ -109,26 +116,66 @@ export function registerMobileSession(qrcodeId) {
|
|
|
109
116
|
}
|
|
110
117
|
else if (type === "cookies") {
|
|
111
118
|
const cookies = payload?.cookies || {};
|
|
112
|
-
const uin = cookies.qqmusic_uin?.value;
|
|
113
|
-
const key = cookies.qqmusic_key?.value;
|
|
119
|
+
const uin = cookies.qqmusic_uin?.value || cookies.uin?.value || payload?.uin;
|
|
120
|
+
const key = cookies.qqmusic_key?.value || cookies.qm_keyst?.value || payload?.musickey;
|
|
114
121
|
if (uin && key) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
let loginData = null;
|
|
123
|
+
let resultCookies = {};
|
|
124
|
+
try {
|
|
125
|
+
const loginRes = await requestCgi("music.login.LoginServer", "Login", {
|
|
126
|
+
musicid: Number(uin),
|
|
127
|
+
qrCodeID: qrcodeId,
|
|
128
|
+
token: String(key),
|
|
129
|
+
}, {
|
|
130
|
+
platform: "android",
|
|
131
|
+
comm: { tmeLoginType: 6 },
|
|
132
|
+
});
|
|
133
|
+
loginData = loginRes.body;
|
|
134
|
+
resultCookies = parseSetCookie(loginRes.cookie || []);
|
|
135
|
+
}
|
|
136
|
+
catch { }
|
|
137
|
+
const realMusickey = String(loginData?.musickey || key);
|
|
138
|
+
const realUin = String(loginData?.musicid || uin);
|
|
139
|
+
const nick = String(loginData?.nick || "").trim();
|
|
140
|
+
const logo = String(loginData?.logo || "").trim();
|
|
141
|
+
const encryptUin = String(loginData?.encryptUin || cookies.euin?.value || "");
|
|
142
|
+
const refreshToken = String(loginData?.refresh_token || "");
|
|
143
|
+
const accessToken = String(loginData?.access_token || "");
|
|
144
|
+
const loginType = String(loginData?.loginType || 2);
|
|
121
145
|
const cookieObj = {
|
|
122
|
-
uin:
|
|
123
|
-
qqmusic_uin:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
146
|
+
uin: realUin,
|
|
147
|
+
qqmusic_uin: realUin,
|
|
148
|
+
musicid: realUin,
|
|
149
|
+
qm_keyst: realMusickey,
|
|
150
|
+
qqmusic_key: realMusickey,
|
|
151
|
+
musickey: realMusickey,
|
|
152
|
+
tmeAppID: "qqmusic",
|
|
153
|
+
tmeLoginType: loginType,
|
|
127
154
|
};
|
|
155
|
+
if (encryptUin) {
|
|
156
|
+
cookieObj.euin = encryptUin;
|
|
157
|
+
cookieObj.encryptUin = encryptUin;
|
|
158
|
+
}
|
|
159
|
+
if (refreshToken) {
|
|
160
|
+
cookieObj.refresh_token = refreshToken;
|
|
161
|
+
}
|
|
162
|
+
if (accessToken) {
|
|
163
|
+
cookieObj.access_token = accessToken;
|
|
164
|
+
}
|
|
165
|
+
for (const [cookieName, cookieItem] of Object.entries(cookies)) {
|
|
166
|
+
if (cookieItem && typeof cookieItem.value === "string" && !cookieObj[cookieName]) {
|
|
167
|
+
cookieObj[cookieName] = cookieItem.value;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
Object.assign(cookieObj, resultCookies);
|
|
128
171
|
state.code = 200;
|
|
129
172
|
state.message = "登录成功";
|
|
130
|
-
state.uin =
|
|
131
|
-
state.musickey =
|
|
173
|
+
state.uin = realUin;
|
|
174
|
+
state.musickey = realMusickey;
|
|
175
|
+
if (nick)
|
|
176
|
+
state.nickname = nick;
|
|
177
|
+
if (logo)
|
|
178
|
+
state.avatarUrl = logo;
|
|
132
179
|
state.cookieStr = cookieToString(cookieObj);
|
|
133
180
|
try {
|
|
134
181
|
client.end(true);
|