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 CHANGED
@@ -34,8 +34,11 @@ export interface LoginQrCreateResult {
34
34
  qrsig?: string;
35
35
  ptqrtoken?: string | number;
36
36
  uuid?: string;
37
- qrimg: string;
38
- url: string;
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?: string;
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 { request, requestCgi, requestRaw, zzcSign, qrcDecrypt, hash33, getGtk, getGuid, getSearchId, cookieToJson, cookieToString, parseSetCookie, };
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 from "./module/login_status.js";
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>;
@@ -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) {
@@ -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 (typeof res.body === "string") {
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 (Buffer.isBuffer(res.body)) {
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: cgiRes.body?.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 (typeof res.body === "string") {
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: cgiRes.body?.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
+ }>>;
@@ -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: "web",
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: cgiRes.body?.code ?? 200,
31
- data: cgiRes.body,
54
+ code: resBody.code ?? (cgiRes.status === 200 ? 200 : 500),
55
+ data: resBody,
56
+ cookieStr,
32
57
  },
33
58
  cookie: cgiRes.cookie,
34
59
  };