oicq-icalingua-plus-plus 1.26.6 → 1.26.8

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 CHANGED
@@ -56,6 +56,23 @@ export interface ConfBot {
56
56
 
57
57
  /** 使用NT上线包,部分消息暂未支持 */
58
58
  useNT?: boolean,
59
+
60
+ /** 指定APK信息,覆盖默认的协议参数 */
61
+ apk_info?: {
62
+ id?: string,
63
+ name?: string,
64
+ version?: string,
65
+ ver?: string,
66
+ sign?: Buffer,
67
+ buildtime?: number,
68
+ appid?: number,
69
+ subid?: number,
70
+ bitmap?: number,
71
+ sigmap?: number,
72
+ sdkver?: string,
73
+ ssover?: number,
74
+ qua?: string,
75
+ }
59
76
  }
60
77
 
61
78
  export interface Statistics {
@@ -1223,7 +1240,9 @@ export class Client extends EventEmitter {
1223
1240
 
1224
1241
  //----------以下为隐藏方法,可用于扩展协议----------\\
1225
1242
  /** 发送一个未加密的uni包 */
1226
- sendUni(cmd: string, body: Uint8Array): Promise<Buffer>;
1243
+ sendUni(cmd: string, body: Uint8Array, timeout?: number, options?: SsoOptions): Promise<Buffer>;
1244
+ /** 写入一个未加密的uni包 */
1245
+ writeUni(cmd: string, body: Buffer, seq?: number, options?: SsoOptions): Promise<void>;
1227
1246
  /** 发送一个未加密的oidb包 */
1228
1247
  sendOidb(cmd: string, body: Uint8Array): Promise<Buffer>;
1229
1248
  /** 发送一个未加密的OidbSvcTrpcTcp包 */
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const Readable = require("stream").Readable;
2
+ const Reader = require("../reader");
3
3
  class JceError extends Error { }
4
4
  const BUF0 = Buffer.alloc(0);
5
5
 
@@ -36,7 +36,7 @@ let _encoding = "utf8";
36
36
  class Struct extends null { }
37
37
 
38
38
  /**
39
- * @param {Readable} stream
39
+ * @param {Reader} stream
40
40
  */
41
41
  function readHead(stream) {
42
42
  const head = stream.read(1).readUInt8();
@@ -49,7 +49,7 @@ function readHead(stream) {
49
49
  }
50
50
 
51
51
  /**
52
- * @param {Readable} stream
52
+ * @param {Reader} stream
53
53
  * @param {number} type UInt8 0~13
54
54
  */
55
55
  function readBody(stream, type) {
@@ -108,7 +108,7 @@ function readBody(stream, type) {
108
108
  }
109
109
 
110
110
  /**
111
- * @param {Readable} stream
111
+ * @param {Reader} stream
112
112
  */
113
113
  function readStruct(stream) {
114
114
  const struct = Object.create(Struct.prototype);
@@ -123,7 +123,7 @@ function readStruct(stream) {
123
123
  }
124
124
 
125
125
  /**
126
- * @param {Readable} stream
126
+ * @param {Reader} stream
127
127
  */
128
128
  function readElement(stream) {
129
129
  const head = readHead(stream);
@@ -286,7 +286,7 @@ function setEncoding(encoding = "utf8") {
286
286
  * @returns {{[k: number]: any}}
287
287
  */
288
288
  function decode(blob) {
289
- const stream = Readable.from(blob, {objectMode: false});
289
+ const stream = new Reader(blob);
290
290
  stream.read(0);
291
291
  const result = Object.create(null);
292
292
  while(stream.readableLength) {
@@ -0,0 +1,81 @@
1
+ class Reader {
2
+ /**
3
+ * @param {Buffer | ArrayBuffer | Uint8Array} data
4
+ */
5
+ constructor(data) {
6
+ if (data instanceof Buffer) {
7
+ this._buf = data;
8
+ } else {
9
+ this._buf = Buffer.from(data);
10
+ }
11
+ this._offset = 0;
12
+ }
13
+
14
+ get readableLength() {
15
+ return Math.max(0, this._buf.length - this._offset);
16
+ }
17
+
18
+ read(n) {
19
+ if (arguments.length === 0) {
20
+ n = this.readableLength;
21
+ }
22
+ if (typeof n !== "number") {
23
+ throw new TypeError("n must be a number");
24
+ }
25
+ if (n <= 0 || this.readableLength === 0) {
26
+ return null;
27
+ }
28
+
29
+ const r = this._buf.subarray(this._offset, this._offset + n);
30
+ this._offset += n;
31
+ return r;
32
+ }
33
+
34
+ readU8() {
35
+ const value = this._buf.readUInt8(this._offset);
36
+ this._offset += 1;
37
+ return value;
38
+ }
39
+
40
+ readU16() {
41
+ const value = this._buf.readUInt16BE(this._offset);
42
+ this._offset += 2;
43
+ return value;
44
+ }
45
+
46
+ readU32() {
47
+ const value = this._buf.readUInt32BE(this._offset);
48
+ this._offset += 4;
49
+ return value;
50
+ }
51
+
52
+ read32() {
53
+ const value = this._buf.readInt32BE(this._offset);
54
+ this._offset += 4;
55
+ return value;
56
+ }
57
+
58
+ readU64() {
59
+ const value = this._buf.readBigUInt64BE(this._offset);
60
+ this._offset += 8;
61
+ return value;
62
+ }
63
+
64
+ readBytes(length) {
65
+ const value = Buffer.from(this._buf.slice(this._offset, this._offset + length));
66
+ this._offset += length;
67
+ return value;
68
+ }
69
+
70
+ readWithLength() {
71
+ const length = this.readU32() - 4;
72
+ return this.readBytes(length);
73
+ }
74
+
75
+ readTlv() {
76
+ const length = this.readU16();
77
+ return this.readBytes(length);
78
+ }
79
+ }
80
+
81
+ module.exports = Reader;
package/lib/client-ext.js CHANGED
@@ -68,9 +68,9 @@ Client.prototype.writeUni = async function (cmd, body, seq = 0, options = {}) {
68
68
  /**
69
69
  * 发送一个uni包并返回响应包
70
70
  */
71
- Client.prototype.sendUni = async function (cmd, body, timeout = 5) {
71
+ Client.prototype.sendUni = async function (cmd, body, timeout = 5, options = {}) {
72
72
  const seq = this.nextSeq();
73
- const packet = await this._buildUniPacket(cmd, body, seq);
73
+ const packet = await this._buildUniPacket(cmd, body, seq, options);
74
74
  return this.send(packet, timeout, seq);
75
75
  };
76
76
 
package/lib/client.js CHANGED
@@ -263,11 +263,15 @@ class Client extends EventEmitter {
263
263
  data_dir: path.join(require.main ? require.main.path : process.cwd(), "data"),
264
264
  ffmpeg_path: "ffmpeg",
265
265
  ffprobe_path: "ffprobe",
266
+ apk_info: {},
266
267
  ...config
267
268
  };
268
269
  this.config = config;
269
270
  this.dir = createDataDir(config.data_dir, uin);
270
271
  this.apk = getApkInfo(config.platform);
272
+ if (config.apk_info) {
273
+ Object.assign(this.apk, config.apk_info);
274
+ }
271
275
  if (this.config.useNT && this.apk.ssover < 20) this.apk.ssover = 20;
272
276
  this.logger = log4js.getLogger(`[${platforms[config.platform] ||
273
277
  ((config.platform % 2 === 1 ? "Android_" : "aPad_") + this.apk.ver)
@@ -85,13 +85,12 @@ async function initFL() {
85
85
  this.sync_finished = false;
86
86
  const tmp = this.fl;
87
87
  this.fl = new Map;
88
- let start = 0, limit = 150;
88
+ let start = 0, limit = 100;
89
89
  while (1) {
90
90
  try {
91
91
  const total = await _initFL.call(this, start, limit, tmp);
92
92
  start += limit;
93
- if (start + limit > total) limit = total - start;
94
- if (start >= total) break;
93
+ if (start > total) break;
95
94
  } catch (e) {
96
95
  this.logger.debug(e);
97
96
  this.logger.warn("加载好友列表出现异常,未加载完成。");
@@ -14,6 +14,7 @@ const deflate = util.promisify(zlib.deflate);
14
14
  const { getC2CMsgs, getGroupMsgs } = require("./history");
15
15
  const http = require("http");
16
16
  const https = require("https");
17
+ const { sleep } = require("../qsign-api");
17
18
  const BUF1 = Buffer.from([1]);
18
19
 
19
20
  function buildTextElem(str) {
@@ -193,8 +194,9 @@ async function _downloadMultiMsgNt(resid, bu) {
193
194
  * @param {boolean} dm
194
195
  * @param {number} target
195
196
  * @param {boolean} nt
197
+ * @param {number} retry
196
198
  */
197
- async function makeForwardMsg(iterable, dm = false, target = 0, nt = true) {
199
+ async function makeForwardMsg(iterable, dm = false, target = 0, nt = true, retry = 0) {
198
200
 
199
201
  if (typeof iterable[Symbol.iterator] !== "function")
200
202
  iterable = [iterable];
@@ -520,6 +522,11 @@ async function makeForwardMsg(iterable, dm = false, target = 0, nt = true) {
520
522
  this.logger.warn("failed to upload forward msg with NT protocol, try old protocol -> " + e.message);
521
523
  return makeForwardMsg.call(this, iterable, dm, target, false);
522
524
  }
525
+ if (retry < 3) {
526
+ this.logger.warn("failed to upload forward msg, retry -> " + e.message);
527
+ await sleep(1000);
528
+ return makeForwardMsg.call(this, iterable, dm, target, nt, retry + 1);
529
+ }
523
530
  throw new Error("failed to upload forward msg -> " + e.message);
524
531
  }
525
532
 
@@ -612,28 +612,18 @@ class Parser {
612
612
 
613
613
  data.file = buildImageFileParam(md5, size, width, height, imgType);
614
614
 
615
- if (ntPath && storeId === 1) {
615
+ if (ntPath) {
616
616
  data.url = `https://${elem[1][2][3]}${ntPath}&spec=0`;
617
617
  } else {
618
618
  data.url = `https://gchat.qpic.cn/gchatpic_new/0/0-0-${md5.toUpperCase()}/0`;
619
619
  }
620
- if (timestamp() - time > 60 * 60 || !(await checkImgUrl(data.url))) {
621
- // 图片地址失效,尝试发包获取
622
- let newURL = null;
623
- if (dm) { //私聊
624
- newURL = await getNTPicURL.call(this.c, imgInfo, String(this.uid), true);
625
- } else { //群聊
626
- newURL = await getNTPicURL.call(this.c, imgInfo, this.gid);
627
- }
628
- if (newURL) {
629
- data.url = newURL;
630
- }
620
+ if (storeId === 1 && fileid) {
621
+ data.url = await getNTPicURLbyFileid.call(this.c, fileid);
622
+ } else if (storeId === 0) {
623
+ data.url = `https://gchat.qpic.cn/gchatpic_new/0/0-0-${md5.toUpperCase()}/0`;
631
624
  }
632
625
  // QQNT 图片容错,某些 QQNT 的机器人会发出 appid 异常的图片
633
626
  data.url = data.url.replace("multimedia.nt.qq.com.cn", "gchat.qpic.cn"); //这个host下面似乎不检查rkey有效期
634
- if (!(await checkImgUrl(data.url))) {
635
- data.url = await getNTPicURLbyFileid.call(this.c, fileid);
636
- }
637
627
  this.isNTImg = true;
638
628
  return data;
639
629
  }
@@ -300,8 +300,10 @@ async function _uploadPtt(target, buf) {
300
300
  4: md5,
301
301
  5: md5.toString("hex") + ".amr",
302
302
  6: buf.length,
303
+ 8: 0,
303
304
  11: 1,
304
305
  18: fid,
306
+ 19: seconds,
305
307
  29: codec,
306
308
  30: Buffer.from([8, 0, 40, 0, 56, 0]),
307
309
  });
package/lib/oicq.js CHANGED
@@ -10,6 +10,7 @@ const { Client, STATUS_ONLINE, STATUS_OFFLINE, STATUS_PENDING } = require("./cli
10
10
  const tea = require("./algo/tea");
11
11
  const pb = require("./algo/pb");
12
12
  const jce = require("./algo/jce");
13
+ const Reader = require("./algo/reader");
13
14
  const { timestamp, BUF0, BUF16, unzip, log } = require("./common");
14
15
  const push = require("./onlinepush");
15
16
  const sysmsg = require("./core/sysmsg");
@@ -388,20 +389,19 @@ function pushDomainListener() {
388
389
  * @param {Buffer} buf
389
390
  */
390
391
  async function parseSSO(buf) {
392
+ const stream = new Reader(buf);
393
+ const header = new Reader(stream.readWithLength());
391
394
  const headlen = buf.readInt32BE();
392
- const seq = buf.readInt32BE(4);
393
- const retcode = buf.readInt32BE(8);
395
+ const seq = header.read(4).readInt32BE();
396
+ const retcode = header.read(4).readInt32BE();
394
397
  if (retcode !== 0) {
395
- const retmsg_len = buf.readUInt32BE(12);
396
- const retmsg = String(buf.slice(16, 16 + retmsg_len - 4));
398
+ const retmsg = String(header.readWithLength());
397
399
  const error = new Error("unsuccessful retcode: " + retcode + " retmsg: " + retmsg + " seq: " + seq);
398
400
  if (this.handlers.has(seq))
399
401
  this.handlers.get(seq)(null, error);
400
402
  throw error;
401
403
  }
402
- let offset = buf.readUInt32BE(12) + 12;
403
- let len = buf.readUInt32BE(offset); // length of cmd
404
- const cmd = String(buf.slice(offset + 4, offset + len));
404
+ const cmd = String(header.readWithLength());
405
405
 
406
406
  if (cmd === "Heartbeat.Alive") {
407
407
  return {
@@ -409,13 +409,9 @@ async function parseSSO(buf) {
409
409
  };
410
410
  }
411
411
 
412
- offset += len;
413
- len = buf.readUInt32BE(offset); // length of session_id
414
- offset += len;
415
- const flag = buf.readInt32BE(offset);
416
- offset += 4;
417
- len = buf.readUInt32BE(offset); // length of reserved
418
- const reserved = buf.slice(offset + 4, offset + len);
412
+ const session_id = header.readWithLength();
413
+ const flag = header.read(4).readInt32BE();
414
+ const reserved = header.readWithLength();
419
415
  let payload;
420
416
  if (flag === 0) {
421
417
  payload = buf.slice(headlen + 4);
@@ -502,7 +498,7 @@ async function packetListener(pkt) {
502
498
  if (this.handlers.has(sso.seq))
503
499
  return this.handlers.get(sso.seq)(sso.payload);
504
500
  if (Reflect.has(listeners, sso.cmd))
505
- listeners[sso.cmd].call(this, sso.payload, sso.seq);
501
+ listeners[sso.cmd].call(this, sso.payload, sso.seq, sso);
506
502
  this.emit("internal.sso", sso);
507
503
  } catch (e) {
508
504
  ++this.stat.sso_error;
package/lib/onlinepush.js CHANGED
@@ -7,7 +7,7 @@ const pb = require("./algo/pb");
7
7
  const jce = require("./algo/jce");
8
8
  const sysmsg = require("./core/sysmsg");
9
9
  const { parseC2CMsg ,parseGroupMsg, parseDiscussMsg } = require("./message/parser");
10
- const { genC2CMessageId, genGroupMessageId, log, uin2code } = require("./common");
10
+ const { genC2CMessageId, genGroupMessageId, log, uin2code, BUF0 } = require("./common");
11
11
  const { updateFL, uid2uin } = require("./core/friendlist");
12
12
 
13
13
  /**
@@ -766,7 +766,7 @@ function rebuildFragments(msg) {
766
766
  /**
767
767
  * @this {import("./ref").Client}
768
768
  */
769
- async function NTMsgListener(blob, seq) {
769
+ async function NTMsgListener(blob, seq, sso) {
770
770
  const o = pb.decode(blob);
771
771
  const type = o[1][2] ? o[1][2][1] : 114514;
772
772
  //this.logger.debug("NT message type", type);
@@ -845,11 +845,6 @@ async function NTMsgListener(blob, seq) {
845
845
  } catch (e) {
846
846
  this.logger.debug(e);
847
847
  }
848
- if (o[4]) {
849
- this.writeUni("trpc.msg.olpush.OlPushService.SsoPushAck", pb.encode({
850
- 1: o[4],
851
- }));
852
- }
853
848
  break;
854
849
  case 732: //push732
855
850
  try {
@@ -864,11 +859,6 @@ async function NTMsgListener(blob, seq) {
864
859
  } catch (e) {
865
860
  this.logger.debug(e);
866
861
  }
867
- if (o[4]) {
868
- this.writeUni("trpc.msg.olpush.OlPushService.SsoPushAck", pb.encode({
869
- 1: o[4],
870
- }));
871
- }
872
862
  break;
873
863
  case 114514: // 1919810
874
864
  // 部分消息没有content
@@ -877,6 +867,20 @@ async function NTMsgListener(blob, seq) {
877
867
  this.logger.warn("Unknown NT message type", type);
878
868
  break;
879
869
  }
870
+ if (o[4]) {
871
+ let trans_info = BUF0;
872
+ try {
873
+ const trans_info = pb.decode(sso.reserved)[23] || BUF0;
874
+ } catch (e) {
875
+ this.logger.debug(e);
876
+ }
877
+ this.writeUni("trpc.msg.olpush.OlPushService.SsoPushAck", pb.encode({
878
+ 1: o[4],
879
+ }), undefined, {
880
+ trans_info: trans_info,
881
+ nt_core: true,
882
+ });
883
+ }
880
884
  }
881
885
 
882
886
  /**
package/lib/ref.d.ts CHANGED
@@ -183,7 +183,8 @@ export class Client extends oicq.Client {
183
183
 
184
184
  nextSeq(): number;
185
185
  send(): Promise<Buffer>;
186
- writeUni(cmd: string, body: Buffer, seq?: number): Promise<void>;
186
+ sendUni(cmd: string, body: Uint8Array, timeout?: number, options?: SsoOptions): Promise<Buffer>;
187
+ writeUni(cmd: string, body: Buffer, seq?: number, options?: SsoOptions): Promise<void>;
187
188
  msgExists(from: number, type: number, seq: number, time: number): boolean;
188
189
  buildSyncCookie(): Buffer;
189
190
  parseEventType(name: string): oicq.CommonEventData;
package/lib/service.js CHANGED
@@ -187,9 +187,9 @@ function highwayHttpUpload(readable, obj) {
187
187
  const limit = 524288
188
188
  obj.ticket = this.storage.sig_session
189
189
 
190
+ const MAX_QUEUED = 3 // 队列中最多保留的分块数,控制内存上限(3×512KB≈1.5MB)
190
191
  let seq = 1
191
192
  let offset = 0
192
- let totalChunks = 0
193
193
  let readEnded = false
194
194
 
195
195
  // 顺序队列:每读到一个完整分块就推入,drain 循环逐个串行上传
@@ -230,37 +230,40 @@ function highwayHttpUpload(readable, obj) {
230
230
  return Buffer.concat([header, head, chunk, __])
231
231
  }
232
232
 
233
- // 读取流 → 分块 → 入队
233
+ // 读取流 → 分块 → 入队(带背压:队列满时暂停读取)
234
234
  readable.on("data", data => {
235
235
  if (errFlag || quickDone) return
236
236
  let _offset = 0
237
237
  while (_offset < data.length) {
238
238
  const chunk = data.slice(_offset, limit + _offset)
239
239
  _offset += chunk.length
240
- ++totalChunks
241
240
  const buf = buildBuf(chunk, offset + _offset - chunk.length)
242
- queue.push(buf)
241
+ queue.push({ buf, chunkLen: chunk.length })
243
242
  if (notify) { notify(); notify = null }
244
243
  }
245
244
  offset += data.length
245
+ // 队列积压过多,暂停读取,等上传消化后再恢复
246
+ if (queue.length >= MAX_QUEUED) {
247
+ readable.pause()
248
+ }
246
249
  })
247
250
 
248
251
  readable.on("err", err => { errFlag = err })
249
252
 
250
253
  // 串行 drain 循环:逐个从队列取分块上传
251
254
  const drain = async () => {
252
- let finished = 0
255
+ let uploadedBytes = 0
253
256
  while (true) {
254
257
  while (queue.length === 0) {
255
258
  if (errFlag) return
256
259
  if (readEnded) {
257
260
  // 流已结束且队列为空,检查是否全部完成
258
- if (finished >= totalChunks) return
261
+ if (uploadedBytes >= obj.size) return
259
262
  }
260
263
  // 等待新分块入队
261
264
  await new Promise(r => { notify = r })
262
265
  }
263
- const buf = queue.shift()
266
+ const { buf, chunkLen } = queue.shift()
264
267
  const r = await axios.post(url, buf, {
265
268
  responseType: "arraybuffer",
266
269
  httpAgent: agent,
@@ -280,8 +283,8 @@ function highwayHttpUpload(readable, obj) {
280
283
  if (rsp?.[3] !== 0) {
281
284
  throw new Error(`${rsp[3]}, unknown highway error`)
282
285
  }
283
- ++finished
284
- const percentage = (finished / totalChunks * 100).toFixed(2)
286
+ uploadedBytes += chunkLen
287
+ const percentage = (uploadedBytes / obj.size * 100).toFixed(2)
285
288
  this.logger.debug(`highway(http) chunk uploaded (${percentage}%)`)
286
289
  if (typeof obj.callback === "function")
287
290
  obj.callback(percentage)
@@ -294,6 +297,10 @@ function highwayHttpUpload(readable, obj) {
294
297
  obj.callback("100.00")
295
298
  return
296
299
  }
300
+ // 队列消化后恢复读取
301
+ if (queue.length < MAX_QUEUED && readable.isPaused()) {
302
+ readable.resume()
303
+ }
297
304
  }
298
305
  }
299
306
 
@@ -16,7 +16,7 @@ const Writer = require("./writer");
16
16
  const { md5, NOOP, timestamp, BUF0 } = require("../common");
17
17
  const tea = require("../algo/tea");
18
18
  const Ecdh = require("./ecdh");
19
- const Readable = require("stream").Readable;
19
+ const Reader = require("../algo/reader");
20
20
  const { getQIMEI } = require("../qimei");
21
21
 
22
22
  WtLogin.prototype.passwordLogin = async function () {
@@ -66,15 +66,15 @@ WtLogin.prototype.passwordLogin = async function () {
66
66
  this.logger.warn("你的登录缓存有缺失或者是旧版创建的,将使用旧版刷新包。");
67
67
  }
68
68
  const token = await fs.promises.readFile(path.join(this.dir, "token"));
69
- const stream = Readable.from(token, { objectMode: false });
70
- const d2key = stream.read(stream.read(2).readUInt16BE());
71
- const d2 = stream.read(stream.read(2).readUInt16BE());
72
- const ticket = stream.read(stream.read(2).readUInt16BE());
73
- const sig = stream.read(stream.read(2).readUInt16BE());
74
- const srm = stream.read(stream.read(2).readUInt16BE());
75
- const tgt = stream.read(stream.read(2).readUInt16BE());
76
- this.sig.device_token = stream.read(stream.read(2).readUInt16BE());
77
- this.sig._ksid = stream.read(stream.read(2).readUInt16BE()) || BUF0;
69
+ const stream = new Reader(token);
70
+ const d2key = stream.readTlv();
71
+ const d2 = stream.readTlv();
72
+ const ticket = stream.readTlv();
73
+ const sig = stream.readTlv();
74
+ const srm = stream.readTlv();
75
+ const tgt = stream.readTlv();
76
+ this.sig.device_token = stream.readTlv();
77
+ this.sig._ksid = stream.readTlv() || BUF0;
78
78
  if (d2key.length && d2.length && ticket.length && sig.length && srm.length && tgt.length) {
79
79
  this.sig.d2 = d2;
80
80
  this.sig.d2key = d2key;
@@ -364,9 +364,9 @@ WtLogin.prototype.refreshD2 = async function (forceEmp11 = false) {
364
364
  try {
365
365
  let payload = await this.c.send(pkt, undefined, seq);
366
366
  payload = tea.decrypt(payload.slice(16, payload.length - 1), this.ecdh.share_key);
367
- const stream = Readable.from(payload, { objectMode: false });
367
+ const stream = new Reader(payload);
368
368
  stream.read(2);
369
- const type = stream.read(1).readUInt8();
369
+ const type = stream.readU8();
370
370
  stream.read(2);
371
371
  const t = readTlv(stream);
372
372
  if (type === 0)
@@ -495,9 +495,9 @@ WtLogin.prototype.refreshD2WithT10C = async function () {
495
495
  try {
496
496
  let payload = await this.c.send(pkt, undefined, seq);
497
497
  payload = tea.decrypt(payload.slice(16, payload.length - 1), this.ecdh.share_key);
498
- const stream = Readable.from(payload, { objectMode: false });
498
+ const stream = new Reader(payload);
499
499
  stream.read(2);
500
- const type = stream.read(1).readUInt8();
500
+ const type = stream.readU8();
501
501
  stream.read(2);
502
502
  const t = readTlv(stream);
503
503
  if (type === 0)
@@ -9,7 +9,7 @@
9
9
  "use strict";
10
10
  const fs = require("fs");
11
11
  const path = require("path");
12
- const Readable = require("stream").Readable;
12
+ const Reader = require("../algo/reader");
13
13
  const jsqr = require("jsqr");
14
14
  const { PNG } = require("pngjs");
15
15
  const qrt = require("qrcode-terminal");
@@ -127,7 +127,7 @@ WtLogin.prototype.fetchQrcode = async function () {
127
127
  try {
128
128
  let payload = await this.c.send(pkt, undefined, seq);
129
129
  payload = tea.decrypt(payload.slice(16, -1), this.ecdh.share_key);
130
- const stream = Readable.from(payload, { objectMode: false });
130
+ const stream = new Reader(payload);
131
131
  stream.read(54);
132
132
  const retcode = stream.read(1)[0];
133
133
  const sig = stream.read(stream.read(2).readUInt16BE());
@@ -177,7 +177,7 @@ WtLogin.prototype.qrcodeLogin = async function () {
177
177
  try {
178
178
  let payload = await this.c.send(pkt, undefined, seq);
179
179
  payload = tea.decrypt(payload.slice(16, -1), this.ecdh.share_key);
180
- const stream = Readable.from(payload, { objectMode: false });
180
+ const stream = new Reader(payload);
181
181
  stream.read(48);
182
182
  let len = stream.read(2).readUInt16BE();
183
183
  if (len > 0) {
package/lib/wtlogin/wt.js CHANGED
@@ -2,7 +2,7 @@
2
2
  const fs = require("fs");
3
3
  const path = require("path");
4
4
  const { randomBytes, createHash } = require("crypto");
5
- const Readable = require("stream").Readable;
5
+ const Reader = require("../algo/reader");
6
6
  const tea = require("../algo/tea");
7
7
  const jce = require("../algo/jce");
8
8
  const pb = require("../algo/pb");
@@ -84,7 +84,7 @@ class WtLogin {
84
84
  * @param {Buffer} data
85
85
  */
86
86
  decodeT119(data, token = false) {
87
- const reader = Readable.from(tea.decrypt(data, this.device.tgtgt), { objectMode: false });
87
+ const reader = new Reader(tea.decrypt(data, this.device.tgtgt), { objectMode: false });
88
88
  reader.read(2);
89
89
  const t = readTlv(reader);
90
90
  this.readT106(t[0x106]);
@@ -170,7 +170,7 @@ class WtLogin {
170
170
  }
171
171
  readT11A(data) {
172
172
  if (!data) return;
173
- const stream = Readable.from(data, { objectMode: false });
173
+ const stream = new Reader(data, { objectMode: false });
174
174
  stream.read(2);
175
175
  this.age = stream.read(1).readUInt8();
176
176
  this.sex = ["unknown", "male", "female"][stream.read(1).readUInt8()];
@@ -179,7 +179,7 @@ class WtLogin {
179
179
  }
180
180
  readT512(data) {
181
181
  if (!data) return;
182
- const stream = Readable.from(data, { objectMode: false });
182
+ const stream = new Reader(data);
183
183
  let len = stream.read(2).readUInt16BE();
184
184
  while (len-- > 0) {
185
185
  const domain = String(stream.read(stream.read(2).readUInt16BE()));
@@ -191,7 +191,7 @@ class WtLogin {
191
191
 
192
192
  calcPoW(data) {
193
193
  if (!data || data.length === 0) return Buffer.alloc(0);
194
- const stream = Readable.from(data, { objectMode: false });
194
+ const stream = new Reader(data);
195
195
  const version = stream.read(1).readUInt8();
196
196
  const typ = stream.read(1).readUInt8();
197
197
  const hashType = stream.read(1).readUInt8();
@@ -262,7 +262,7 @@ class WtLogin {
262
262
 
263
263
  _decodeLoginResponse(payload) {
264
264
  payload = tea.decrypt(payload.slice(16, payload.length - 1), this.ecdh.share_key);
265
- const stream = Readable.from(payload, { objectMode: false });
265
+ const stream = new Reader(payload);
266
266
  stream.read(2);
267
267
  const type = stream.read(1).readUInt8();
268
268
  stream.read(2);
@@ -287,7 +287,7 @@ class WtLogin {
287
287
  if (this.token_flag) {
288
288
  this.logger.mark("token失效,重新login..");
289
289
  if (t[0x146]) {
290
- const stream = Readable.from(t[0x146], { objectMode: false });
290
+ const stream = new Reader(t[0x146]);
291
291
  const version = stream.read(4);
292
292
  const title = stream.read(stream.read(2).readUInt16BE()).toString();
293
293
  const content = stream.read(stream.read(2).readUInt16BE()).toString();
@@ -333,7 +333,7 @@ class WtLogin {
333
333
  }
334
334
 
335
335
  if (t[0x149]) {
336
- const stream = Readable.from(t[0x149], { objectMode: false });
336
+ const stream = new Reader(t[0x149]);
337
337
  stream.read(2);
338
338
  const title = stream.read(stream.read(2).readUInt16BE()).toString();
339
339
  const content = stream.read(stream.read(2).readUInt16BE()).toString();
@@ -343,7 +343,7 @@ class WtLogin {
343
343
  }
344
344
 
345
345
  if (t[0x146]) {
346
- const stream = Readable.from(t[0x146], { objectMode: false });
346
+ const stream = new Reader(t[0x146]);
347
347
  const version = stream.read(4);
348
348
  const title = stream.read(stream.read(2).readUInt16BE()).toString();
349
349
  const content = stream.read(stream.read(2).readUInt16BE()).toString();
@@ -437,7 +437,7 @@ class WtLogin {
437
437
  const seq = this.c.nextSeq();
438
438
  if (logout) {
439
439
  const payload = pb.encode({
440
- 1: 0,
440
+ 1: 0, // reg type
441
441
  2: {
442
442
  1: d.brand + '-' + d.model,
443
443
  2: d.device,
@@ -453,7 +453,8 @@ class WtLogin {
453
453
  const rsp = pb.decode(blob);
454
454
  const result = rsp[7] ? String(rsp[7][2]) === 'unregister success' : false;
455
455
  return result;
456
- } catch {
456
+ } catch (e) {
457
+ this.logger.error(`下线异常:${e.message}`);
457
458
  return false;
458
459
  }
459
460
  }
@@ -484,9 +485,9 @@ class WtLogin {
484
485
  },
485
486
  9: {
486
487
  1: d.guid.toString("hex"),
487
- 2: 0,
488
- 3: String(this.apk.version.split(".")[3]),
489
- 4: 0,
488
+ 2: 0, // kickPc
489
+ 3: String(this.apk.version.split(".")[3] || "14110"), // buildVer
490
+ 4: 0, // first
490
491
  5: 2052,
491
492
  6: {
492
493
  1: d.brand + '-' + d.model,
@@ -496,8 +497,8 @@ class WtLogin {
496
497
  5: `${d.product}-user ${d.version.release} ${d.display} ${d.version.incremental} release-keys`
497
498
  },
498
499
  7: 0,
499
- 8: 5,
500
- 9: 1,
500
+ 8: 6, // vendor type
501
+ 9: 1, // reg type
501
502
  10: {
502
503
  1: 1,
503
504
  2: 1,
@@ -519,10 +520,13 @@ class WtLogin {
519
520
  const blob = await this.c.send(pkt, undefined, seq);
520
521
  const rsp = pb.decode(blob);
521
522
  const result = rsp[7] ? String(rsp[7][2]) === 'register success' : false;
522
- if (!result)
523
+ if (!result) {
524
+ this.logger.error(`上线失败:${rsp[7] ? String(rsp[7][2]) : "未知错误"}`);
523
525
  await this.deleteToken();
526
+ }
524
527
  return result;
525
- } catch {
528
+ } catch (e) {
529
+ this.logger.error(`上线异常:${e.message}`);
526
530
  return false;
527
531
  }
528
532
  }
@@ -682,7 +686,7 @@ class WtLogin {
682
686
  try {
683
687
  let payload = await this.c.sendUni("wtlogin.exchange_emp", pkt);
684
688
  payload = tea.decrypt(payload.slice(16, payload.length - 1), this.sig.ticket_key);
685
- const stream = Readable.from(payload, { objectMode: false });
689
+ const stream = new Reader(payload);
686
690
  stream.read(5);
687
691
  const t = readTlv(stream);
688
692
  if (t[0x119])
@@ -697,7 +701,7 @@ class WtLogin {
697
701
  }
698
702
 
699
703
  /**
700
- * @param {Readable} stream
704
+ * @param {Reader} stream
701
705
  * @returns {{[k: number]: Buffer}}
702
706
  */
703
707
  function readTlv(stream) {
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.6",
5
- "app_version": "1.26.6",
6
- "upday": "2026/07/18",
4
+ "version": "1.26.8",
5
+ "app_version": "1.26.8",
6
+ "upday": "2026/07/19",
7
7
  "protocol_version": "v11",
8
8
  "description": "oicq(https://github.com/takayama-lily/oicq) v1 for Icalingua++",
9
9
  "main": "index.js",