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/interface.d.ts
CHANGED
|
@@ -34,8 +34,11 @@ export interface LoginQrCreateResult {
|
|
|
34
34
|
qrsig?: string;
|
|
35
35
|
ptqrtoken?: string | number;
|
|
36
36
|
uuid?: string;
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
qrcode?: string;
|
|
38
|
+
qrcodeID?: string;
|
|
39
|
+
qrurl?: string;
|
|
40
|
+
qrimg?: string;
|
|
41
|
+
url?: string;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
export interface LoginQrCheckQuery extends RequestBaseConfig {
|
|
@@ -43,8 +46,14 @@ export interface LoginQrCheckQuery extends RequestBaseConfig {
|
|
|
43
46
|
type?: "qq" | "wx" | "mobile";
|
|
44
47
|
/** 手机 QQ 轮询凭证,来源于 login_qr_create */
|
|
45
48
|
qrsig?: string;
|
|
49
|
+
/** 手机 QQ 轮询 token,来源于 login_qr_create */
|
|
50
|
+
ptqrtoken?: string | number;
|
|
46
51
|
/** 微信轮询凭证 uuid,来源于 login_qr_create */
|
|
47
52
|
uuid?: string;
|
|
53
|
+
/** QQ 音乐移动端 App 扫码 qrcodeID,来源于 login_qr_create */
|
|
54
|
+
qrcodeID?: string;
|
|
55
|
+
/** 兼容通用 key 参数 */
|
|
56
|
+
key?: string;
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
export interface LoginQrCheckResult {
|
|
@@ -54,6 +63,26 @@ export interface LoginQrCheckResult {
|
|
|
54
63
|
musickey?: string;
|
|
55
64
|
cookie?: Record<string, string>;
|
|
56
65
|
cookieStr?: string;
|
|
66
|
+
data?: Record<string, any>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface NormalizedVipInfo {
|
|
70
|
+
vipStatus: "vip" | "nonVip" | "unknown";
|
|
71
|
+
vipTier?: "svip" | "vip";
|
|
72
|
+
vipLevel?: number;
|
|
73
|
+
vipYear?: boolean;
|
|
74
|
+
vipExpireTime?: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface NormalizedLoginAccount {
|
|
78
|
+
userId: string;
|
|
79
|
+
nickname: string;
|
|
80
|
+
avatarUrl?: string;
|
|
81
|
+
vipStatus: "vip" | "nonVip" | "unknown";
|
|
82
|
+
vipTier?: "svip" | "vip";
|
|
83
|
+
vipLevel?: number;
|
|
84
|
+
vipYear?: boolean;
|
|
85
|
+
vipExpireTime?: number;
|
|
57
86
|
}
|
|
58
87
|
|
|
59
88
|
export interface LoginStatusQuery extends RequestBaseConfig {}
|
|
@@ -61,18 +90,37 @@ export interface LoginStatusQuery extends RequestBaseConfig {}
|
|
|
61
90
|
export interface LoginStatusResult {
|
|
62
91
|
code: number;
|
|
63
92
|
isLogin: boolean;
|
|
64
|
-
uin
|
|
93
|
+
uin: string;
|
|
94
|
+
nickname: string;
|
|
95
|
+
avatarUrl?: string;
|
|
96
|
+
vip: NormalizedVipInfo;
|
|
97
|
+
account: NormalizedLoginAccount;
|
|
98
|
+
data?: Record<string, any>;
|
|
65
99
|
userInfo?: Record<string, any>;
|
|
66
100
|
}
|
|
67
101
|
|
|
68
|
-
export interface LoginRefreshQuery extends RequestBaseConfig {
|
|
102
|
+
export interface LoginRefreshQuery extends RequestBaseConfig {
|
|
103
|
+
openid?: string;
|
|
104
|
+
refresh_token?: string;
|
|
105
|
+
refresh_key?: string;
|
|
106
|
+
}
|
|
69
107
|
|
|
70
108
|
export interface LoginRefreshResult {
|
|
71
109
|
code: number;
|
|
72
110
|
musickey?: string;
|
|
111
|
+
cookieStr?: string;
|
|
112
|
+
data?: Record<string, any>;
|
|
73
113
|
raw?: Record<string, any>;
|
|
74
114
|
}
|
|
75
115
|
|
|
116
|
+
export interface UserVipInfoQuery extends RequestBaseConfig {}
|
|
117
|
+
|
|
118
|
+
export interface UserVipInfoResult {
|
|
119
|
+
code: number;
|
|
120
|
+
data: Record<string, any>;
|
|
121
|
+
vip: NormalizedVipInfo;
|
|
122
|
+
}
|
|
123
|
+
|
|
76
124
|
// ==============================
|
|
77
125
|
// 2. 歌曲与播放 (Songs & Playback)
|
|
78
126
|
// ==============================
|
|
@@ -281,6 +329,8 @@ export function song_detail(query?: SongDetailQuery): Promise<Response<SongDetai
|
|
|
281
329
|
export function song_url(query?: SongUrlQuery): Promise<Response<SongUrlResult>>;
|
|
282
330
|
export function user_detail(query?: UserDetailQuery): Promise<Response<UserDetailResult>>;
|
|
283
331
|
export function user_playlist(query?: UserPlaylistQuery): Promise<Response<UserPlaylistResult>>;
|
|
332
|
+
export function user_vip_info(query?: UserVipInfoQuery): Promise<Response<UserVipInfoResult>>;
|
|
333
|
+
export function deriveVipInfo(body: any): NormalizedVipInfo;
|
|
284
334
|
|
|
285
335
|
// 基础网络调度器与工具函数
|
|
286
336
|
export function request(arg1: string, arg2?: any, arg3?: any, arg4?: any): Promise<ApiResponse>;
|
|
@@ -312,6 +362,8 @@ declare const api: {
|
|
|
312
362
|
song_url: typeof song_url;
|
|
313
363
|
user_detail: typeof user_detail;
|
|
314
364
|
user_playlist: typeof user_playlist;
|
|
365
|
+
user_vip_info: typeof user_vip_info;
|
|
366
|
+
deriveVipInfo: typeof deriveVipInfo;
|
|
315
367
|
request: typeof request;
|
|
316
368
|
requestCgi: typeof requestCgi;
|
|
317
369
|
requestRaw: typeof requestRaw;
|
|
@@ -327,3 +379,4 @@ declare const api: {
|
|
|
327
379
|
};
|
|
328
380
|
|
|
329
381
|
export default api;
|
|
382
|
+
|
package/main.js
CHANGED
|
@@ -13,6 +13,7 @@ import song_detail_fn from "./module/song_detail.js";
|
|
|
13
13
|
import song_url_fn from "./module/song_url.js";
|
|
14
14
|
import user_detail_fn from "./module/user_detail.js";
|
|
15
15
|
import user_playlist_fn from "./module/user_playlist.js";
|
|
16
|
+
import user_vip_info_fn, { deriveVipInfo, parseVipFromIcons, } from "./module/user_vip_info.js";
|
|
16
17
|
import { cookieToJson, cookieToString, parseSetCookie } from "./util/cookie.js";
|
|
17
18
|
import { getGtk, getGuid, getSearchId, hash33, qrcDecrypt } from "./util/crypto.js";
|
|
18
19
|
import { request, requestCgi, requestRaw } from "./util/request.js";
|
|
@@ -32,7 +33,8 @@ export const song_detail = (query = {}) => song_detail_fn(query, request);
|
|
|
32
33
|
export const song_url = (query = {}) => song_url_fn(query, request);
|
|
33
34
|
export const user_detail = (query = {}) => user_detail_fn(query, request);
|
|
34
35
|
export const user_playlist = (query = {}) => user_playlist_fn(query, request);
|
|
35
|
-
export
|
|
36
|
+
export const user_vip_info = (query = {}) => user_vip_info_fn(query, request);
|
|
37
|
+
export { request, requestCgi, requestRaw, zzcSign, qrcDecrypt, hash33, getGtk, getGuid, getSearchId, cookieToJson, cookieToString, parseSetCookie, deriveVipInfo, parseVipFromIcons, };
|
|
36
38
|
const api = {
|
|
37
39
|
artist_detail,
|
|
38
40
|
artist_songs,
|
|
@@ -49,6 +51,9 @@ const api = {
|
|
|
49
51
|
song_url,
|
|
50
52
|
user_detail,
|
|
51
53
|
user_playlist,
|
|
54
|
+
user_vip_info,
|
|
55
|
+
deriveVipInfo,
|
|
56
|
+
parseVipFromIcons,
|
|
52
57
|
request,
|
|
53
58
|
requestCgi,
|
|
54
59
|
requestRaw,
|
package/main.ts
CHANGED
|
@@ -3,7 +3,10 @@ import artist_songs_fn from "./module/artist_songs.js";
|
|
|
3
3
|
import login_qr_check_fn from "./module/login_qr_check.js";
|
|
4
4
|
import login_qr_create_fn from "./module/login_qr_create.js";
|
|
5
5
|
import login_refresh_fn from "./module/login_refresh.js";
|
|
6
|
-
import login_status_fn
|
|
6
|
+
import login_status_fn, {
|
|
7
|
+
type LoginStatusResult,
|
|
8
|
+
type NormalizedLoginAccount,
|
|
9
|
+
} from "./module/login_status.js";
|
|
7
10
|
import lyric_fn from "./module/lyric.js";
|
|
8
11
|
import playlist_detail_fn from "./module/playlist_detail.js";
|
|
9
12
|
import playlist_track_all_fn from "./module/playlist_track_all.js";
|
|
@@ -13,6 +16,11 @@ import song_detail_fn from "./module/song_detail.js";
|
|
|
13
16
|
import song_url_fn from "./module/song_url.js";
|
|
14
17
|
import user_detail_fn from "./module/user_detail.js";
|
|
15
18
|
import user_playlist_fn from "./module/user_playlist.js";
|
|
19
|
+
import user_vip_info_fn, {
|
|
20
|
+
deriveVipInfo,
|
|
21
|
+
parseVipFromIcons,
|
|
22
|
+
type NormalizedVipInfo,
|
|
23
|
+
} from "./module/user_vip_info.js";
|
|
16
24
|
|
|
17
25
|
import { cookieToJson, cookieToString, parseSetCookie } from "./util/cookie.js";
|
|
18
26
|
import { getGtk, getGuid, getSearchId, hash33, qrcDecrypt } from "./util/crypto.js";
|
|
@@ -64,6 +72,9 @@ export const user_detail = (query: Parameters<typeof user_detail_fn>[0] = {}) =>
|
|
|
64
72
|
export const user_playlist = (query: Parameters<typeof user_playlist_fn>[0] = {}) =>
|
|
65
73
|
user_playlist_fn(query, request);
|
|
66
74
|
|
|
75
|
+
export const user_vip_info = (query: Parameters<typeof user_vip_info_fn>[0] = {}) =>
|
|
76
|
+
user_vip_info_fn(query, request);
|
|
77
|
+
|
|
67
78
|
export {
|
|
68
79
|
request,
|
|
69
80
|
requestCgi,
|
|
@@ -77,7 +88,12 @@ export {
|
|
|
77
88
|
cookieToJson,
|
|
78
89
|
cookieToString,
|
|
79
90
|
parseSetCookie,
|
|
91
|
+
deriveVipInfo,
|
|
92
|
+
parseVipFromIcons,
|
|
80
93
|
type ApiResponse,
|
|
94
|
+
type NormalizedVipInfo,
|
|
95
|
+
type NormalizedLoginAccount,
|
|
96
|
+
type LoginStatusResult,
|
|
81
97
|
};
|
|
82
98
|
|
|
83
99
|
const api = {
|
|
@@ -96,6 +112,9 @@ const api = {
|
|
|
96
112
|
song_url,
|
|
97
113
|
user_detail,
|
|
98
114
|
user_playlist,
|
|
115
|
+
user_vip_info,
|
|
116
|
+
deriveVipInfo,
|
|
117
|
+
parseVipFromIcons,
|
|
99
118
|
request,
|
|
100
119
|
requestCgi,
|
|
101
120
|
requestRaw,
|
|
@@ -2,14 +2,17 @@ import { request, type ApiResponse } from "../util/request.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* 轮询检查二维码登录状态.
|
|
4
4
|
*
|
|
5
|
-
* @param query.type 登录类型: 'qq' | 'wx'
|
|
5
|
+
* @param query.type 登录类型: 'qq' | 'wx' | 'mobile'
|
|
6
6
|
* @param query.qrsig QQ 二维码凭证标识 (type=qq 时必填)
|
|
7
7
|
* @param query.ptqrtoken QQ 二维码校验 token (type=qq 时必填)
|
|
8
8
|
* @param query.uuid 微信登录会话 uuid (type=wx 时必填)
|
|
9
|
+
* @param query.qrcodeID QQ 音乐 App 扫码 qrcodeID (type=mobile 时必填)
|
|
9
10
|
*/
|
|
10
11
|
export default function login_qr_check(query?: {
|
|
11
|
-
type?: "qq" | "wx";
|
|
12
|
+
type?: "qq" | "wx" | "mobile";
|
|
12
13
|
qrsig?: string;
|
|
13
14
|
ptqrtoken?: string | number;
|
|
14
15
|
uuid?: string;
|
|
16
|
+
qrcodeID?: string;
|
|
17
|
+
key?: string;
|
|
15
18
|
}, req?: typeof request): Promise<ApiResponse>;
|
package/module/login_qr_check.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { request, requestCgi } from "../util/request.js";
|
|
2
2
|
import { getGtk, getGuid } from "../util/crypto.js";
|
|
3
3
|
import { cookieToString, parseSetCookie } from "../util/cookie.js";
|
|
4
|
+
import { getMobileSession, registerMobileSession } from "../util/mobile_qr_session.js";
|
|
4
5
|
/**
|
|
5
6
|
* 轮询检查二维码登录状态.
|
|
6
7
|
*
|
|
7
|
-
* @param query.type 登录类型: 'qq' | 'wx'
|
|
8
|
+
* @param query.type 登录类型: 'qq' | 'wx' | 'mobile'
|
|
8
9
|
* @param query.qrsig QQ 二维码凭证标识 (type=qq 时必填)
|
|
9
10
|
* @param query.ptqrtoken QQ 二维码校验 token (type=qq 时必填)
|
|
10
11
|
* @param query.uuid 微信登录会话 uuid (type=wx 时必填)
|
|
12
|
+
* @param query.qrcodeID QQ 音乐 App 扫码 qrcodeID (type=mobile 时必填)
|
|
11
13
|
*/
|
|
12
14
|
export default async function login_qr_check(query = {}, req = request) {
|
|
13
15
|
const type = query.type || "qq";
|
|
@@ -138,6 +140,115 @@ export default async function login_qr_check(query = {}, req = request) {
|
|
|
138
140
|
cookie: resultCookie,
|
|
139
141
|
};
|
|
140
142
|
}
|
|
143
|
+
if (type === "mobile") {
|
|
144
|
+
const qrcodeID = query.qrcodeID || query.key;
|
|
145
|
+
if (!qrcodeID) {
|
|
146
|
+
return {
|
|
147
|
+
status: 400,
|
|
148
|
+
body: { code: 400, message: "缺少必要参数 qrcodeID" },
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// 1. 确保后台 MQTT 会话已启动
|
|
152
|
+
let session = getMobileSession(qrcodeID);
|
|
153
|
+
if (!session) {
|
|
154
|
+
try {
|
|
155
|
+
registerMobileSession(qrcodeID);
|
|
156
|
+
}
|
|
157
|
+
catch { }
|
|
158
|
+
session = getMobileSession(qrcodeID);
|
|
159
|
+
}
|
|
160
|
+
// 2. 优先检查实时会话事件
|
|
161
|
+
if (session && session.code === 200) {
|
|
162
|
+
return {
|
|
163
|
+
status: 200,
|
|
164
|
+
body: {
|
|
165
|
+
code: 200,
|
|
166
|
+
message: "登录成功",
|
|
167
|
+
uin: session.uin || "",
|
|
168
|
+
musickey: session.musickey || "",
|
|
169
|
+
nickname: session.nickname || "",
|
|
170
|
+
avatarUrl: session.avatarUrl || "",
|
|
171
|
+
cookieStr: session.cookieStr || "",
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (session && session.code === 802) {
|
|
176
|
+
return {
|
|
177
|
+
status: 200,
|
|
178
|
+
body: { code: 802, message: session.message || "已扫码,等待确认" },
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (session && session.code === 800) {
|
|
182
|
+
return {
|
|
183
|
+
status: 200,
|
|
184
|
+
body: { code: 800, message: session.message || "二维码已过期,请刷新" },
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (session && session.code === 803) {
|
|
188
|
+
return {
|
|
189
|
+
status: 200,
|
|
190
|
+
body: { code: 803, message: session.message || "用户取消授权" },
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
// 3. 兜底轮询 CheckQRCode CGI 状态
|
|
194
|
+
try {
|
|
195
|
+
const cgiRes = await req("music.login.LoginServer", "CheckQRCode", {
|
|
196
|
+
qrcodeID,
|
|
197
|
+
tmeAppID: "qqmusic",
|
|
198
|
+
}, {
|
|
199
|
+
platform: "android",
|
|
200
|
+
});
|
|
201
|
+
const cgiBody = (cgiRes.body || {});
|
|
202
|
+
const code = cgiBody.code !== undefined ? cgiBody.code : cgiBody.ret;
|
|
203
|
+
if (code === 40001) {
|
|
204
|
+
return {
|
|
205
|
+
status: 200,
|
|
206
|
+
body: { code: 802, message: "已扫码,等待确认" },
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (code === 40002) {
|
|
210
|
+
return {
|
|
211
|
+
status: 200,
|
|
212
|
+
body: { code: 800, message: "二维码已过期,请刷新" },
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
if (code === 0) {
|
|
216
|
+
const cookies = cgiRes.cookie || [];
|
|
217
|
+
const cookieObj = parseSetCookie(cookies);
|
|
218
|
+
if (cgiBody.musicid && !cookieObj.uin)
|
|
219
|
+
cookieObj.uin = String(cgiBody.musicid);
|
|
220
|
+
if (cgiBody.musickey && !cookieObj.qm_keyst)
|
|
221
|
+
cookieObj.qm_keyst = String(cgiBody.musickey);
|
|
222
|
+
if (cgiBody.musickey && !cookieObj.qqmusic_key)
|
|
223
|
+
cookieObj.qqmusic_key = String(cgiBody.musickey);
|
|
224
|
+
if (cgiBody.openid && !cookieObj.openid)
|
|
225
|
+
cookieObj.openid = String(cgiBody.openid);
|
|
226
|
+
if (cgiBody.refresh_token && !cookieObj.refresh_token)
|
|
227
|
+
cookieObj.refresh_token = String(cgiBody.refresh_token);
|
|
228
|
+
const cookieStr = cookieToString(cookieObj);
|
|
229
|
+
return {
|
|
230
|
+
status: 200,
|
|
231
|
+
body: {
|
|
232
|
+
code: 200,
|
|
233
|
+
message: "登录成功",
|
|
234
|
+
uin: cookieObj.uin || String(cgiBody.musicid || ""),
|
|
235
|
+
musickey: cookieObj.qm_keyst || String(cgiBody.musickey || ""),
|
|
236
|
+
cookieStr,
|
|
237
|
+
data: cgiBody,
|
|
238
|
+
},
|
|
239
|
+
cookie: cookies,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch { }
|
|
244
|
+
return {
|
|
245
|
+
status: 200,
|
|
246
|
+
body: {
|
|
247
|
+
code: session?.code || 801,
|
|
248
|
+
message: session?.message || "等待 QQ 音乐 App 扫码",
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
|
141
252
|
// 微信扫码轮询
|
|
142
253
|
const { uuid } = query;
|
|
143
254
|
if (!uuid) {
|
package/module/login_qr_check.ts
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
import { request, requestCgi, type ApiResponse } from "../util/request.js";
|
|
2
2
|
import { getGtk, getGuid } from "../util/crypto.js";
|
|
3
3
|
import { cookieToString, parseSetCookie } from "../util/cookie.js";
|
|
4
|
+
import { getMobileSession, registerMobileSession } from "../util/mobile_qr_session.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* 轮询检查二维码登录状态.
|
|
7
8
|
*
|
|
8
|
-
* @param query.type 登录类型: 'qq' | 'wx'
|
|
9
|
+
* @param query.type 登录类型: 'qq' | 'wx' | 'mobile'
|
|
9
10
|
* @param query.qrsig QQ 二维码凭证标识 (type=qq 时必填)
|
|
10
11
|
* @param query.ptqrtoken QQ 二维码校验 token (type=qq 时必填)
|
|
11
12
|
* @param query.uuid 微信登录会话 uuid (type=wx 时必填)
|
|
13
|
+
* @param query.qrcodeID QQ 音乐 App 扫码 qrcodeID (type=mobile 时必填)
|
|
12
14
|
*/
|
|
13
15
|
export default async function login_qr_check(
|
|
14
16
|
query: {
|
|
15
|
-
type?: "qq" | "wx";
|
|
17
|
+
type?: "qq" | "wx" | "mobile";
|
|
16
18
|
qrsig?: string;
|
|
17
19
|
ptqrtoken?: string | number;
|
|
18
20
|
uuid?: string;
|
|
21
|
+
qrcodeID?: string;
|
|
22
|
+
key?: string;
|
|
19
23
|
} = {},
|
|
20
24
|
req = request
|
|
21
25
|
): Promise<ApiResponse> {
|
|
@@ -179,6 +183,124 @@ export default async function login_qr_check(
|
|
|
179
183
|
};
|
|
180
184
|
}
|
|
181
185
|
|
|
186
|
+
if (type === "mobile") {
|
|
187
|
+
const qrcodeID = query.qrcodeID || query.key;
|
|
188
|
+
if (!qrcodeID) {
|
|
189
|
+
return {
|
|
190
|
+
status: 400,
|
|
191
|
+
body: { code: 400, message: "缺少必要参数 qrcodeID" },
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 1. 确保后台 MQTT 会话已启动
|
|
196
|
+
let session = getMobileSession(qrcodeID);
|
|
197
|
+
if (!session) {
|
|
198
|
+
try {
|
|
199
|
+
registerMobileSession(qrcodeID);
|
|
200
|
+
} catch {}
|
|
201
|
+
session = getMobileSession(qrcodeID);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// 2. 优先检查实时会话事件
|
|
205
|
+
if (session && session.code === 200) {
|
|
206
|
+
return {
|
|
207
|
+
status: 200,
|
|
208
|
+
body: {
|
|
209
|
+
code: 200,
|
|
210
|
+
message: "登录成功",
|
|
211
|
+
uin: session.uin || "",
|
|
212
|
+
musickey: session.musickey || "",
|
|
213
|
+
nickname: session.nickname || "",
|
|
214
|
+
avatarUrl: session.avatarUrl || "",
|
|
215
|
+
cookieStr: session.cookieStr || "",
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (session && session.code === 802) {
|
|
221
|
+
return {
|
|
222
|
+
status: 200,
|
|
223
|
+
body: { code: 802, message: session.message || "已扫码,等待确认" },
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (session && session.code === 800) {
|
|
228
|
+
return {
|
|
229
|
+
status: 200,
|
|
230
|
+
body: { code: 800, message: session.message || "二维码已过期,请刷新" },
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (session && session.code === 803) {
|
|
235
|
+
return {
|
|
236
|
+
status: 200,
|
|
237
|
+
body: { code: 803, message: session.message || "用户取消授权" },
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 3. 兜底轮询 CheckQRCode CGI 状态
|
|
242
|
+
try {
|
|
243
|
+
const cgiRes = await req(
|
|
244
|
+
"music.login.LoginServer",
|
|
245
|
+
"CheckQRCode",
|
|
246
|
+
{
|
|
247
|
+
qrcodeID,
|
|
248
|
+
tmeAppID: "qqmusic",
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
platform: "android",
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const cgiBody = (cgiRes.body || {}) as any;
|
|
256
|
+
const code = cgiBody.code !== undefined ? cgiBody.code : cgiBody.ret;
|
|
257
|
+
|
|
258
|
+
if (code === 40001) {
|
|
259
|
+
return {
|
|
260
|
+
status: 200,
|
|
261
|
+
body: { code: 802, message: "已扫码,等待确认" },
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
if (code === 40002) {
|
|
265
|
+
return {
|
|
266
|
+
status: 200,
|
|
267
|
+
body: { code: 800, message: "二维码已过期,请刷新" },
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (code === 0) {
|
|
271
|
+
const cookies = cgiRes.cookie || [];
|
|
272
|
+
const cookieObj = parseSetCookie(cookies);
|
|
273
|
+
if (cgiBody.musicid && !cookieObj.uin) cookieObj.uin = String(cgiBody.musicid);
|
|
274
|
+
if (cgiBody.musickey && !cookieObj.qm_keyst) cookieObj.qm_keyst = String(cgiBody.musickey);
|
|
275
|
+
if (cgiBody.musickey && !cookieObj.qqmusic_key) cookieObj.qqmusic_key = String(cgiBody.musickey);
|
|
276
|
+
if (cgiBody.openid && !cookieObj.openid) cookieObj.openid = String(cgiBody.openid);
|
|
277
|
+
if (cgiBody.refresh_token && !cookieObj.refresh_token) cookieObj.refresh_token = String(cgiBody.refresh_token);
|
|
278
|
+
|
|
279
|
+
const cookieStr = cookieToString(cookieObj);
|
|
280
|
+
return {
|
|
281
|
+
status: 200,
|
|
282
|
+
body: {
|
|
283
|
+
code: 200,
|
|
284
|
+
message: "登录成功",
|
|
285
|
+
uin: cookieObj.uin || String(cgiBody.musicid || ""),
|
|
286
|
+
musickey: cookieObj.qm_keyst || String(cgiBody.musickey || ""),
|
|
287
|
+
cookieStr,
|
|
288
|
+
data: cgiBody,
|
|
289
|
+
},
|
|
290
|
+
cookie: cookies,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
} catch {}
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
status: 200,
|
|
297
|
+
body: {
|
|
298
|
+
code: session?.code || 801,
|
|
299
|
+
message: session?.message || "等待 QQ 音乐 App 扫码",
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
182
304
|
// 微信扫码轮询
|
|
183
305
|
const { uuid } = query;
|
|
184
306
|
if (!uuid) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { request } from "../util/request.js";
|
|
2
2
|
import { hash33 } from "../util/crypto.js";
|
|
3
3
|
import { parseSetCookie } from "../util/cookie.js";
|
|
4
|
+
import { registerMobileSession } from "../util/mobile_qr_session.js";
|
|
4
5
|
/**
|
|
5
6
|
* 获取登录二维码 (QQ / 微信 / 移动端App).
|
|
6
7
|
*
|
|
@@ -24,14 +25,17 @@ export default async function login_qr_create(query = {}, req = request) {
|
|
|
24
25
|
const ptqrtoken = hash33(qrsig);
|
|
25
26
|
// 二维码图片转 base64
|
|
26
27
|
let qrimg = "";
|
|
27
|
-
if (
|
|
28
|
+
if (Buffer.isBuffer(res.body)) {
|
|
29
|
+
qrimg = `data:image/png;base64,${res.body.toString("base64")}`;
|
|
30
|
+
}
|
|
31
|
+
else if (res.body instanceof Uint8Array) {
|
|
28
32
|
qrimg = `data:image/png;base64,${Buffer.from(res.body).toString("base64")}`;
|
|
29
33
|
}
|
|
30
|
-
else if (
|
|
31
|
-
qrimg = `data:image/png;base64,${res.body.toString("base64")}`;
|
|
34
|
+
else if (typeof res.body === "string") {
|
|
35
|
+
qrimg = res.body.startsWith("data:") ? res.body : `data:image/png;base64,${Buffer.from(res.body).toString("base64")}`;
|
|
32
36
|
}
|
|
33
37
|
else {
|
|
34
|
-
qrimg = `data:image/png;base64,${Buffer.from(String(res.body)).toString("base64")}`;
|
|
38
|
+
qrimg = `data:image/png;base64,${Buffer.from(String(res.body || "")).toString("base64")}`;
|
|
35
39
|
}
|
|
36
40
|
return {
|
|
37
41
|
status: 200,
|
|
@@ -75,13 +79,20 @@ export default async function login_qr_create(query = {}, req = request) {
|
|
|
75
79
|
const cgiRes = await req("music.login.LoginServer", "CreateQRCode", {
|
|
76
80
|
tmeAppID: "qqmusic",
|
|
77
81
|
}, { platform: "android" });
|
|
82
|
+
const qrcodeID = cgiRes.body?.qrcodeID;
|
|
83
|
+
if (qrcodeID) {
|
|
84
|
+
try {
|
|
85
|
+
registerMobileSession(qrcodeID);
|
|
86
|
+
}
|
|
87
|
+
catch { }
|
|
88
|
+
}
|
|
78
89
|
return {
|
|
79
90
|
status: cgiRes.status,
|
|
80
91
|
body: {
|
|
81
92
|
code: 200,
|
|
82
93
|
type: "mobile",
|
|
83
94
|
qrcode: cgiRes.body?.qrcode,
|
|
84
|
-
qrcodeID
|
|
95
|
+
qrcodeID,
|
|
85
96
|
},
|
|
86
97
|
cookie: cgiRes.cookie,
|
|
87
98
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { request, type ApiResponse } from "../util/request.js";
|
|
2
2
|
import { hash33 } from "../util/crypto.js";
|
|
3
3
|
import { parseSetCookie } from "../util/cookie.js";
|
|
4
|
+
import { registerMobileSession } from "../util/mobile_qr_session.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* 获取登录二维码 (QQ / 微信 / 移动端App).
|
|
@@ -34,12 +35,14 @@ export default async function login_qr_create(
|
|
|
34
35
|
|
|
35
36
|
// 二维码图片转 base64
|
|
36
37
|
let qrimg = "";
|
|
37
|
-
if (
|
|
38
|
-
qrimg = `data:image/png;base64,${Buffer.from(res.body).toString("base64")}`;
|
|
39
|
-
} else if (Buffer.isBuffer(res.body)) {
|
|
38
|
+
if (Buffer.isBuffer(res.body)) {
|
|
40
39
|
qrimg = `data:image/png;base64,${res.body.toString("base64")}`;
|
|
40
|
+
} else if (res.body instanceof Uint8Array) {
|
|
41
|
+
qrimg = `data:image/png;base64,${Buffer.from(res.body).toString("base64")}`;
|
|
42
|
+
} else if (typeof res.body === "string") {
|
|
43
|
+
qrimg = res.body.startsWith("data:") ? res.body : `data:image/png;base64,${Buffer.from(res.body).toString("base64")}`;
|
|
41
44
|
} else {
|
|
42
|
-
qrimg = `data:image/png;base64,${Buffer.from(String(res.body)).toString("base64")}`;
|
|
45
|
+
qrimg = `data:image/png;base64,${Buffer.from(String(res.body || "")).toString("base64")}`;
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
return {
|
|
@@ -97,13 +100,20 @@ export default async function login_qr_create(
|
|
|
97
100
|
{ platform: "android" }
|
|
98
101
|
);
|
|
99
102
|
|
|
103
|
+
const qrcodeID = cgiRes.body?.qrcodeID;
|
|
104
|
+
if (qrcodeID) {
|
|
105
|
+
try {
|
|
106
|
+
registerMobileSession(qrcodeID);
|
|
107
|
+
} catch {}
|
|
108
|
+
}
|
|
109
|
+
|
|
100
110
|
return {
|
|
101
111
|
status: cgiRes.status,
|
|
102
112
|
body: {
|
|
103
113
|
code: 200,
|
|
104
114
|
type: "mobile",
|
|
105
115
|
qrcode: cgiRes.body?.qrcode,
|
|
106
|
-
qrcodeID
|
|
116
|
+
qrcodeID,
|
|
107
117
|
},
|
|
108
118
|
cookie: cgiRes.cookie,
|
|
109
119
|
};
|
|
@@ -11,4 +11,8 @@ export default function login_refresh(query?: {
|
|
|
11
11
|
openid?: string;
|
|
12
12
|
refresh_token?: string;
|
|
13
13
|
refresh_key?: string;
|
|
14
|
-
}, req?: typeof request): Promise<ApiResponse
|
|
14
|
+
}, req?: typeof request): Promise<ApiResponse<{
|
|
15
|
+
code: number;
|
|
16
|
+
data: any;
|
|
17
|
+
cookieStr: string;
|
|
18
|
+
}>>;
|
package/module/login_refresh.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { request } 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
|
*
|
|
@@ -11,10 +11,12 @@ export default async function login_refresh(query = {}, req = request) {
|
|
|
11
11
|
const cookieObj = cookieToJson(query.cookie);
|
|
12
12
|
const uin = cookieObj.uin || cookieObj.qqmusic_uin || cookieObj.wxuin || "0";
|
|
13
13
|
const musickey = cookieObj.qm_keyst || cookieObj.qqmusic_key || "";
|
|
14
|
+
const loginType = cookieObj.tmeLoginType || (musickey.startsWith("W_X") ? "1" : "2");
|
|
14
15
|
const param = {
|
|
15
16
|
openid: query.openid || cookieObj.openid || "",
|
|
16
17
|
refresh_token: query.refresh_token || cookieObj.refresh_token || "",
|
|
17
18
|
refresh_key: query.refresh_key || cookieObj.refresh_key || "",
|
|
19
|
+
access_token: cookieObj.access_token || "",
|
|
18
20
|
musicid: Number(uin) || 0,
|
|
19
21
|
str_musicid: uin,
|
|
20
22
|
musickey,
|
|
@@ -22,13 +24,36 @@ export default async function login_refresh(query = {}, req = request) {
|
|
|
22
24
|
};
|
|
23
25
|
const cgiRes = await req("music.login.LoginServer", "Login", param, {
|
|
24
26
|
cookie: query.cookie,
|
|
25
|
-
platform: "
|
|
27
|
+
platform: "android",
|
|
28
|
+
comm: { tmeLoginType: Number(loginType) || 2 },
|
|
26
29
|
});
|
|
30
|
+
const resBody = (cgiRes.body || {});
|
|
31
|
+
const newCookies = parseSetCookie(cgiRes.cookie || []);
|
|
32
|
+
const mergedCookies = { ...cookieObj, ...newCookies };
|
|
33
|
+
if (resBody.musickey) {
|
|
34
|
+
mergedCookies.qm_keyst = resBody.musickey;
|
|
35
|
+
mergedCookies.qqmusic_key = resBody.musickey;
|
|
36
|
+
mergedCookies.musickey = resBody.musickey;
|
|
37
|
+
}
|
|
38
|
+
if (resBody.musicid) {
|
|
39
|
+
mergedCookies.uin = String(resBody.musicid);
|
|
40
|
+
mergedCookies.qqmusic_uin = String(resBody.musicid);
|
|
41
|
+
mergedCookies.musicid = String(resBody.musicid);
|
|
42
|
+
}
|
|
43
|
+
if (resBody.encryptUin) {
|
|
44
|
+
mergedCookies.euin = resBody.encryptUin;
|
|
45
|
+
mergedCookies.encryptUin = resBody.encryptUin;
|
|
46
|
+
}
|
|
47
|
+
if (resBody.refresh_token) {
|
|
48
|
+
mergedCookies.refresh_token = resBody.refresh_token;
|
|
49
|
+
}
|
|
50
|
+
const cookieStr = cookieToString(mergedCookies);
|
|
27
51
|
return {
|
|
28
52
|
status: cgiRes.status,
|
|
29
53
|
body: {
|
|
30
|
-
code:
|
|
31
|
-
data:
|
|
54
|
+
code: resBody.code ?? (cgiRes.status === 200 ? 200 : 500),
|
|
55
|
+
data: resBody,
|
|
56
|
+
cookieStr,
|
|
32
57
|
},
|
|
33
58
|
cookie: cgiRes.cookie,
|
|
34
59
|
};
|