oicq-icalingua-plus-plus 1.26.1 → 1.26.3

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
@@ -1165,6 +1165,9 @@ export class Client extends EventEmitter {
1165
1165
  /** 踢出已登录的设备 */
1166
1166
  delDevLoginInfo(flag: string): Promise<Ret>;
1167
1167
 
1168
+ /** 根据fileid获取私聊文件URL */
1169
+ getC2CFileUrl(fileid: string): Promise<Ret<string>>;
1170
+
1168
1171
  /** 清除 image 和 record 文件夹下的缓存文件 */
1169
1172
  cleanCache(type?: "image" | "record"): Promise<Ret>;
1170
1173
 
package/lib/client-ext.js CHANGED
@@ -11,19 +11,7 @@ const { timestamp, uin2code, BUF16, BUF0, log } = require("./common");
11
11
  const { TimeoutError } = require("./exception");
12
12
  const { parseC2CMsg } = require("./message/parser");
13
13
  const Writer = require("./wtlogin/writer");
14
- const _axios = require("axios");
15
- const http = require("http");
16
- const https = require("https");
17
- const API_WAIT_TIME = 45000;
18
- const axios = _axios.create({
19
- httpAgent: new http.Agent({ keepAlive: true }),
20
- httpsAgent: new https.Agent({ keepAlive: true }),
21
- timeout: API_WAIT_TIME,
22
- headers: {
23
- 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.5249.199 Safari/537.36 OICQ/1.23 ILPP/2",
24
- 'Content-Type': "application/x-www-form-urlencoded"
25
- }
26
- });
14
+ const { buildUrl, getCommonParams, qsignGet, qsignPost, executeWithRetry, sleep } = require("./qsign-api");
27
15
 
28
16
  /**
29
17
  * seqid递增并返回
@@ -377,10 +365,6 @@ Client.prototype.pbGetMsg = async function (force = false, flag = 0, count = 10)
377
365
  }
378
366
  };
379
367
 
380
- function sleep(time) {
381
- return new Promise(resolve => setTimeout(resolve, time));
382
- }
383
-
384
368
  class Lock {
385
369
  constructor() {
386
370
  this.queue = [];
@@ -409,37 +393,22 @@ const requestLock = new Lock();
409
393
 
410
394
  /**
411
395
  * 注册qsign
396
+ * @param {boolean} [lockAcquired=false] 调用者是否已持有requestLock
412
397
  */
413
- Client.prototype.registerQSign = async function(retry = 0) {
398
+ Client.prototype.registerQSign = async function(lockAcquired = false) {
414
399
  if (!this.config.sign_api_addr) return BUF0;
415
- let url = this.config.sign_api_addr;
416
- if (url[url.length - 1] !== '/') {
417
- url += '/';
418
- }
419
- url += 'register';
420
- const params = {
421
- ver: this.apk.ver,
422
- android_id: this.device.android_id,
423
- qimei36: this.device.qimei36 || this.device.qimei16 || this.device.android_id,
424
- guid: this.device.guid.toString('hex'),
425
- uin: this.uin,
426
- key: this.config.sign_api_key,
427
- };
428
- await requestLock.acquire();
429
- const { data } = await axios.get(url,
430
- {
431
- params,
432
- }
433
- ).catch((e) => ({ data: { code: -1, msg: e ? e.message : 'failed to connect to qsign api' } }));
434
- requestLock.release();
435
- if (data.code !== 0) {
436
- if (retry < 3) {
437
- this.logger.warn(`[qsign][register] ${data.msg}(${data.code}), retry in 1s...`);
438
- await sleep(1000);
439
- return await this.registerQSign(retry + 1);
440
- }
441
- data.msg = data.msg || 'unknown error';
442
- throw new Error(`[qsign][register] ${data.msg}(${data.code})`);
400
+ const url = buildUrl(this.config.sign_api_addr, 'register');
401
+ const params = getCommonParams.call(this);
402
+ if (!lockAcquired) await requestLock.acquire();
403
+ try {
404
+ const data = await executeWithRetry(
405
+ () => qsignGet(url, params),
406
+ 'register',
407
+ { throwOnError: true, logger: this.logger }
408
+ );
409
+ return data;
410
+ } finally {
411
+ if (!lockAcquired) requestLock.release();
443
412
  }
444
413
  };
445
414
 
@@ -469,89 +438,50 @@ Client.prototype.handleSsoCallback = async function(callbackList = []) {
469
438
  * @param {number} callbackId
470
439
  * @param {Buffer} body
471
440
  */
472
- Client.prototype.submitSsoCallback = async function(cmd, callbackId, body, retry = 0) {
473
- let url = this.config.sign_api_addr;
474
- if (url[url.length - 1] !== '/') {
475
- url += '/';
476
- }
477
- url += 'submit';
478
- const params = {
479
- ver: this.apk.ver,
480
- android_id: this.device.android_id,
481
- androidId: this.device.android_id,
482
- qimei36: this.device.qimei36 || this.device.qimei16 || this.device.android_id,
483
- guid: this.device.guid.toString('hex'),
484
- uin: this.uin,
441
+ Client.prototype.submitSsoCallback = async function(cmd, callbackId, body) {
442
+ const url = buildUrl(this.config.sign_api_addr, 'submit');
443
+ const params = getCommonParams.call(this, {
485
444
  buffer: body.toString('hex'),
486
445
  cmd: cmd,
487
446
  callback_id: callbackId,
488
447
  callbackId: callbackId,
489
- qua: this.apk.qua,
490
- key: this.config.sign_api_key
491
- };
492
- const { data } = await axios.get(url,
493
- {
494
- params,
495
- }
496
- ).catch((e) => ({ data: { code: -1, msg: e ? e.message : 'failed to connect to qsign api' } }));
497
- if (data.code !== 0) {
498
- if (retry < 3) {
499
- this.logger.warn(`[qsign][submitSso] ${data.msg}(${data.code}), retry in 1s...`);
500
- await sleep(1000);
501
- return await this.submitSsoCallback(cmd, callbackId, body, retry + 1);
502
- }
503
- data.msg = data.msg || 'unknown error';
504
- this.logger.warn(`[qsign][submitSso] ${data.msg}(${data.code})`);
505
- }
448
+ });
449
+ await executeWithRetry(
450
+ () => qsignGet(url, params),
451
+ 'submitSso',
452
+ { throwOnError: false, logger: this.logger }
453
+ );
506
454
  }
507
455
 
508
456
  /**
509
457
  * 刷新qsign的token
510
458
  */
511
- Client.prototype.refreshQSignToken = async function(retry = 0) {
459
+ Client.prototype.refreshQSignToken = async function() {
512
460
  if (!this.config.sign_api_addr) return;
513
461
  if (unsendSso.length > 0) {
514
462
  await this.handleSsoCallback(unsendSso.splice(0, unsendSso.length));
515
463
  }
516
- let url = this.config.sign_api_addr;
517
- if (url[url.length - 1] !== '/') {
518
- url += '/';
519
- }
520
- url += 'request_token';
521
- const params = {
522
- ver: this.apk.ver,
523
- uin: this.uin,
524
- android_id: this.device.android_id,
525
- androidId: this.device.android_id,
526
- qimei36: this.device.qimei36 || this.device.qimei16 || this.device.android_id,
527
- guid: this.device.guid.toString('hex'),
528
- qua: this.apk.qua,
529
- };
530
- const { data } = await axios.get(url,
464
+ const url = buildUrl(this.config.sign_api_addr, 'request_token');
465
+ const params = getCommonParams.call(this);
466
+ const data = await executeWithRetry(
467
+ () => qsignGet(url, params),
468
+ 'refreshToken',
531
469
  {
532
- params,
533
- }
534
- ).catch((e) => ({ data: { code: -1, msg: e ? e.message : 'failed to connect to qsign api' } }));
535
- if (data.code !== 0) {
536
- if (data.code === 1 && String(data.msg).includes('not registered')) {
537
- try {
538
- await this.registerQSign();
539
- } catch (e) {
540
- this.logger.warn(`${e.message || '[qsign][register] unknown error'}`);
541
- console.warn(e);
542
- }
543
- await sleep(100);
544
- return await this.refreshQSignToken(retry);
545
- }
546
- if (retry < 3) {
547
- this.logger.warn(`[qsign][refreshToken] ${data.msg}(${data.code}), retry in 1s...`);
548
- await sleep(1000);
549
- return await this.refreshQSignToken(retry + 1);
470
+ throwOnError: false,
471
+ handleNotRegistered: true,
472
+ onNotRegistered: async () => {
473
+ try {
474
+ await this.registerQSign();
475
+ } catch (e) {
476
+ this.logger.warn(`${e.message || '[qsign][register] unknown error'}`);
477
+ console.warn(e);
478
+ }
479
+ await sleep(100);
480
+ },
481
+ logger: this.logger,
550
482
  }
551
- data.msg = data.msg || 'unknown error';
552
- this.logger.warn(`[qsign][refreshToken] ${data.msg}(${data.code})`);
553
- }
554
- if (data.data){
483
+ );
484
+ if (data.data) {
555
485
  const pkts = data.data.ssoPacketList || data.data.requestCallback || data.data;
556
486
  if (Array.isArray(pkts)) {
557
487
  await this.handleSsoCallback(pkts);
@@ -575,7 +505,7 @@ Client.prototype.buildSSOReserveField = async function(cmd, body, seq_id = this.
575
505
  12: qimei, //qimei (string)
576
506
  14: 0, //newconn_flag (uint32)
577
507
  15: null, //trace_parent (string)
578
- 16: String(this.uin), //uid (string)
508
+ 16: this.uid ? String(this.uid) : "", //uid (string)
579
509
  18: 0, //imsi (uint32)
580
510
  19: 1, //network_type (uint32)
581
511
  20: 1, //ip_stack_type (uint32)
@@ -596,7 +526,6 @@ Client.prototype.buildSSOReserveField = async function(cmd, body, seq_id = this.
596
526
  }
597
527
  }
598
528
  if (options.nt_core) { //此时数据包内需用uid取代uin
599
- reserveFields[16] = String(this.uid);
600
529
  reserveFields[21] |= 32;
601
530
  reserveFields[26] = 100;
602
531
  }
@@ -611,117 +540,88 @@ Client.prototype.buildSSOReserveField = async function(cmd, body, seq_id = this.
611
540
  * @param {string} cmd
612
541
  * @param {Buffer} body
613
542
  * @param {number} seq_id
614
- * @param {number} retry 重试次数
615
543
  */
616
- Client.prototype.getSign = async function(cmd, body, seq_id = this.seq_id, retry = 0) {
544
+ Client.prototype.getSign = async function(cmd, body, seq_id = this.seq_id) {
617
545
  if (!this.config.sign_api_addr || !this.apk.qua) return BUF0;
618
- let url = this.config.sign_api_addr;
619
- if (url[url.length - 1] !== '/') {
620
- url += '/';
621
- }
622
- url += 'sign';
546
+ const url = buildUrl(this.config.sign_api_addr, 'sign');
547
+ const params = getCommonParams.call(this, {
548
+ buffer: body.toString('hex'),
549
+ cmd: cmd,
550
+ seq: seq_id,
551
+ });
623
552
  await requestLock.acquire();
624
- const { data } = await axios.post(url,
625
- {
626
- ver: this.apk.ver,
627
- androidId: this.device.android_id,
628
- android_id: this.device.android_id,
629
- qimei36: this.device.qimei36 || this.device.qimei16 || this.device.android_id,
630
- guid: this.device.guid.toString('hex'),
631
- uin: this.uin,
632
- buffer: body.toString('hex'),
633
- cmd: cmd,
634
- seq: seq_id,
635
- qua: this.apk.qua,
636
- key: this.config.sign_api_key
637
- },
638
- ).catch((e) => ({ data: { code: -1, msg: e ? e.message : 'failed to connect to qsign api' } }));
639
- requestLock.release();
640
- if (data.code !== 0) {
641
- if (data.code === 1 && String(data.msg).includes('not registered')) {
642
- await this.registerQSign();
643
- await sleep(1000);
644
- return await this.getSign(cmd, body, seq_id, retry);
645
- }
646
- if (retry < 3) {
647
- this.logger.warn(`[qsign][HeadSign] ${data.msg}(${data.code}), retry in 1s...`);
648
- await sleep(1000);
649
- return await this.getSign(cmd, body, seq_id, retry + 1);
553
+ try {
554
+ const data = await executeWithRetry(
555
+ () => qsignPost(url, params),
556
+ 'HeadSign',
557
+ {
558
+ throwOnError: true,
559
+ handleNotRegistered: true,
560
+ onNotRegistered: () => this.registerQSign(true),
561
+ logger: this.logger,
562
+ }
563
+ );
564
+ if (!data.data.sign) {
565
+ throw new Error(`[qsign][HeadSign] server return invalid response: ${JSON.stringify(data)}`);
650
566
  }
651
- data.msg = data.msg || 'unknown error';
652
- throw new Error(`[qsign][HeadSign] ${data.msg}(${data.code})`);
653
- }
654
- if (!data.data.sign) {
655
- throw new Error(`[qsign][HeadSign] server return invalid response: ${JSON.stringify(data)}`);
656
- }
657
- if (data.data.requestCallback && data.data.requestCallback.length > 0) {
658
- // 成功上线register后再发送SSO包
659
- unsendSso.push.apply(unsendSso, data.data.requestCallback);
660
- if (this.isOnline()) {
661
- setTimeout(() => this.refreshQSignToken(), 100);
662
- } else {
663
- this.once("internal.registered", async () => {
664
- await this.refreshQSignToken();
665
- });
567
+ if (data.data.requestCallback && data.data.requestCallback.length > 0) {
568
+ // 成功上线register后再发送SSO包
569
+ unsendSso.push.apply(unsendSso, data.data.requestCallback);
570
+ if (this.isOnline()) {
571
+ setTimeout(() => this.refreshQSignToken(), 100);
572
+ } else {
573
+ this.once("internal.registered", async () => {
574
+ await this.refreshQSignToken();
575
+ });
576
+ }
666
577
  }
578
+ return Buffer.from(pb.encode({
579
+ 1: Buffer.from(data.data.sign, "hex"),
580
+ 2: Buffer.from(data.data.token, "hex"),
581
+ 3: Buffer.from(data.data.extra, "hex"),
582
+ }));
583
+ } finally {
584
+ requestLock.release();
667
585
  }
668
- return Buffer.from(pb.encode({
669
- 1: Buffer.from(data.data.sign, "hex"),
670
- 2: Buffer.from(data.data.token, "hex"),
671
- 3: Buffer.from(data.data.extra, "hex"),
672
- }));
673
586
  };
674
587
 
675
588
  /**
676
589
  * 获取Tlv544
677
590
  * @param {string} cmd
678
- * @param {number} retry 重试次数
679
591
  */
680
- Client.prototype.getT544 = async function(cmd, retry = 0) {
592
+ Client.prototype.getT544 = async function(cmd) {
681
593
  if (!this.config.sign_api_addr || !this.apk.qua) return BUF0;
682
594
  if (this.config.force_algo_T544) return BUF0;
683
- let url = this.config.sign_api_addr;
684
- if (url[url.length - 1] !== '/') {
685
- url += '/';
595
+ // 优先使用custom_energy,避免服务器使用不正确的 salt 组装
596
+ try {
597
+ return await this.getT544CustomEnergy(cmd);
598
+ } catch (e) {
599
+ this.logger.warn(`[qsign][T544] /custom_energy failed, falling back to /energy: ${e.message || e}`);
686
600
  }
687
- url += 'energy';
688
- const params = {
689
- ver: this.apk.ver,
690
- androidId: this.device.android_id,
691
- android_id: this.device.android_id,
692
- qimei36: this.device.qimei36 || this.device.qimei16 || this.device.android_id,
693
- guid: this.device.guid.toString('hex'),
694
- uin: this.uin,
601
+ const url = buildUrl(this.config.sign_api_addr, 'energy');
602
+ const params = getCommonParams.call(this, {
695
603
  data: cmd,
696
- key: this.config.sign_api_key,
697
- version: this.apk.sdkver
698
- };
699
- const { data } = await axios.get(url,
604
+ version: this.apk.sdkver,
605
+ });
606
+ const data = await executeWithRetry(
607
+ () => qsignGet(url, params),
608
+ 'T544',
700
609
  {
701
- params,
702
- }
703
- ).catch((e) => ({ data: { code: -1, msg: e ? e.message : 'failed to connect to qsign api' } }));
704
- if (data.code !== 0) {
705
- if (data.code === 1 && String(data.msg).includes('not registered')) {
706
- await this.registerQSign();
707
- await sleep(1000);
708
- return await this.getT544(cmd, retry);
610
+ throwOnError: true,
611
+ handleNotRegistered: true,
612
+ onNotRegistered: () => this.registerQSign(),
613
+ logger: this.logger,
709
614
  }
710
- if (retry < 3) {
711
- this.logger.warn(`[qsign][T544] ${data.msg}(${data.code}), retry in 1s...`);
712
- await sleep(1000);
713
- return await this.getT544(cmd, retry + 1);
714
- }
715
- data.msg = data.msg || 'unknown error';
716
- throw new Error(`[qsign][T544] ${data.msg}(${data.code})`);
717
- }
615
+ );
718
616
  if (!data.data) {
719
- this.logger.warn(`[qsign][T544] server may not support /energy api: ${JSON.stringify(data)}`);
720
- return await this.getT544CustomEnergy(cmd);
617
+ throw new Error(`[qsign][T544] server return invalid response: ${JSON.stringify(data)}`);
721
618
  }
722
- if (typeof(data.data) === 'string')
619
+ if (typeof(data.data) === 'string') {
620
+ if (this.sig.t553 && this.sig.t553.length === 0) {
621
+ await this.getXwDebugId();
622
+ }
723
623
  return Buffer.from(data.data, 'hex');
724
- else if (typeof(data.data.sign) === 'string') {
624
+ } else if (typeof(data.data.sign) === 'string') {
725
625
  if (typeof(data.data.t553) === 'string') this.sig.t553 = Buffer.from(data.data.t553, 'hex');
726
626
  return Buffer.from(data.data.sign, 'hex');
727
627
  }
@@ -731,20 +631,15 @@ Client.prototype.getT544 = async function(cmd, retry = 0) {
731
631
  /**
732
632
  * 获取Tlv544 custom_energy
733
633
  * @param {string} cmd
734
- * @param {number} retry 重试次数
735
634
  */
736
- Client.prototype.getT544CustomEnergy = async function(cmd, retry = 0) {
635
+ Client.prototype.getT544CustomEnergy = async function(cmd) {
737
636
  if (!this.config.sign_api_addr || !this.apk.qua) return BUF0;
738
637
  if (this.config.force_algo_T544) return BUF0;
739
- let url = this.config.sign_api_addr;
740
- if (url[url.length - 1] !== '/') {
741
- url += '/';
742
- }
743
- url += 'custom_energy';
638
+ const url = buildUrl(this.config.sign_api_addr, 'custom_energy');
744
639
  const salt = new Writer();
745
640
  const subCmd = parseInt('0x' + (cmd.split("_")[1] || 9));
746
641
  if (["810_9", "810_a", "810_d", "810_f"].includes(cmd)) {
747
- salt.writeU32(0)
642
+ salt.writeU32(this.uin > 2147483647 ? 0xffffffff : 0)
748
643
  salt.writeTlv(this.device.guid)
749
644
  salt.writeTlv(this.apk.sdkver)
750
645
  salt.writeU32(subCmd)
@@ -755,44 +650,87 @@ Client.prototype.getT544CustomEnergy = async function(cmd, retry = 0) {
755
650
  salt.writeTlv(this.apk.sdkver)
756
651
  salt.writeU32(subCmd)
757
652
  }
758
- const params = {
759
- ver: this.apk.ver,
760
- androidId: this.device.android_id,
761
- android_id: this.device.android_id,
762
- qimei36: this.device.qimei36 || this.device.qimei16 || this.device.android_id,
763
- guid: this.device.guid.toString('hex'),
764
- uin: this.uin,
653
+ const params = getCommonParams.call(this, {
765
654
  data: cmd,
766
- key: this.config.sign_api_key,
767
- salt: salt.read().toString('hex')
768
- };
769
- const { data } = await axios.get(url,
655
+ salt: salt.read().toString('hex'),
656
+ });
657
+ const data = await executeWithRetry(
658
+ () => qsignGet(url, params),
659
+ 'T544',
770
660
  {
771
- params,
661
+ throwOnError: true,
662
+ handleNotRegistered: true,
663
+ onNotRegistered: () => this.registerQSign(),
664
+ logger: this.logger,
772
665
  }
773
- ).catch((e) => ({ data: { code: -1, msg: e ? e.message : 'failed to connect to qsign api' } }));
774
- if (data.code !== 0) {
775
- if (data.code === 1 && String(data.msg).includes('not registered')) {
776
- await this.registerQSign();
777
- await sleep(1000);
778
- return await this.getT544CustomEnergy(cmd, retry);
779
- }
780
- if (retry < 3) {
781
- this.logger.warn(`[qsign][T544] ${data.msg}(${data.code}), retry in 1s...`);
782
- await sleep(1000);
783
- return await this.getT544CustomEnergy(cmd, retry + 1);
784
- }
785
- data.msg = data.msg || 'unknown error';
786
- throw new Error(`[qsign][T544] ${data.msg}(${data.code})`);
787
- }
666
+ );
788
667
  if (!data.data) {
789
668
  throw new Error(`[qsign][T544] server return invalid response: ${JSON.stringify(data)}`);
790
669
  }
791
- if (typeof(data.data) === 'string')
670
+ if (typeof(data.data) === 'string') {
671
+ if (this.sig.t553 && this.sig.t553.length === 0) {
672
+ await this.getXwDebugId();
673
+ }
792
674
  return Buffer.from(data.data, 'hex');
793
- else if (typeof(data.data.sign) === 'string') {
675
+ } else if (typeof(data.data.sign) === 'string') {
794
676
  if (typeof(data.data.t553) === 'string') this.sig.t553 = Buffer.from(data.data.t553, 'hex');
795
677
  return Buffer.from(data.data.sign, 'hex');
796
678
  }
797
679
  else throw new Error(`[qsign][T544] server return invalid response: ${JSON.stringify(data)}`);
798
680
  };
681
+
682
+ /**
683
+ * 从服务器获得XwDebugId
684
+ */
685
+ Client.prototype.getXwDebugId = async function() {
686
+ if (!this.config.sign_api_addr || !this.apk.qua) return;
687
+ const url = buildUrl(this.config.sign_api_addr, 'get_xw_debug_id');
688
+ const params = getCommonParams.call(this);
689
+ await requestLock.acquire();
690
+ try {
691
+ const data = await executeWithRetry(
692
+ () => qsignGet(url, params),
693
+ 'XwDebugId',
694
+ {
695
+ throwOnError: false,
696
+ handleNotRegistered: true,
697
+ onNotRegistered: () => this.registerQSign(true),
698
+ logger: this.logger,
699
+ }
700
+ );
701
+ if (typeof(data.data) === 'string') {
702
+ this.sig.t553 = Buffer.from(data.data, 'hex');
703
+ this.logger.info(`[qsign][XwDebugId] updated t553: ${data.data}`);
704
+ }
705
+ } finally {
706
+ requestLock.release();
707
+ }
708
+ };
709
+
710
+ /**
711
+ * 从服务器拉取签名cmd名单
712
+ */
713
+ Client.prototype.updateCmdWhiteList = async function() {
714
+ if (!this.config.sign_api_addr || !this.apk.qua) return;
715
+ const url = buildUrl(this.config.sign_api_addr, 'cmd_whitelist');
716
+ const params = getCommonParams.call(this);
717
+ await requestLock.acquire();
718
+ try {
719
+ const data = await executeWithRetry(
720
+ () => qsignGet(url, params),
721
+ 'CmdWhiteList',
722
+ {
723
+ throwOnError: false,
724
+ handleNotRegistered: true,
725
+ onNotRegistered: () => this.registerQSign(true),
726
+ logger: this.logger,
727
+ }
728
+ );
729
+ if (data.data && Array.isArray(data.data.list) && data.data.list.length > 0) {
730
+ this.needSignCmd = data.data.list;
731
+ this.logger.info(`[qsign][CmdWhiteList] updated needSignCmd list, count: ${this.needSignCmd.length}`);
732
+ }
733
+ } finally {
734
+ requestLock.release();
735
+ }
736
+ };
package/lib/client.js CHANGED
@@ -22,7 +22,7 @@ const { WtLogin } = require("./wtlogin/wt");
22
22
  const chat = require("./message/chat");
23
23
  const multi = require("./message/multi");
24
24
  const image = require("./message/image");
25
- const { Gfs } = require("./message/file");
25
+ const { Gfs, getC2CFileUrlApi } = require("./message/file");
26
26
  const { getErrorMessage, TimeoutError } = require("./exception");
27
27
 
28
28
  function buildApiRet(retcode, data = null, error = null) {
@@ -161,6 +161,7 @@ class Client extends EventEmitter {
161
161
  seq_id = 1145 + Math.round(Math.random() * 1419);
162
162
  handlers = new Map; //存放响应包的回调
163
163
  seq_cache = new Map; //一分钟内的缓存
164
+ _registerRetryCount = 0; //register失败重试计数
164
165
 
165
166
  sig = {
166
167
  srm_token: BUF0,
@@ -370,6 +371,7 @@ class Client extends EventEmitter {
370
371
  else
371
372
  this.password_md5 = md5(String(password));
372
373
  }
374
+ this._registerRetryCount = 0;
373
375
  this._connect(() => {
374
376
  this._wt.syncTimeDiff();
375
377
  this._wt.passwordLogin();
@@ -628,7 +630,7 @@ class Client extends EventEmitter {
628
630
  return this._useProtocol(chat.preloadImages, arguments);
629
631
  }
630
632
 
631
- getNTPicURLbyFileid() {
633
+ getNTPicURLbyFileid(fileid) {
632
634
  return this._useProtocol(image.getNTPicURLbyFileidApi, arguments);
633
635
  }
634
636
 
@@ -640,6 +642,10 @@ class Client extends EventEmitter {
640
642
  return this._useProtocol(nessy.delDevLoginInfo, arguments);
641
643
  }
642
644
 
645
+ getC2CFileUrl(fileid) {
646
+ return this._useProtocol(getC2CFileUrlApi, arguments);
647
+ }
648
+
643
649
  ///////////////////////////////////////////////////
644
650
 
645
651
  async getCookies(domain) {
package/lib/device.js CHANGED
@@ -564,6 +564,19 @@ const apk = {
564
564
  ssover: 22,
565
565
  qua: "V1_AND_SQ_9.1.20_8198_YYB_D",
566
566
  },
567
+
568
+ //android phone 9.2.80
569
+ 61: {
570
+ ...DEFAULT_ANDROID_APK_INFO,
571
+ name: "A9.2.80.90316290",
572
+ version: "9.2.80.35125",
573
+ ver: "9.2.80",
574
+ buildtime: 1762849776,
575
+ subid: 537348293,
576
+ sdkver: "6.0.0.2591",
577
+ ssover: 22,
578
+ qua: "V1_AND_SQ_9.2.80_13690_YYB_D",
579
+ },
567
580
  };
568
581
 
569
582
  //apad
@@ -674,6 +687,10 @@ apk[58].subid = 537244932;
674
687
  apk[60] = { ...apk[59] };
675
688
  apk[60].subid = 537257453;
676
689
 
690
+ //apad 9.2.80
691
+ apk[62] = { ...apk[61] };
692
+ apk[62].subid = 537348332;
693
+
677
694
  //ipad
678
695
  apk[5] = { ...apk[1] };
679
696
  apk[5].subid = 537155074;
@@ -347,6 +347,7 @@ const map = {
347
347
  415: "/划龙舟",
348
348
  416: "/中龙舟",
349
349
  417: "/大龙舟",
350
+ 418: "/填罐罐",
350
351
 
351
352
  419: "/火车",
352
353
  420: "/中火车",
@@ -426,6 +426,15 @@ async function getC2CFileUrl(fileid) {
426
426
  return _url.toString();
427
427
  }
428
428
 
429
+ /**
430
+ * @this {import("../ref").Client}
431
+ * @param {Buffer|string} fileid
432
+ */
433
+ async function getC2CFileUrlApi(fileid) {
434
+ const url = await getC2CFileUrl.call(this, fileid);
435
+ return { result: 0, data: url };
436
+ }
437
+
429
438
  /**
430
439
  * @this {import("../ref").Client}
431
440
  * @param {Buffer|string} fileid
@@ -456,5 +465,5 @@ async function getSyncDeviceFileUrl(fileid) {
456
465
  }
457
466
 
458
467
  module.exports = {
459
- getC2CFileUrl, getSyncDeviceFileUrl, Gfs
468
+ getC2CFileUrl, getC2CFileUrlApi, getSyncDeviceFileUrl, Gfs
460
469
  };
@@ -56,6 +56,9 @@ const rkey_cache = {
56
56
  guildNTPicRkey: { value: '', expire: 0 },
57
57
  }
58
58
 
59
+ /** @type {Promise<typeof rkey_cache> | null} */
60
+ let rkey_fetching = null;
61
+
59
62
  /**
60
63
  * 生成CQ码file字段
61
64
  * @param {string} md5
@@ -627,91 +630,31 @@ async function checkImgUrl(url) {
627
630
  }
628
631
 
629
632
  /**
630
- * 获取QQNT私聊图URL
631
- * @this {import("../ref").Client}
632
- * @param {Object} imgInfo
633
- * @param {String} uid
634
- */
635
- async function getOffNTPicURL(imgInfo, uid) {
636
- if (!uid) uid = String(this.uid || this.uin);
637
- const body = pb.encode({
638
- 1: {
639
- 1: {
640
- 1: 1,
641
- 2: 200,
642
- },
643
- 2: {
644
- 101: 2,
645
- 102: 1,
646
- 200: 1,
647
- 201: {
648
- 1: 2,
649
- 2: uid,
650
- },
651
- },
652
- 3: {
653
- 1: 2,
654
- },
655
- },
656
- 3: {
657
- 1: imgInfo,
658
- 2: {
659
- 2: {
660
- 1: 0,
661
- 3: 0,
662
- },
663
- },
664
- },
665
- });
666
- try {
667
- const rsp = await this.sendOidbSvcTrpcTcp("OidbSvcTrpcTcp.0x11c5_200", body);
668
- const rkey = rsp[3][1];
669
- if (!rkey)
670
- throw new Error("rkey不存在");
671
- return `https://${rsp[3][3][1]}${rsp[3][3][2]}${rkey}&spec=0`;
672
- } catch (e) {
673
- this.logger.warn("获取QQNT图片URL失败", e.message);
674
- return null;
675
- }
676
- }
677
-
678
- /**
679
- * 获取QQNT群图URL
633
+ * 获取QQNT图片URL
680
634
  * @this {import("../ref").Client}
681
635
  * @param {Object} imgInfo
682
- * @param {number} gid
636
+ * @param {number|string} target 群号或UID
637
+ * @param {boolean} c2c 是否为私聊
683
638
  */
684
- async function getGroupNTPicURL(imgInfo, gid = 284840486) {
639
+ async function getNTPicURL(imgInfo, target, c2c = false) {
640
+ if (!target) target = c2c ? String(this.uid || this.uin) : 284840486;
685
641
  const body = pb.encode({
686
642
  1: {
687
- 1: {
688
- 1: 1,
689
- 2: 200,
690
- },
643
+ 1: { 1: 1, 2: 200 },
691
644
  2: {
692
- 101: 2,
693
- 102: 1,
694
- 200: 2,
695
- 202: {
696
- 1: gid,
697
- },
698
- },
699
- 3: {
700
- 1: 2,
645
+ 101: 2, 102: 1,
646
+ 200: c2c ? 1 : 2,
647
+ [c2c ? 201 : 202]: c2c ? { 1: 2, 2: String(target) } : { 1: target },
701
648
  },
649
+ 3: { 1: 2 },
702
650
  },
703
651
  3: {
704
652
  1: imgInfo,
705
- 2: {
706
- 2: {
707
- 1: 0,
708
- 3: 0,
709
- },
710
- },
653
+ 2: { 2: { 1: 0, 3: 0 } },
711
654
  },
712
655
  });
713
656
  try {
714
- const rsp = await this.sendOidbSvcTrpcTcp("OidbSvcTrpcTcp.0x11c4_200", body);
657
+ const rsp = await this.sendOidbSvcTrpcTcp(`OidbSvcTrpcTcp.0x11c${c2c ? 5 : 4}_200`, body);
715
658
  const rkey = rsp[3][1];
716
659
  if (!rkey)
717
660
  throw new Error("rkey不存在");
@@ -732,7 +675,7 @@ async function getNTPicRkey() {
732
675
  let needRefresh = false;
733
676
  for (const key of Object.keys(rkey_cache)) {
734
677
  const entry = rkey_cache[key];
735
- if (!entry.value || entry.expire <= now) {
678
+ if (!entry.value || (entry.expire || 0) - now < 600) { // 提前 10 分钟刷新
736
679
  needRefresh = true;
737
680
  break;
738
681
  }
@@ -740,6 +683,21 @@ async function getNTPicRkey() {
740
683
  if (!needRefresh) {
741
684
  return rkey_cache;
742
685
  }
686
+ // 如果已有请求正在进行中,直接复用其结果
687
+ if (rkey_fetching) {
688
+ return rkey_fetching;
689
+ }
690
+ rkey_fetching = _fetchNTPicRkey.call(this).finally(() => {
691
+ rkey_fetching = null;
692
+ });
693
+ return rkey_fetching;
694
+ }
695
+
696
+ /**
697
+ * 实际执行获取QQNT图片rkey的请求
698
+ * @this {import("../ref").Client}
699
+ */
700
+ async function _fetchNTPicRkey() {
743
701
  const body = pb.encode({
744
702
  1: {
745
703
  1: {
@@ -776,36 +734,6 @@ async function getNTPicRkey() {
776
734
  return rkey_cache;
777
735
  }
778
736
 
779
- /**
780
- * 获取QQNT群图rkey
781
- * @deprecated
782
- * @this {import("../ref").Client}
783
- */
784
- async function getGroupNTPicRkey() {
785
- const rsp = await getGroupNTPicURL.call(this, {
786
- 2: "CNL-BhjClnU",
787
- 3: 1,
788
- });
789
- if (!rsp)
790
- return "&rkey=CAQSKDOc_jvbthUjZrMxJG2jNZ-rIWue47Q3PGKb_GWljzyvOUZOv0-EVao";
791
- return '&rkey=' + rsp.match(/&rkey=([^&]+)/)[1];
792
- }
793
-
794
- /**
795
- * 获取QQNT私聊图rkey
796
- * @deprecated
797
- * @this {import("../ref").Client}
798
- */
799
- async function getOffNTPicRkey() {
800
- const rsp = await getOffNTPicURL.call(this, {
801
- 2: "CNL-BhjClnU",
802
- 3: 1,
803
- });
804
- if (!rsp)
805
- return "&rkey=CAQSKDOc_jvbthUjAatuFPQIo-x9wwcDhDGd8SOEu5FyJWNxNMabJTTRpO8";
806
- return '&rkey=' + rsp.match(/&rkey=([^&]+)/)[1];
807
- }
808
-
809
737
  /**
810
738
  * 通过fileid获取QQNT图片URL
811
739
  * @this {import("../ref").Client}
@@ -839,7 +767,7 @@ async function getNTPicURLbyFileidApi(fileid) {
839
767
  module.exports = {
840
768
  ImageBuilder, uploadImages, buildImageFileParam,
841
769
  groupNTPicDown, offNTPicDown, checkImgUrl,
842
- getGroupNTPicURL, getOffNTPicURL,
770
+ getNTPicURL,
843
771
  getNTPicURLbyFileid,
844
772
  getNTPicURLbyFileidApi,
845
773
  };
@@ -10,7 +10,7 @@ const pb = require("../algo/pb");
10
10
  const face = require("./face");
11
11
  const { getGroupMsgs } = require("./history");
12
12
  const { int32ip2str } = require("../service");
13
- const { buildImageFileParam, groupNTPicDown, offNTPicDown, checkImgUrl, getOffNTPicURL, getGroupNTPicURL, getNTPicURLbyFileid } = require("./image");
13
+ const { buildImageFileParam, groupNTPicDown, offNTPicDown, checkImgUrl, getNTPicURL, getNTPicURLbyFileid } = require("./image");
14
14
  const { Gfs, getC2CFileUrl, getSyncDeviceFileUrl } = require("./file");
15
15
  const { getVideoUrl, getPttUrl, getOffNTPttUrl, getGroupNTPttUrl, getOffNTVideoUrl, getGroupNTVideoUrl } = require("./ptt");
16
16
  const { genC2CMessageId, genGroupMessageId, timestamp, parseFunString, code2uin, genRandom, unzip } = require("../common");
@@ -589,9 +589,9 @@ class Parser {
589
589
  // 图片地址失效,尝试发包获取
590
590
  let newURL = null;
591
591
  if (dm) { //私聊
592
- newURL = await getOffNTPicURL.call(this.c, imgInfo, String(this.uid));
592
+ newURL = await getNTPicURL.call(this.c, imgInfo, String(this.uid), true);
593
593
  } else { //群聊
594
- newURL = await getGroupNTPicURL.call(this.c, imgInfo, this.gid);
594
+ newURL = await getNTPicURL.call(this.c, imgInfo, this.gid);
595
595
  }
596
596
  if (newURL) {
597
597
  data.url = newURL;
package/lib/oicq.js CHANGED
@@ -216,8 +216,15 @@ function kickoffListener(data) {
216
216
 
217
217
  Client.prototype._register = async function () {
218
218
  if (!await this._wt.register()) {
219
+ if (++this._registerRetryCount > 10) {
220
+ this.logger.error("register重试超过10次,停止重试");
221
+ this._registerRetryCount = 0;
222
+ this.terminate();
223
+ return this.em("system.login.error", { code: -1, message: "上线注册失败,请检查token与协议类型是否匹配" });
224
+ }
219
225
  return this.emit("internal.network", "服务器繁忙(register)");
220
226
  }
227
+ this._registerRetryCount = 0;
221
228
  this.status = STATUS_ONLINE;
222
229
  if (!this.online_status)
223
230
  this.online_status = 11;
@@ -292,6 +299,7 @@ Client.prototype._stopHeartbeat = function () {
292
299
  */
293
300
  async function onlineListener() {
294
301
  this.sync_finished = false;
302
+ this._registerRetryCount = 0;
295
303
  await this._register();
296
304
  if (!this.isOnline())
297
305
  return;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * QSign API 公共模块
3
+ * 封装所有与 qsign 服务器交互的通用逻辑
4
+ */
5
+ "use strict";
6
+
7
+ const _axios = require("axios");
8
+ const http = require("http");
9
+ const https = require("https");
10
+
11
+ const API_WAIT_TIME = 45000;
12
+ const MAX_RETRY = 3;
13
+
14
+ const axios = _axios.create({
15
+ httpAgent: new http.Agent({ keepAlive: true }),
16
+ httpsAgent: new https.Agent({ keepAlive: true }),
17
+ timeout: API_WAIT_TIME,
18
+ headers: {
19
+ 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.5249.199 Safari/537.36 OICQ/1.26 ILPP/2",
20
+ 'Content-Type': "application/x-www-form-urlencoded"
21
+ }
22
+ });
23
+
24
+ /**
25
+ * 构建 qsign API URL
26
+ * @param {string} baseAddr 基础地址
27
+ * @param {string} endpoint 端点路径
28
+ * @returns {string}
29
+ */
30
+ function buildUrl(baseAddr, endpoint) {
31
+ let url = baseAddr;
32
+ if (url[url.length - 1] !== '/') {
33
+ url += '/';
34
+ }
35
+ return url + endpoint;
36
+ }
37
+
38
+ /**
39
+ * 获取 qsign API 公共参数
40
+ * @this {import("./ref").Client}
41
+ * @param {object} [extraParams] 额外参数
42
+ * @returns {object}
43
+ */
44
+ function getCommonParams(extraParams = {}) {
45
+ return {
46
+ ver: this.apk.ver,
47
+ android_id: this.device.android_id,
48
+ androidId: this.device.android_id,
49
+ qimei36: this.device.qimei36 || this.device.qimei16 || this.device.android_id,
50
+ guid: this.device.guid.toString('hex'),
51
+ uin: this.uin,
52
+ key: this.config.sign_api_key,
53
+ qua: this.apk.qua,
54
+ ...extraParams,
55
+ };
56
+ }
57
+
58
+ /**
59
+ * 发送 qsign GET 请求
60
+ * @param {string} url
61
+ * @param {object} params
62
+ * @returns {Promise<object>}
63
+ */
64
+ async function qsignGet(url, params) {
65
+ const { data } = await axios.get(url, { params })
66
+ .catch((e) => ({ data: { code: -1, msg: e ? e.message : 'failed to connect to qsign api' } }));
67
+ return data;
68
+ }
69
+
70
+ /**
71
+ * 发送 qsign POST 请求
72
+ * @param {string} url
73
+ * @param {object} body
74
+ * @returns {Promise<object>}
75
+ */
76
+ async function qsignPost(url, body) {
77
+ const { data } = await axios.post(url, body)
78
+ .catch((e) => ({ data: { code: -1, msg: e ? e.message : 'failed to connect to qsign api' } }));
79
+ return data;
80
+ }
81
+
82
+ /**
83
+ * 执行带重试的 qsign API 调用
84
+ * @param {function} apiFn 实际 API 调用函数
85
+ * @param {string} apiName API 名称(用于日志)
86
+ * @param {object} options 选项
87
+ * @param {boolean} [options.throwOnError=false] 是否在失败时抛出错误
88
+ * @param {boolean} [options.handleNotRegistered=false] 是否处理 "not registered" 错误
89
+ * @param {function} [options.onNotRegistered] 处理 "not registered" 的回调
90
+ * @param {number} [options.retry=0] 当前重试次数
91
+ * @returns {Promise<object>}
92
+ */
93
+ async function executeWithRetry(apiFn, apiName, options = {}) {
94
+ const {
95
+ throwOnError = false,
96
+ handleNotRegistered = false,
97
+ onNotRegistered = null,
98
+ retry = 0,
99
+ logger = console,
100
+ } = options;
101
+
102
+ const data = await apiFn();
103
+
104
+ if (data.code !== 0) {
105
+ // 处理 "not registered" 错误
106
+ if (handleNotRegistered && data.code === 1 && String(data.msg).includes('not registered')) {
107
+ if (retry >= MAX_RETRY) {
108
+ data.msg = 'not registered after max retries. Original message: ' + data.msg;
109
+ const errorMsg = `[qsign][${apiName}] ${data.msg}(${data.code})`;
110
+ if (throwOnError) {
111
+ throw new Error(errorMsg);
112
+ }
113
+ logger.warn(errorMsg);
114
+ return data;
115
+ }
116
+ if (onNotRegistered) {
117
+ await onNotRegistered();
118
+ }
119
+ await sleep(100);
120
+ return executeWithRetry(apiFn, apiName, { ...options, retry: retry + 1 });
121
+ }
122
+
123
+ // 重试逻辑
124
+ if (retry < MAX_RETRY) {
125
+ logger.warn(`[qsign][${apiName}] ${data.msg}(${data.code}), retry in 1s...`);
126
+ await sleep(1000);
127
+ return executeWithRetry(apiFn, apiName, { ...options, retry: retry + 1 });
128
+ }
129
+
130
+ // 最终失败处理
131
+ data.msg = data.msg || 'unknown error';
132
+ const errorMsg = `[qsign][${apiName}] ${data.msg}(${data.code})`;
133
+ if (throwOnError) {
134
+ throw new Error(errorMsg);
135
+ }
136
+ logger.warn(errorMsg);
137
+ }
138
+
139
+ return data;
140
+ }
141
+
142
+ /**
143
+ * 延迟函数
144
+ * @param {number} time 毫秒
145
+ * @returns {Promise<void>}
146
+ */
147
+ function sleep(time) {
148
+ return new Promise(resolve => setTimeout(resolve, time));
149
+ }
150
+
151
+ module.exports = {
152
+ buildUrl,
153
+ getCommonParams,
154
+ qsignGet,
155
+ qsignPost,
156
+ executeWithRetry,
157
+ sleep,
158
+ };
package/lib/ref.d.ts CHANGED
@@ -195,6 +195,8 @@ export class Client extends oicq.Client {
195
195
  submitSsoCallback(cmd: string, callbackId: number, body: Buffer): Promise<void>;
196
196
  refreshQSignToken(): Promise<void>;
197
197
  buildSSOReserveField(cmd: string, body: Buffer, seq?: number, options?: SsoOptions): Promise<Buffer>;
198
+ getXwDebugId(): Promise<void>;
199
+ updateCmdWhiteList(): Promise<void>;
198
200
  }
199
201
 
200
202
  export * from '../index';
@@ -52,6 +52,12 @@ WtLogin.prototype.passwordLogin = async function () {
52
52
  } catch {
53
53
  this.uid = "";
54
54
  }
55
+ try {
56
+ await this.c.updateCmdWhiteList();
57
+ } catch (e) {
58
+ const message = e.message || "[qsign][CmdWhiteList] 未知错误";
59
+ this.logger.error(message);
60
+ }
55
61
  try {
56
62
  this.t106 = await fs.promises.readFile(path.join(this.dir, "t106"));
57
63
  try {
@@ -367,7 +367,7 @@ const tlv_map = {
367
367
  const salt = new Writer()
368
368
  const subCmd = parseInt('0x' + (code.split("_")[1] || 9))
369
369
  if (["810_9", "810_a", "810_d", "810_f"].includes(code)) {
370
- salt.writeU32(0)
370
+ salt.writeU32(this.uin > 2147483647 ? 0xffffffff : 0)
371
371
  salt.writeTlv(this.device.guid)
372
372
  salt.writeTlv(Buffer.from(this.apk.sdkver))
373
373
  salt.writeU32(subCmd)
package/lib/wtlogin/wt.js CHANGED
@@ -206,7 +206,6 @@ class WtLogin {
206
206
  this.logger.warn(`Unsupported tlv546 hash type ${hashType}`);
207
207
  return Buffer.alloc(0);
208
208
  }
209
- let inputNum = BigInt("0x" + src.toString("hex"));
210
209
  switch (typ) {
211
210
  case 1:
212
211
  // TODO
@@ -219,10 +218,15 @@ class WtLogin {
219
218
  let elp = 0, cnt = 0;
220
219
  if (tgt.length === 32) {
221
220
  const start = Date.now();
222
- let hash = createHash("sha256").update(Buffer.from(inputNum.toString(16).padStart(256, "0"), "hex")).digest();
221
+ const buf = Buffer.alloc(128);
222
+ src.copy(buf, 128 - src.length);
223
+ let hash = createHash("sha256").update(buf).digest();
223
224
  while (Buffer.compare(hash, tgt) !== 0) {
224
- inputNum ++;
225
- hash = createHash("sha256").update(Buffer.from(inputNum.toString(16).padStart(256, "0"), "hex")).digest();
225
+ for (let i = 127; i >= 0; i--) {
226
+ buf[i] = (buf[i] + 1) & 0xFF;
227
+ if (buf[i] !== 0) break;
228
+ }
229
+ hash = createHash("sha256").update(buf).digest();
226
230
  cnt++;
227
231
  if (cnt > 1000000) {
228
232
  this.logger.error("Calculating PoW cost too much time, maybe something wrong");
@@ -230,7 +234,7 @@ class WtLogin {
230
234
  }
231
235
  }
232
236
  ok = true;
233
- dst = Buffer.from(inputNum.toString(16).padStart(256, "0"), "hex");
237
+ dst = buf;
234
238
  elp = Date.now() - start;
235
239
  this.logger.info(`Calculating PoW of plus ${cnt} times cost ${elp} ms`);
236
240
  }
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.1",
5
- "app_version": "1.26.1",
6
- "upday": "2026/05/17",
4
+ "version": "1.26.3",
5
+ "app_version": "1.26.3",
6
+ "upday": "2026/06/28",
7
7
  "protocol_version": "v11",
8
8
  "description": "oicq(https://github.com/takayama-lily/oicq) v1 for Icalingua++",
9
9
  "main": "index.js",
@@ -21,23 +21,23 @@
21
21
  "author": "The Icalingua++ Authors",
22
22
  "license": "MPL-2.0",
23
23
  "dependencies": {
24
- "axios": "^1.2.1",
25
- "jsqr": "^1.4.0",
26
- "log4js": "^6.3.0",
27
- "long": "^4.0.0",
28
- "pngjs": "^6.0.0",
29
- "probe-image-size": "^7.2.1",
30
- "qrcode-terminal": "^0.12.0",
31
- "ws": "^7.5.5"
24
+ "axios": "1.14.0",
25
+ "jsqr": "1.4.0",
26
+ "log4js": "6.9.1",
27
+ "long": "4.0.0",
28
+ "pngjs": "6.0.0",
29
+ "probe-image-size": "7.2.3",
30
+ "qrcode-terminal": "0.12.0",
31
+ "ws": "7.5.11"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/node": "^16.7.13",
35
- "@types/probe-image-size": "^7.0.1",
36
- "@types/ws": "^7.4.7",
37
- "chai": "^4.3.4",
38
- "mocha": "^8.4.0",
39
- "mockjs": "^1.1.0",
40
- "protobufjs": "^6.11.2"
35
+ "@types/probe-image-size": "7.0.1",
36
+ "@types/ws": "7.4.7",
37
+ "chai": "4.5.0",
38
+ "mocha": "8.4.0",
39
+ "mockjs": "1.1.0",
40
+ "protobufjs": "6.11.6"
41
41
  },
42
42
  "files": [
43
43
  "/bin",