oicq-icalingua-plus-plus 1.26.24 → 1.26.26
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/index.d.ts +28 -3
- package/lib/client.js +23 -0
- package/lib/device.js +17 -0
- package/lib/message/face.js +15 -0
- package/lib/ntlogin/ecdh.js +19 -0
- package/lib/ntlogin/index.js +691 -0
- package/lib/ref.d.ts +2 -0
- package/lib/wtlogin/login-password.js +14 -2
- package/lib/wtlogin/wt.js +47 -2
- package/package.json +3 -3
package/index.d.ts
CHANGED
|
@@ -57,9 +57,14 @@ export interface ConfBot {
|
|
|
57
57
|
/** 使用NT上线包,部分消息暂未支持 */
|
|
58
58
|
useNT?: boolean,
|
|
59
59
|
|
|
60
|
+
/** 强制使用旧版WT登录,即使当前APK支持NT登录 */
|
|
61
|
+
forceWt?: boolean,
|
|
62
|
+
|
|
60
63
|
/** 指定APK信息,覆盖默认的协议参数 */
|
|
61
64
|
apk_info?: {
|
|
62
65
|
id?: string,
|
|
66
|
+
nt?: boolean,
|
|
67
|
+
platform?: string,
|
|
63
68
|
name?: string,
|
|
64
69
|
version?: string,
|
|
65
70
|
ver?: string,
|
|
@@ -517,6 +522,24 @@ export interface DeviceEventData extends CommonSystemEventData {
|
|
|
517
522
|
url: string, //验证地址
|
|
518
523
|
phone: string, //密保手机
|
|
519
524
|
}
|
|
525
|
+
export interface LoginAuthDevice {
|
|
526
|
+
guid: string,
|
|
527
|
+
qimei: string,
|
|
528
|
+
qimei36: string,
|
|
529
|
+
subappid: string,
|
|
530
|
+
platform: string,
|
|
531
|
+
brand: string,
|
|
532
|
+
model: string,
|
|
533
|
+
bssid: string,
|
|
534
|
+
devInfo: string,
|
|
535
|
+
sysVersion: string,
|
|
536
|
+
}
|
|
537
|
+
export interface LoginAuthEventData extends CommonSystemEventData {
|
|
538
|
+
system_type: "login",
|
|
539
|
+
sub_type: "auth", //登录频繁时的身份验证
|
|
540
|
+
url: string, //验证地址
|
|
541
|
+
device: LoginAuthDevice,
|
|
542
|
+
}
|
|
520
543
|
export interface LoginErrorEventData extends CommonSystemEventData {
|
|
521
544
|
system_type: "login",
|
|
522
545
|
sub_type: "error", //登录遇到错误
|
|
@@ -805,8 +828,8 @@ export type GroupNoticeEventData = GroupRecallEventData | GroupSettingEventData
|
|
|
805
828
|
MemberIncreaseEventData | MemberDecreaseEventData | GroupPokeEventData | GroupSignEventData |
|
|
806
829
|
GroupEssenceEventData; //11
|
|
807
830
|
|
|
808
|
-
export type SystemEventData = DeviceEventData | SliderEventData | LoginErrorEventData | QrcodeEventData |
|
|
809
|
-
OfflineEventData | OnlineEventData; //
|
|
831
|
+
export type SystemEventData = DeviceEventData | LoginAuthEventData | SliderEventData | LoginErrorEventData | QrcodeEventData |
|
|
832
|
+
OfflineEventData | OnlineEventData; //7(5+2)
|
|
810
833
|
export type RequestEventData = FriendAddEventData | GroupAddEventData | GroupInviteEventData; //3
|
|
811
834
|
export type MessageEventData = PrivateMessageEventData | GroupMessageEventData | DiscussMessageEventData; //3
|
|
812
835
|
export type NoticeEventData = FriendNoticeEventData | GroupNoticeEventData; //2
|
|
@@ -921,9 +944,11 @@ export interface EventMap {
|
|
|
921
944
|
"system.login.slider": (this: Client, data: SliderEventData) => void;
|
|
922
945
|
/**设备锁验证事件 */
|
|
923
946
|
"system.login.device": (this: Client, data: DeviceEventData) => void;
|
|
947
|
+
/**登录频繁时的身份验证事件 */
|
|
948
|
+
"system.login.auth": (this: Client, data: LoginAuthEventData) => void;
|
|
924
949
|
/**登录遇到错误事件 */
|
|
925
950
|
"system.login.error": (this: Client, data: LoginErrorEventData) => void;
|
|
926
|
-
"system.login": (this: Client, data: DeviceEventData | LoginErrorEventData | SliderEventData | QrcodeEventData) => void;
|
|
951
|
+
"system.login": (this: Client, data: DeviceEventData | LoginAuthEventData | LoginErrorEventData | SliderEventData | QrcodeEventData) => void;
|
|
927
952
|
/**上线事件 */
|
|
928
953
|
"system.online": (this: Client, data: OnlineEventData) => void;
|
|
929
954
|
|
package/lib/client.js
CHANGED
|
@@ -128,6 +128,28 @@ class Client extends EventEmitter {
|
|
|
128
128
|
"SQQzoneSvc.shuoshuo",
|
|
129
129
|
"trpc.group_pro.msgproxy.sendmsg",
|
|
130
130
|
"trpc.login.ecdh.EcdhService.SsoNTLoginPasswordLoginUnusualDevice",
|
|
131
|
+
"trpc.login.ecdh.EcdhService.SsoQRLoginGenQr",
|
|
132
|
+
"trpc.login.ecdh.EcdhService.SsoNTLoginPasswordLogin",
|
|
133
|
+
"trpc.login.ecdh.EcdhService.SsoNTLoginGetSaltList",
|
|
134
|
+
"trpc.login.ecdh.EcdhService.SsoNTLoginCheckA1List",
|
|
135
|
+
"trpc.login.ecdh.EcdhService.SsoNTLoginAuthNewDevice",
|
|
136
|
+
"trpc.login.ecdh.EcdhService.SsoNTLoginRefreshTicket",
|
|
137
|
+
"trpc.login.ecdh.EcdhService.SsoNTLoginOptimusLogin",
|
|
138
|
+
"trpc.login.ecdh.EcdhService.SsoNTLoginCheckGateWayCode",
|
|
139
|
+
"trpc.login.ecdh.EcdhService.SsoNTLoginCheckThirdCode",
|
|
140
|
+
"trpc.login.ecdh.EcdhService.SsoNTLoginGetSms",
|
|
141
|
+
"trpc.login.ecdh.EcdhService.SsoNTLoginCheckSms",
|
|
142
|
+
"trpc.login.ecdh.EcdhService.SsoOIDB0x916a",
|
|
143
|
+
"trpc.login.ecdh.EcdhService.SsoOIDB0x916b",
|
|
144
|
+
"trpc.login.ecdh.EcdhService.SsoOIDB0x916c",
|
|
145
|
+
"trpc.login.ecdh.EcdhService.SsoOIDB0x916d",
|
|
146
|
+
"trpc.login.ecdh.EcdhService.SsoNTLoginRapidLogin",
|
|
147
|
+
"trpc.login.ecdh.EcdhService.SsoNTLoginRefreshA2",
|
|
148
|
+
"trpc.login.ecdh.EcdhService.SsoKeyExchange",
|
|
149
|
+
"trpc.login.ecdh.EcdhService.SsoQRLoginAuthQr",
|
|
150
|
+
"trpc.login.ecdh.EcdhService.SsoQRLoginRejectQr",
|
|
151
|
+
"trpc.login.ecdh.EcdhService.SsoQRLoginCancleQr",
|
|
152
|
+
"trpc.login.ecdh.EcdhService.SsoNTLoginEasyLogin",
|
|
131
153
|
"trpc.o3.ecdh_access.EcdhAccess.SsoEstablishShareKey",
|
|
132
154
|
"trpc.o3.ecdh_access.EcdhAccess.SsoSecureA2Access",
|
|
133
155
|
"trpc.o3.ecdh_access.EcdhAccess.SsoSecureA2Establish",
|
|
@@ -264,6 +286,7 @@ class Client extends EventEmitter {
|
|
|
264
286
|
ffmpeg_path: "ffmpeg",
|
|
265
287
|
ffprobe_path: "ffprobe",
|
|
266
288
|
apk_info: {},
|
|
289
|
+
forceWt: false,
|
|
267
290
|
...config
|
|
268
291
|
};
|
|
269
292
|
this.config = config;
|
package/lib/device.js
CHANGED
|
@@ -142,6 +142,8 @@ function getDeviceInfo(filepath, uin) {
|
|
|
142
142
|
|
|
143
143
|
const DEFAULT_ANDROID_APK_INFO = {//android phone
|
|
144
144
|
id: "com.tencent.mobileqq",
|
|
145
|
+
nt: false,
|
|
146
|
+
platform: "Android",
|
|
145
147
|
name: "A8.9.50.f5a7d351",
|
|
146
148
|
version: "8.9.50.10650",
|
|
147
149
|
ver: "8.9.50",
|
|
@@ -168,6 +170,8 @@ const apk = {
|
|
|
168
170
|
//android watch
|
|
169
171
|
3: {
|
|
170
172
|
id: "com.tencent.qqlite",
|
|
173
|
+
nt: false,
|
|
174
|
+
platform: "Android",
|
|
171
175
|
name: "A9.0.1",
|
|
172
176
|
version: "9.0.1",
|
|
173
177
|
ver: "9.0.1",
|
|
@@ -185,6 +189,8 @@ const apk = {
|
|
|
185
189
|
//mac
|
|
186
190
|
4: {
|
|
187
191
|
id: "com.tencent.qq",
|
|
192
|
+
nt: false,
|
|
193
|
+
platform: "Mac",
|
|
188
194
|
name: "A6.8.2.21241",
|
|
189
195
|
version: "6.8.2.21241",
|
|
190
196
|
ver: "6.8.2",
|
|
@@ -226,6 +232,8 @@ const apk = {
|
|
|
226
232
|
//TIM 3.5.1
|
|
227
233
|
10: {
|
|
228
234
|
id: "com.tencent.tim",
|
|
235
|
+
nt: false,
|
|
236
|
+
platform: "Android",
|
|
229
237
|
name: "A3.5.1.db08e878",
|
|
230
238
|
version: "3.5.1.3168",
|
|
231
239
|
ver: "3.5.1",
|
|
@@ -568,6 +576,7 @@ const apk = {
|
|
|
568
576
|
//android phone 9.2.80
|
|
569
577
|
61: {
|
|
570
578
|
...DEFAULT_ANDROID_APK_INFO,
|
|
579
|
+
nt: true,
|
|
571
580
|
name: "A9.2.80.90316290",
|
|
572
581
|
version: "9.2.80.35125",
|
|
573
582
|
ver: "9.2.80",
|
|
@@ -697,6 +706,7 @@ apk[5].subid = 537155074;
|
|
|
697
706
|
apk[5].sign = apk[4].sign;
|
|
698
707
|
apk[5].name = "A8.9.50.611";
|
|
699
708
|
apk[5].version = "8.9.50.611";
|
|
709
|
+
apk[5].platform = "iPad";
|
|
700
710
|
|
|
701
711
|
//ipad 8.9.33
|
|
702
712
|
apk[9] = { ...apk[7] };
|
|
@@ -704,9 +714,12 @@ apk[9].subid = 537151363;
|
|
|
704
714
|
apk[9].sign = apk[4].sign;
|
|
705
715
|
apk[9].name = "A8.9.33.614";
|
|
706
716
|
apk[9].version = "8.9.33.614";
|
|
717
|
+
apk[9].platform = "iPad";
|
|
707
718
|
|
|
708
719
|
apk[30001] = {
|
|
709
720
|
id: "com.tencent.qqlite",
|
|
721
|
+
nt: false,
|
|
722
|
+
platform: "Android",
|
|
710
723
|
name: "A2.0.8",
|
|
711
724
|
version: "2.0.8",
|
|
712
725
|
ver: "2.0.8",
|
|
@@ -722,6 +735,8 @@ apk[30001] = {
|
|
|
722
735
|
|
|
723
736
|
apk[30002] = {
|
|
724
737
|
id: "com.tencent.qqlite",
|
|
738
|
+
nt: false,
|
|
739
|
+
platform: "Android",
|
|
725
740
|
name: "A2.0.5",
|
|
726
741
|
version: "2.0.5",
|
|
727
742
|
ver: "2.0.5",
|
|
@@ -737,6 +752,8 @@ apk[30002] = {
|
|
|
737
752
|
|
|
738
753
|
apk[30003] = {
|
|
739
754
|
id: "com.tencent.qqlite",
|
|
755
|
+
nt: false,
|
|
756
|
+
platform: "Android",
|
|
740
757
|
name: "A9.0.3",
|
|
741
758
|
version: "9.0.3",
|
|
742
759
|
ver: "9.0.3",
|
package/lib/message/face.js
CHANGED
|
@@ -411,6 +411,21 @@ const map = {
|
|
|
411
411
|
490: "/能送我吗",
|
|
412
412
|
491: "/自信学霸",
|
|
413
413
|
492: "/让我细品",
|
|
414
|
+
493: "/又是早八",
|
|
415
|
+
494: "/举杯邀月",
|
|
416
|
+
495: "/兔来",
|
|
417
|
+
496: "/阴晴圆缺",
|
|
418
|
+
497: "/休假了",
|
|
419
|
+
498: "/中!",
|
|
420
|
+
499: "/观月C位",
|
|
421
|
+
500: "/秋秋赏月",
|
|
422
|
+
501: "/秋秋之约",
|
|
423
|
+
502: "/秋秋泛舟",
|
|
424
|
+
503: "/秋愿达成",
|
|
425
|
+
504: "/同你玩",
|
|
426
|
+
505: "/送花花",
|
|
427
|
+
506: "/捡到宝了",
|
|
428
|
+
507: "/被发现了",
|
|
414
429
|
};
|
|
415
430
|
|
|
416
431
|
const FACE_OLD_BUF = Buffer.from([0x00, 0x01, 0x00, 0x04, 0x52, 0xCC, 0xF5, 0xD0]);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const { createECDH } = require("crypto");
|
|
3
|
+
|
|
4
|
+
const NTQQ_PUBLIC_KEY = Buffer.from("049D1423332735980EDABE7E9EA451B3395B6F35250DB8FC56F25889F628CBAE3E8E73077914071EEEBC108F4E0170057792BB17AA303AF652313D17C1AC815E79", "hex");
|
|
5
|
+
|
|
6
|
+
class Ecdh {
|
|
7
|
+
constructor() {
|
|
8
|
+
const ecdh = createECDH("prime256v1");
|
|
9
|
+
this.public_key = ecdh.generateKeys();
|
|
10
|
+
this.nt_share_key = ecdh.computeSecret(NTQQ_PUBLIC_KEY);
|
|
11
|
+
this._ecdh = ecdh;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
ntExchange(bobPublic) {
|
|
15
|
+
return this._ecdh.computeSecret(bobPublic);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = Ecdh;
|
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
randomBytes,
|
|
5
|
+
createCipheriv,
|
|
6
|
+
createDecipheriv,
|
|
7
|
+
createHash,
|
|
8
|
+
} = require("crypto");
|
|
9
|
+
const { WtLogin } = require("../wtlogin/wt");
|
|
10
|
+
const Writer = require("../wtlogin/writer");
|
|
11
|
+
const Ecdh = require("./ecdh");
|
|
12
|
+
const pb = require("../algo/pb");
|
|
13
|
+
const tea = require("../algo/tea");
|
|
14
|
+
const { md5, timestamp, BUF0, BUF4 } = require("../common");
|
|
15
|
+
const { TimeoutError } = require("../exception");
|
|
16
|
+
|
|
17
|
+
const NT_LOGIN_CMD_PREFIX = "trpc.login.ecdh.EcdhService.";
|
|
18
|
+
const NT_LOGIN_COMMANDS = Object.freeze({
|
|
19
|
+
KEY_EXCHANGE: NT_LOGIN_CMD_PREFIX + "SsoKeyExchange",
|
|
20
|
+
PASSWORD_LOGIN: NT_LOGIN_CMD_PREFIX + "SsoNTLoginPasswordLogin",
|
|
21
|
+
PASSWORD_LOGIN_UNUSUAL_DEVICE: NT_LOGIN_CMD_PREFIX + "SsoNTLoginPasswordLoginUnusualDevice",
|
|
22
|
+
EASY_LOGIN: NT_LOGIN_CMD_PREFIX + "SsoNTLoginEasyLogin",
|
|
23
|
+
REFRESH_TICKET: NT_LOGIN_CMD_PREFIX + "SsoNTLoginRefreshTicket",
|
|
24
|
+
REFRESH_A2: NT_LOGIN_CMD_PREFIX + "SsoNTLoginRefreshA2",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const NT_LOGIN_PROOF_KEY = Buffer.from(
|
|
28
|
+
"e2733bf403149913cbf80c7a95168bd4ca6935ee53cd39764beebe2e007e3aee",
|
|
29
|
+
"hex",
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const NTLoginErrorCode = {
|
|
33
|
+
AccountNotUin: 140022018,
|
|
34
|
+
AccountOrPasswordError: 140022013,
|
|
35
|
+
BlackAccount: 150022021,
|
|
36
|
+
CookieExpired: 150022039,
|
|
37
|
+
CookieUsedExceedUpperLimit: 150022040,
|
|
38
|
+
Default: 140022000,
|
|
39
|
+
ExpireTicket: 140022014,
|
|
40
|
+
Frozen: 140022005,
|
|
41
|
+
IllegalTicket: 140022016,
|
|
42
|
+
IllegalAccount: 150022030,
|
|
43
|
+
InterfaceUnavailable: 150022031,
|
|
44
|
+
InvalidCookie: 140022012,
|
|
45
|
+
InvalidParameter: 140022001,
|
|
46
|
+
KickedTicket: 140022015,
|
|
47
|
+
NeedUpdate: 140022004,
|
|
48
|
+
NewDevice: 140022010,
|
|
49
|
+
ProofWater: 140022008,
|
|
50
|
+
Strict: 140022007,
|
|
51
|
+
Success: 0,
|
|
52
|
+
UnusualDevice: 140022011,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const NT_LOGIN_PLATFORM_ID = {
|
|
56
|
+
Android: 2,
|
|
57
|
+
Mac: 5,
|
|
58
|
+
iPad: 6,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {any} value
|
|
63
|
+
* @returns {Buffer}
|
|
64
|
+
*/
|
|
65
|
+
function toBuffer(value) {
|
|
66
|
+
if (Buffer.isBuffer(value)) return value;
|
|
67
|
+
if (value instanceof Uint8Array) return Buffer.from(value);
|
|
68
|
+
if (value && typeof value.toBuffer === "function") {
|
|
69
|
+
const buffer = value.toBuffer();
|
|
70
|
+
return Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
|
|
71
|
+
}
|
|
72
|
+
return BUF0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {any} value
|
|
77
|
+
* @returns {string}
|
|
78
|
+
*/
|
|
79
|
+
function toString(value) {
|
|
80
|
+
return value === undefined || value === null ? "" : String(value);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {any} value
|
|
85
|
+
* @returns {number}
|
|
86
|
+
*/
|
|
87
|
+
function toNumber(value) {
|
|
88
|
+
if (typeof value === "bigint") return Number(value);
|
|
89
|
+
return Number(value || 0);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {any} value
|
|
94
|
+
* @returns {any}
|
|
95
|
+
*/
|
|
96
|
+
function first(value) {
|
|
97
|
+
return Array.isArray(value) ? value[0] : value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
102
|
+
* @returns {number}
|
|
103
|
+
*/
|
|
104
|
+
function getNTLoginPlatform(wt) {
|
|
105
|
+
return NT_LOGIN_PLATFORM_ID[wt.apk.platform] || 2;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
110
|
+
* @returns {string}
|
|
111
|
+
*/
|
|
112
|
+
function getNTLoginPlatformName(wt) {
|
|
113
|
+
return String(wt.apk.platform || "Android").toUpperCase();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @param {Buffer} data
|
|
118
|
+
* @param {Buffer} key
|
|
119
|
+
* @returns {Buffer}
|
|
120
|
+
*/
|
|
121
|
+
function aesGcmEncrypt(data, key) {
|
|
122
|
+
const iv = randomBytes(12);
|
|
123
|
+
const cipher = createCipheriv("aes-256-gcm", toBuffer(key), iv);
|
|
124
|
+
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
125
|
+
return Buffer.concat([iv, encrypted, cipher.getAuthTag()]);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param {Buffer} data
|
|
130
|
+
* @param {Buffer} key
|
|
131
|
+
* @returns {Buffer}
|
|
132
|
+
*/
|
|
133
|
+
function aesGcmDecrypt(data, key) {
|
|
134
|
+
if (!Buffer.isBuffer(data) || data.length < 28)
|
|
135
|
+
throw new Error("invalid AES-GCM data");
|
|
136
|
+
const iv = data.subarray(0, 12);
|
|
137
|
+
const tag = data.subarray(-16);
|
|
138
|
+
const decipher = createDecipheriv("aes-256-gcm", toBuffer(key), iv);
|
|
139
|
+
decipher.setAuthTag(tag);
|
|
140
|
+
return Buffer.concat([
|
|
141
|
+
decipher.update(data.subarray(12, -16)),
|
|
142
|
+
decipher.final(),
|
|
143
|
+
]);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param {Buffer} data
|
|
148
|
+
* @returns {Buffer}
|
|
149
|
+
*/
|
|
150
|
+
function sha256(data) {
|
|
151
|
+
return createHash("sha256").update(data).digest();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* NT 0x106 is intentionally kept here. The normal TLV packer must retain
|
|
156
|
+
* the original OICQ behavior, while NT uses sso_ver=0 and subid=1.
|
|
157
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
158
|
+
* @param {Buffer} md5pass
|
|
159
|
+
* @returns {Buffer}
|
|
160
|
+
*/
|
|
161
|
+
function buildNTPasswordA1(wt, md5pass) {
|
|
162
|
+
md5pass = toBuffer(md5pass);
|
|
163
|
+
if (!md5pass.length) return toBuffer(wt.t106);
|
|
164
|
+
const body = new Writer()
|
|
165
|
+
.writeU16(4)
|
|
166
|
+
.writeBytes(randomBytes(4))
|
|
167
|
+
.writeU32(0)
|
|
168
|
+
.writeU32(wt.apk.appid)
|
|
169
|
+
.writeU32(0)
|
|
170
|
+
.writeU64(wt.uin)
|
|
171
|
+
.write32((Date.now() / 1000) & 0xffffffff)
|
|
172
|
+
.writeBytes(BUF4)
|
|
173
|
+
.writeU8(1)
|
|
174
|
+
.writeBytes(md5pass)
|
|
175
|
+
.writeBytes(wt.device.tgtgt)
|
|
176
|
+
.writeU32(0)
|
|
177
|
+
.writeU8(1)
|
|
178
|
+
.writeBytes(wt.device.guid)
|
|
179
|
+
.writeU32(1)
|
|
180
|
+
.writeU32(1)
|
|
181
|
+
.writeTlv(String(wt.uin))
|
|
182
|
+
.writeU16(0)
|
|
183
|
+
.read();
|
|
184
|
+
const uin = Buffer.alloc(4);
|
|
185
|
+
uin.writeUInt32BE(wt.uin);
|
|
186
|
+
const key = md5(Buffer.concat([md5pass, BUF4, uin]));
|
|
187
|
+
return tea.encrypt(body, key);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
192
|
+
* @param {boolean} passwordLogin
|
|
193
|
+
* @returns {object}
|
|
194
|
+
*/
|
|
195
|
+
function buildNTLoginHead(wt, passwordLogin) {
|
|
196
|
+
const head = {
|
|
197
|
+
1: {
|
|
198
|
+
1: String(wt.uin),
|
|
199
|
+
},
|
|
200
|
+
2: {
|
|
201
|
+
1: getNTLoginPlatformName(wt),
|
|
202
|
+
2: wt.device.model,
|
|
203
|
+
3: getNTLoginPlatform(wt),
|
|
204
|
+
4: wt.device.guid,
|
|
205
|
+
},
|
|
206
|
+
3: {
|
|
207
|
+
1: passwordLogin ? "" : wt.apk.ver,
|
|
208
|
+
2: wt.apk.appid,
|
|
209
|
+
3: wt.apk.id,
|
|
210
|
+
5: wt.apk.qua,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
if (passwordLogin) head[7] = { 1: 1 };
|
|
214
|
+
if (wt.nt_login_cookie) head[5] = { 1: wt.nt_login_cookie };
|
|
215
|
+
return head;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
220
|
+
* @param {object} proto
|
|
221
|
+
* @returns {Promise<Buffer>}
|
|
222
|
+
*/
|
|
223
|
+
async function buildNTServicePacket(wt, proto) {
|
|
224
|
+
if (!await wt.keyExchange()) throw new Error("NT key exchange failed");
|
|
225
|
+
const inner = wt.apk.appid === 16
|
|
226
|
+
? { 2: proto, 3: { 1: 0, 2: String(wt.uin) } }
|
|
227
|
+
: proto;
|
|
228
|
+
const encrypted = aesGcmEncrypt(pb.encode(inner), wt.nt_session.key);
|
|
229
|
+
return Buffer.from(pb.encode({
|
|
230
|
+
1: wt.nt_session.ticket,
|
|
231
|
+
[wt.apk.appid === 16 ? 5 : 3]: encrypted,
|
|
232
|
+
4: 1,
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* 构造 SsoKeyExchange 包。
|
|
238
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
239
|
+
* @param {number} seq
|
|
240
|
+
* @returns {Promise<Buffer>}
|
|
241
|
+
*/
|
|
242
|
+
async function buildSsoKeyExchangePacket(wt, seq) {
|
|
243
|
+
if (!wt.nt_ecdh) wt.nt_ecdh = new Ecdh();
|
|
244
|
+
const ecdh = wt.nt_ecdh;
|
|
245
|
+
const plain1 = pb.encode({
|
|
246
|
+
1: "",
|
|
247
|
+
2: wt.device.guid,
|
|
248
|
+
});
|
|
249
|
+
const gcmCalc1 = aesGcmEncrypt(plain1, ecdh.nt_share_key);
|
|
250
|
+
const ts = Date.now();
|
|
251
|
+
const plain2 = new Writer()
|
|
252
|
+
.writeBytes(ecdh.public_key)
|
|
253
|
+
.writeU32(1)
|
|
254
|
+
.writeBytes(gcmCalc1)
|
|
255
|
+
.writeU64(ts)
|
|
256
|
+
.read();
|
|
257
|
+
const gcmCalc2 = aesGcmEncrypt(sha256(plain2), NT_LOGIN_PROOF_KEY);
|
|
258
|
+
const body = pb.encode({
|
|
259
|
+
1: ecdh.public_key,
|
|
260
|
+
2: 1,
|
|
261
|
+
3: gcmCalc1,
|
|
262
|
+
4: ts,
|
|
263
|
+
5: gcmCalc2,
|
|
264
|
+
});
|
|
265
|
+
return wt._buildLoginPacket(NT_LOGIN_COMMANDS.KEY_EXCHANGE, body, 2, seq, 0);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
270
|
+
* @returns {object|undefined}
|
|
271
|
+
*/
|
|
272
|
+
function buildNTCaptcha(wt) {
|
|
273
|
+
const captcha = wt.nt_captcha;
|
|
274
|
+
if (!captcha) return undefined;
|
|
275
|
+
const match = /(?:^|[?&])sid=([^&]*)/.exec(String(captcha.url || ""));
|
|
276
|
+
return {
|
|
277
|
+
1: captcha.ticket,
|
|
278
|
+
2: captcha.randStr,
|
|
279
|
+
3: match ? match[1] : "",
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* 构造 SsoNTLoginPasswordLogin 包。
|
|
285
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
286
|
+
* @returns {Promise<Buffer>}
|
|
287
|
+
*/
|
|
288
|
+
async function buildSsoNTLoginPasswordLoginPacket(wt) {
|
|
289
|
+
const proto = {
|
|
290
|
+
1: buildNTLoginHead(wt, true),
|
|
291
|
+
2: {
|
|
292
|
+
1: toBuffer(wt.t106),
|
|
293
|
+
2: buildNTCaptcha(wt),
|
|
294
|
+
5: { 1: 1 },
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
const packet = await buildNTServicePacket(wt, proto);
|
|
298
|
+
wt.nt_captcha = null;
|
|
299
|
+
return packet;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* 构造 SsoNTLoginPasswordLoginUnusualDevice 包。
|
|
304
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
305
|
+
* @returns {Promise<Buffer>}
|
|
306
|
+
*/
|
|
307
|
+
async function buildSsoNTLoginPasswordLoginUnusualDevicePacket(wt) {
|
|
308
|
+
const proto = {
|
|
309
|
+
1: buildNTLoginHead(wt, true),
|
|
310
|
+
2: {
|
|
311
|
+
1: toBuffer(wt.t106),
|
|
312
|
+
2: wt.nt_unusual_device_check_sig,
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
const packet = await buildNTServicePacket(wt, proto);
|
|
316
|
+
wt.nt_unusual_device_check_sig = null;
|
|
317
|
+
return packet;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* 构造 SsoNTLoginEasyLogin 包。
|
|
322
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
323
|
+
* @returns {Promise<Buffer>}
|
|
324
|
+
*/
|
|
325
|
+
async function buildSsoNTLoginEasyLoginPacket(wt) {
|
|
326
|
+
return buildNTServicePacket(wt, {
|
|
327
|
+
1: buildNTLoginHead(wt, false),
|
|
328
|
+
2: {
|
|
329
|
+
1: toBuffer(wt.t106),
|
|
330
|
+
2: wt.sig.srm_token,
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* 构造 SsoNTLoginRefreshTicket 包。
|
|
337
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
338
|
+
* @returns {Promise<Buffer>}
|
|
339
|
+
*/
|
|
340
|
+
async function buildSsoNTLoginRefreshTicketPacket(wt) {
|
|
341
|
+
return buildNTServicePacket(wt, {
|
|
342
|
+
1: buildNTLoginHead(wt, false),
|
|
343
|
+
2: {
|
|
344
|
+
1: toBuffer(wt.t106),
|
|
345
|
+
2: wt.sig.srm_token,
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* 构造 SsoNTLoginRefreshA2 包。
|
|
352
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
353
|
+
* @returns {Promise<Buffer>}
|
|
354
|
+
*/
|
|
355
|
+
async function buildSsoNTLoginRefreshA2Packet(wt) {
|
|
356
|
+
return buildNTServicePacket(wt, {
|
|
357
|
+
1: buildNTLoginHead(wt, false),
|
|
358
|
+
2: {
|
|
359
|
+
1: wt.sig.tgt,
|
|
360
|
+
2: wt.sig.d2,
|
|
361
|
+
3: wt.sig.d2key,
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
WtLogin.prototype.buildSsoKeyExchangePacket = function (seq) {
|
|
367
|
+
return buildSsoKeyExchangePacket(this, seq);
|
|
368
|
+
};
|
|
369
|
+
WtLogin.prototype.buildSsoNTLoginPasswordLoginPacket = function () {
|
|
370
|
+
return buildSsoNTLoginPasswordLoginPacket(this);
|
|
371
|
+
};
|
|
372
|
+
WtLogin.prototype.buildSsoNTLoginPasswordLoginUnusualDevicePacket = function () {
|
|
373
|
+
return buildSsoNTLoginPasswordLoginUnusualDevicePacket(this);
|
|
374
|
+
};
|
|
375
|
+
WtLogin.prototype.buildSsoNTLoginEasyLoginPacket = function () {
|
|
376
|
+
return buildSsoNTLoginEasyLoginPacket(this);
|
|
377
|
+
};
|
|
378
|
+
WtLogin.prototype.buildSsoNTLoginRefreshTicketPacket = function () {
|
|
379
|
+
return buildSsoNTLoginRefreshTicketPacket(this);
|
|
380
|
+
};
|
|
381
|
+
WtLogin.prototype.buildSsoNTLoginRefreshA2Packet = function () {
|
|
382
|
+
return buildSsoNTLoginRefreshA2Packet(this);
|
|
383
|
+
};
|
|
384
|
+
/**
|
|
385
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
386
|
+
* @returns {boolean}
|
|
387
|
+
*/
|
|
388
|
+
function hasNTSession(wt) {
|
|
389
|
+
const session = wt.nt_session;
|
|
390
|
+
return !!session && Buffer.isBuffer(session.key) && session.key.length === 32 &&
|
|
391
|
+
Buffer.isBuffer(session.ticket) && session.ticket.length > 0 &&
|
|
392
|
+
session.expire_time - timestamp() >= 60;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
397
|
+
* @returns {Promise<boolean>}
|
|
398
|
+
*/
|
|
399
|
+
async function performNTKeyExchange(wt) {
|
|
400
|
+
wt.nt_session = null;
|
|
401
|
+
if (!wt.nt_ecdh) wt.nt_ecdh = new Ecdh();
|
|
402
|
+
const seq = wt.c.nextSeq();
|
|
403
|
+
const packet = await wt.buildSsoKeyExchangePacket(seq);
|
|
404
|
+
const response = pb.decode(await wt.c.send(packet, 6, seq));
|
|
405
|
+
const encrypted = toBuffer(response?.[1]);
|
|
406
|
+
const serverPublicKey = toBuffer(response?.[3]);
|
|
407
|
+
if (!encrypted.length || !serverPublicKey.length) return false;
|
|
408
|
+
const shareKey = wt.nt_ecdh.ntExchange(serverPublicKey);
|
|
409
|
+
const session = pb.decode(aesGcmDecrypt(encrypted, shareKey));
|
|
410
|
+
const key = toBuffer(session?.[1]);
|
|
411
|
+
const ticket = toBuffer(session?.[2]);
|
|
412
|
+
const expire = toNumber(session?.[3]);
|
|
413
|
+
if (key.length !== 32 || !ticket.length || !expire) return false;
|
|
414
|
+
wt.nt_session = {
|
|
415
|
+
key,
|
|
416
|
+
ticket,
|
|
417
|
+
expire_time: timestamp() + expire,
|
|
418
|
+
};
|
|
419
|
+
wt.logger.debug(`NT key exchange succeeded, session: ${expire}s`);
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
WtLogin.prototype.keyExchange = function () {
|
|
424
|
+
if (hasNTSession(this)) return Promise.resolve(true);
|
|
425
|
+
if (!this.ntKeyExchangePromise) {
|
|
426
|
+
this.ntKeyExchangePromise = performNTKeyExchange(this).finally(() => {
|
|
427
|
+
this.ntKeyExchangePromise = null;
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
return this.ntKeyExchangePromise;
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
435
|
+
* @param {Buffer} payload
|
|
436
|
+
* @returns {Buffer}
|
|
437
|
+
*/
|
|
438
|
+
function decryptNTLoginPacket(wt, payload) {
|
|
439
|
+
if (!wt.nt_session) return BUF0;
|
|
440
|
+
const response = pb.decode(payload);
|
|
441
|
+
const encrypted = toBuffer(response?.[wt.apk.appid === 16 ? 5 : 3]);
|
|
442
|
+
if (!encrypted.length) throw new Error("invalid NT login response");
|
|
443
|
+
const decrypted = aesGcmDecrypt(encrypted, wt.nt_session.key);
|
|
444
|
+
if (wt.apk.appid !== 16) return decrypted;
|
|
445
|
+
const inner = pb.decode(decrypted);
|
|
446
|
+
return toBuffer(inner?.[2]);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
451
|
+
* @param {string} cmd
|
|
452
|
+
* @param {Buffer} body
|
|
453
|
+
* @param {number} timeout
|
|
454
|
+
* @returns {Promise<void>}
|
|
455
|
+
*/
|
|
456
|
+
async function sendNTLoginPacket(wt, cmd, body, timeout = 5) {
|
|
457
|
+
try {
|
|
458
|
+
const seq = wt.c.nextSeq();
|
|
459
|
+
const packet = await wt._buildLoginPacket(cmd, body, 2, seq);
|
|
460
|
+
const response = await wt.c.send(packet, timeout, seq);
|
|
461
|
+
await decodeNTLoginResponse(wt, cmd, decryptNTLoginPacket(wt, response));
|
|
462
|
+
} catch (e) {
|
|
463
|
+
wt.logger.error(e.message || e);
|
|
464
|
+
if (wt.token_flag && e instanceof TimeoutError === false)
|
|
465
|
+
await wt.deleteToken();
|
|
466
|
+
wt.c.emit("internal.network", "服务器繁忙");
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
472
|
+
* @param {string} cmd
|
|
473
|
+
* @param {() => Promise<Buffer>} builder
|
|
474
|
+
* @returns {Promise<void>}
|
|
475
|
+
*/
|
|
476
|
+
async function sendNTLoginWithBuilder(wt, cmd, builder) {
|
|
477
|
+
let body;
|
|
478
|
+
try {
|
|
479
|
+
body = await builder.call(wt);
|
|
480
|
+
} catch (e) {
|
|
481
|
+
const message = e.message || "NT login packet build failed";
|
|
482
|
+
wt.logger.error(message);
|
|
483
|
+
wt.c.em("system.login.error", { code: -1, message });
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
return sendNTLoginPacket(wt, cmd, body, 5);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
WtLogin.prototype.ntPasswordLogin = function (md5pass) {
|
|
490
|
+
this.nt_ecdh = new Ecdh();
|
|
491
|
+
this.nt_session = null;
|
|
492
|
+
this.t106 = buildNTPasswordA1(this, md5pass);
|
|
493
|
+
if (this.nt_unusual_device_check_sig) {
|
|
494
|
+
return sendNTLoginWithBuilder(
|
|
495
|
+
this,
|
|
496
|
+
NT_LOGIN_COMMANDS.PASSWORD_LOGIN_UNUSUAL_DEVICE,
|
|
497
|
+
this.buildSsoNTLoginPasswordLoginUnusualDevicePacket,
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
return sendNTLoginWithBuilder(
|
|
501
|
+
this,
|
|
502
|
+
NT_LOGIN_COMMANDS.PASSWORD_LOGIN,
|
|
503
|
+
this.buildSsoNTLoginPasswordLoginPacket,
|
|
504
|
+
);
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
WtLogin.prototype.ntHasCaptcha = function () {
|
|
508
|
+
return !!this.nt_captcha;
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
WtLogin.prototype.ntSubmitCaptcha = function (ticket, randStr = "") {
|
|
512
|
+
if (!this.nt_captcha || !this.t106?.length) return;
|
|
513
|
+
this.nt_captcha.ticket = ticket;
|
|
514
|
+
this.nt_captcha.randStr = randStr;
|
|
515
|
+
return sendNTLoginWithBuilder(
|
|
516
|
+
this,
|
|
517
|
+
NT_LOGIN_COMMANDS.PASSWORD_LOGIN,
|
|
518
|
+
this.buildSsoNTLoginPasswordLoginPacket,
|
|
519
|
+
);
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* NT返回的token字段直接落到现有WtLogin字段:
|
|
524
|
+
* a1 -> t106,a1_key -> t10c,a2 -> sig.tgt,a2_key -> sig.tgt_key,
|
|
525
|
+
* d2 -> sig.d2,d2_key -> sig.d2key,no_pic_sig -> sig.srm_token。
|
|
526
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
527
|
+
* @param {any} payload
|
|
528
|
+
* @returns {boolean}
|
|
529
|
+
*/
|
|
530
|
+
function parseNTToken(wt, payload) {
|
|
531
|
+
const token = payload?.[1];
|
|
532
|
+
if (!token || typeof token !== "object") return false;
|
|
533
|
+
const a1 = toBuffer(token[3]);
|
|
534
|
+
const a1Key = toBuffer(token[14]);
|
|
535
|
+
const a2 = toBuffer(token[4]);
|
|
536
|
+
const a2Key = toBuffer(token[17]);
|
|
537
|
+
const d2 = toBuffer(token[5]);
|
|
538
|
+
const d2key = toBuffer(token[6]);
|
|
539
|
+
const noPicSig = toBuffer(token[13]);
|
|
540
|
+
if (a1.length) wt.readT106(a1);
|
|
541
|
+
if (a1Key.length) wt.readT10C(a1Key);
|
|
542
|
+
if (a2.length) wt.sig.tgt = a2;
|
|
543
|
+
if (a2Key.length) wt.sig.tgt_key = a2Key;
|
|
544
|
+
if (d2.length) wt.sig.d2 = d2;
|
|
545
|
+
if (d2key.length) wt.sig.d2key = d2key;
|
|
546
|
+
if (noPicSig.length) wt.sig.srm_token = noPicSig;
|
|
547
|
+
|
|
548
|
+
const uid = first(payload?.[4]?.[2]) || first(payload?.[2]?.[2]);
|
|
549
|
+
if (uid) wt.uid = toString(uid);
|
|
550
|
+
const user = payload?.[4]?.[3];
|
|
551
|
+
const nickname = user?.[1] ? toString(user[1]) : wt.c.nickname;
|
|
552
|
+
wt.nickname = nickname;
|
|
553
|
+
if (a1Key.length) {
|
|
554
|
+
wt.device.tgtgt = a1Key;
|
|
555
|
+
wt.t10c = a1Key;
|
|
556
|
+
} else if (d2key.length) {
|
|
557
|
+
wt.device.tgtgt = md5(d2key);
|
|
558
|
+
}
|
|
559
|
+
return true;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* @param {any} errorInfo
|
|
564
|
+
* @returns {string}
|
|
565
|
+
*/
|
|
566
|
+
function getNTErrorMessage(errorInfo) {
|
|
567
|
+
if (!errorInfo) return "NT 登录失败";
|
|
568
|
+
const title = errorInfo[2] ? toString(errorInfo[2]) : "登录失败";
|
|
569
|
+
const content = errorInfo[3] ? toString(errorInfo[3]) : "未知错误";
|
|
570
|
+
return `[${title}]${content}`;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* 解码 NT 登录返回并转换为现有 OICQ 登录事件。
|
|
575
|
+
* @param {import("../wtlogin/wt").WtLogin} wt
|
|
576
|
+
* @param {string} cmd
|
|
577
|
+
* @param {Buffer} payload
|
|
578
|
+
* @returns {Promise<any>}
|
|
579
|
+
*/
|
|
580
|
+
async function decodeNTLoginResponse(wt, cmd, payload) {
|
|
581
|
+
wt.NTLoginResponse = payload;
|
|
582
|
+
const proto = pb.decode(payload);
|
|
583
|
+
if (!proto) throw new Error("invalid NT login response");
|
|
584
|
+
const errorInfo = proto[1]?.[4];
|
|
585
|
+
const code = errorInfo ? toNumber(errorInfo[1]) : 0;
|
|
586
|
+
wt.nt_login_cookie = proto[1]?.[5]?.[1]
|
|
587
|
+
? toString(proto[1][5][1])
|
|
588
|
+
: null;
|
|
589
|
+
|
|
590
|
+
if (code === NTLoginErrorCode.Success) {
|
|
591
|
+
if (!parseNTToken(wt, proto[2]))
|
|
592
|
+
throw new Error("NT login response has no token");
|
|
593
|
+
if (wt.apk.appid === 16) {
|
|
594
|
+
wt.token_flag = true;
|
|
595
|
+
return wt.tokenLogin(wt.sig.d2);
|
|
596
|
+
}
|
|
597
|
+
return wt.c.emit("internal.login");
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (code === NTLoginErrorCode.Strict) {
|
|
601
|
+
const message = getNTErrorMessage(errorInfo);
|
|
602
|
+
let jump;
|
|
603
|
+
if (errorInfo?.[7]?.length && errorInfo[7][0]?.[2]) {
|
|
604
|
+
jump = {
|
|
605
|
+
word: toString(errorInfo[7][0][1]),
|
|
606
|
+
url: toString(errorInfo[7][0][2]),
|
|
607
|
+
};
|
|
608
|
+
} else if (errorInfo?.[5]) {
|
|
609
|
+
jump = {
|
|
610
|
+
word: toString(errorInfo[4]),
|
|
611
|
+
url: toString(errorInfo[5]),
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
if (jump?.url) {
|
|
615
|
+
wt.logger.mark(message);
|
|
616
|
+
wt.logger.mark("访问URL完成验证后调用login()可直接登录(需要提交设备信息)。");
|
|
617
|
+
wt.logger.mark(`${jump.word}: ${jump.url}`);
|
|
618
|
+
return wt.c.em("system.login.auth", {
|
|
619
|
+
url: jump.url,
|
|
620
|
+
device: wt.buildLoginAuthDevice(),
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
wt.logger.error(message);
|
|
624
|
+
return wt.c.em("system.login.error", { code: 237, message });
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (code === NTLoginErrorCode.ProofWater) {
|
|
628
|
+
const url = errorInfo?.[5]
|
|
629
|
+
? toString(errorInfo[5])
|
|
630
|
+
: proto[2]?.[2]?.[3]
|
|
631
|
+
? toString(proto[2][2][3])
|
|
632
|
+
: "";
|
|
633
|
+
if (url) {
|
|
634
|
+
wt.nt_captcha = { url };
|
|
635
|
+
wt.logger.mark(`收到滑动验证码,请访问以下地址完成滑动,并从网络响应中取出ticket和randstr参数以英文逗号拼接输入:${url}`);
|
|
636
|
+
return wt.c.em("system.login.slider", { url });
|
|
637
|
+
}
|
|
638
|
+
wt.logger.error("[登陆失败]未知格式的验证码。");
|
|
639
|
+
return wt.c.em("system.login.error", {
|
|
640
|
+
code: 2,
|
|
641
|
+
message: "[登陆失败]未知格式的验证码。",
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (code === NTLoginErrorCode.NewDevice) {
|
|
646
|
+
const url = errorInfo?.[5] ? toString(errorInfo[5]) : "";
|
|
647
|
+
wt.logger.mark("登录保护验证URL:" + url);
|
|
648
|
+
return wt.c.em("system.login.device", {
|
|
649
|
+
url,
|
|
650
|
+
phone: "",
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (code === NTLoginErrorCode.UnusualDevice) {
|
|
655
|
+
const detail = proto[2]?.[3];
|
|
656
|
+
if (detail?.[2]) {
|
|
657
|
+
wt.nt_unusual_device_check_sig = toBuffer(detail[2]);
|
|
658
|
+
}
|
|
659
|
+
const url = detail?.[3] ? toString(detail[3]) : "";
|
|
660
|
+
wt.logger.warn("[登陆失败]当前设备环境异常,请完成设备验证后重新登录");
|
|
661
|
+
if (url) wt.logger.mark("登录保护验证URL:" + url);
|
|
662
|
+
return wt.c.em("system.login.device", {
|
|
663
|
+
url,
|
|
664
|
+
phone: "",
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const message = getNTErrorMessage(errorInfo);
|
|
669
|
+
if (code === NTLoginErrorCode.NeedUpdate) {
|
|
670
|
+
wt.logger.warn(message + "(错误码:" + code + ")");
|
|
671
|
+
return wt.c.em("system.login.error", { code, message });
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (cmd === NT_LOGIN_COMMANDS.EASY_LOGIN || cmd === NT_LOGIN_COMMANDS.REFRESH_TICKET) {
|
|
675
|
+
await wt.deleteToken();
|
|
676
|
+
return wt.passwordLogin();
|
|
677
|
+
}
|
|
678
|
+
return wt.c.em("system.login.error", { code, message });
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
module.exports = {
|
|
682
|
+
NT_LOGIN_CMD_PREFIX,
|
|
683
|
+
NT_LOGIN_COMMANDS,
|
|
684
|
+
NTLoginErrorCode,
|
|
685
|
+
buildSsoKeyExchangePacket,
|
|
686
|
+
buildSsoNTLoginPasswordLoginPacket,
|
|
687
|
+
buildSsoNTLoginPasswordLoginUnusualDevicePacket,
|
|
688
|
+
buildSsoNTLoginEasyLoginPacket,
|
|
689
|
+
buildSsoNTLoginRefreshTicketPacket,
|
|
690
|
+
buildSsoNTLoginRefreshA2Packet,
|
|
691
|
+
};
|
package/lib/ref.d.ts
CHANGED
|
@@ -89,6 +89,15 @@ WtLogin.prototype.passwordLogin = async function () {
|
|
|
89
89
|
} catch {
|
|
90
90
|
this.token_flag = false;
|
|
91
91
|
}
|
|
92
|
+
if (this.apk.nt && !this.c.config.forceWt && this.password_md5) {
|
|
93
|
+
try {
|
|
94
|
+
return await this.ntPasswordLogin(this.password_md5);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
const message = e.message || "NT login failed";
|
|
97
|
+
this.logger.error(message);
|
|
98
|
+
return this.c.em("system.login.error", { code: -1, message });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
92
101
|
if (!this.password_md5)
|
|
93
102
|
return this.fetchQrcode().catch((e) => {
|
|
94
103
|
const message = e.message || "[qsign][HeadSign] 未知错误";
|
|
@@ -155,12 +164,15 @@ WtLogin.prototype.passwordLogin = async function () {
|
|
|
155
164
|
}
|
|
156
165
|
|
|
157
166
|
WtLogin.prototype.sliderLogin = async function (ticket) {
|
|
167
|
+
const [captchaTicket, randStr = ""] = String(ticket).trim().split(",", 2);
|
|
168
|
+
if (this.ntHasCaptcha()) {
|
|
169
|
+
return this.ntSubmitCaptcha(captchaTicket, randStr);
|
|
170
|
+
}
|
|
158
171
|
if (!this.t104)
|
|
159
172
|
return this.logger.warn("未收到滑动验证码或已过期,你不能调用sliderLogin函数。");
|
|
160
|
-
ticket = String(ticket).trim();
|
|
161
173
|
const t = this.tlvPacker;
|
|
162
174
|
const tlvs = [
|
|
163
|
-
t(0x193,
|
|
175
|
+
t(0x193, captchaTicket),
|
|
164
176
|
t(0x8),
|
|
165
177
|
t(0x104),
|
|
166
178
|
t(0x116),
|
package/lib/wtlogin/wt.js
CHANGED
|
@@ -57,6 +57,26 @@ class WtLogin {
|
|
|
57
57
|
this.c = c;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* 构造身份验证事件中的设备信息。
|
|
62
|
+
* @returns {object}
|
|
63
|
+
*/
|
|
64
|
+
buildLoginAuthDevice() {
|
|
65
|
+
const qimei = this.device.qimei36 || this.device.qimei16 || this.device.android_id;
|
|
66
|
+
return {
|
|
67
|
+
guid: this.device.guid.toString("hex"),
|
|
68
|
+
qimei,
|
|
69
|
+
qimei36: qimei,
|
|
70
|
+
subappid: String(this.apk.subid),
|
|
71
|
+
platform: this.apk.platform,
|
|
72
|
+
brand: this.device.brand,
|
|
73
|
+
model: this.device.model,
|
|
74
|
+
bssid: "",
|
|
75
|
+
devInfo: `${this.device.brand} ${this.device.model}`,
|
|
76
|
+
sysVersion: String(this.device.version.sdk),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
60
80
|
async sendLogin(cmd, body) {
|
|
61
81
|
const seq = this.c.nextSeq();
|
|
62
82
|
let pkt;
|
|
@@ -283,6 +303,29 @@ class WtLogin {
|
|
|
283
303
|
this.decodeT119(t[0x119], this.token_flag);
|
|
284
304
|
return this.c.emit("internal.login");
|
|
285
305
|
}
|
|
306
|
+
|
|
307
|
+
if (type === 237) {
|
|
308
|
+
const proto = pb.decode(t[0x543] || BUF0);
|
|
309
|
+
const info = proto?.[60];
|
|
310
|
+
if (info) {
|
|
311
|
+
const title = String(info[7] || "登录失败");
|
|
312
|
+
const content = String(info[8] || "未知错误,错误码:237");
|
|
313
|
+
const message = `[${title}]${content}`;
|
|
314
|
+
if (info[6] && info[6][3]) {
|
|
315
|
+
const jump = {
|
|
316
|
+
word: String(info[6][1]),
|
|
317
|
+
url: String(info[6][3]),
|
|
318
|
+
};
|
|
319
|
+
this.logger.mark(message);
|
|
320
|
+
this.logger.mark("访问URL完成验证后调用login()可直接登录(需要提交设备信息)。");
|
|
321
|
+
this.logger.mark(`${jump.word}: ${jump.url}`);
|
|
322
|
+
return this.c.em("system.login.auth", {
|
|
323
|
+
url: jump.url,
|
|
324
|
+
device: this.buildLoginAuthDevice(),
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
286
329
|
|
|
287
330
|
if (this.token_flag) {
|
|
288
331
|
this.logger.mark("token失效,重新login..");
|
|
@@ -575,9 +618,10 @@ class WtLogin {
|
|
|
575
618
|
* @param {Buffer} body
|
|
576
619
|
* @param {0|1|2} type 0心跳 1上线 2登录
|
|
577
620
|
* @param {number} seq
|
|
621
|
+
* @param {number} uin
|
|
578
622
|
* @returns {Promise<Buffer>}
|
|
579
623
|
*/
|
|
580
|
-
async _buildLoginPacket(cmd, body, type, seq = 0) {
|
|
624
|
+
async _buildLoginPacket(cmd, body, type, seq = 0, uin = this.uin) {
|
|
581
625
|
seq = seq ? seq : this.c.nextSeq();
|
|
582
626
|
const ntRegCmds = [
|
|
583
627
|
"trpc.msg.register_proxy.RegisterProxy.SsoInfoSync",
|
|
@@ -614,7 +658,7 @@ class WtLogin {
|
|
|
614
658
|
.writeU8(type)
|
|
615
659
|
.writeWithLength(this.sig.d2)
|
|
616
660
|
.writeU8(0)
|
|
617
|
-
.writeWithLength(String(
|
|
661
|
+
.writeWithLength(String(uin))
|
|
618
662
|
.writeBytes(sso)
|
|
619
663
|
.read();
|
|
620
664
|
return new Writer().writeWithLength(body).read();
|
|
@@ -717,5 +761,6 @@ module.exports = {
|
|
|
717
761
|
WtLogin, readTlv, BUF_UNKNOWN,
|
|
718
762
|
};
|
|
719
763
|
|
|
764
|
+
require("../ntlogin");
|
|
720
765
|
require("./login-password");
|
|
721
766
|
require("./login-qrcode");
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oicq-icalingua-plus-plus",
|
|
3
3
|
"app_name": "oicq-icalingua-plus-plus",
|
|
4
|
-
"version": "1.26.
|
|
5
|
-
"app_version": "1.26.
|
|
6
|
-
"upday": "2026/09/
|
|
4
|
+
"version": "1.26.26",
|
|
5
|
+
"app_version": "1.26.26",
|
|
6
|
+
"upday": "2026/09/23",
|
|
7
7
|
"protocol_version": "v11",
|
|
8
8
|
"description": "oicq(https://github.com/takayama-lily/oicq) v1 for Icalingua++",
|
|
9
9
|
"main": "index.js",
|