oicq-icalingua-plus-plus 1.24.1 → 1.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -162,6 +162,7 @@ export interface MemberBaseInfo {
162
162
  readonly role: GroupRole, //权限
163
163
  readonly title: string, //头衔
164
164
  readonly subid: number,
165
+ readonly uid: string,
165
166
  }
166
167
  /** 群员资料 */
167
168
  export interface MemberInfo extends MemberBaseInfo {
package/lib/client-ext.js CHANGED
@@ -102,7 +102,7 @@ Client.prototype.sendOidbSvcTrpcTcp = async function (cmd, body) {
102
102
  1: type1,
103
103
  2: isNaN(type2) ? 1 : type2,
104
104
  4: body,
105
- 6: "android " + this.apk.ver,
105
+ 12: 0,
106
106
  });
107
107
  const payload = await this.sendUni(cmd, body);
108
108
  const rsp = pb.decode(payload);
package/lib/client.js CHANGED
@@ -278,6 +278,8 @@ class Client extends EventEmitter {
278
278
  this.on("internal.login", onlineListener);
279
279
  this.on("internal.packet", packetListener);
280
280
  this.on("internal.network", networkErrorListener);
281
+
282
+ this.readStorageConfig();
281
283
  }
282
284
 
283
285
  /**
@@ -688,6 +690,33 @@ class Client extends EventEmitter {
688
690
  acquireGfs(group_id) {
689
691
  return new Gfs(this, group_id);
690
692
  }
693
+ saveStorageConfig() {
694
+ try {
695
+ fs.writeFileSync(path.join(this.dir, "storage.json"), JSON.stringify({
696
+ sig_session: this.storage.sig_session.toString("base64"),
697
+ session_key: this.storage.session_key.toString("base64"),
698
+ ip: this.storage.ip,
699
+ port: this.storage.port,
700
+ }));
701
+ } catch (e) {
702
+ this.logger.error(e);
703
+ }
704
+ }
705
+ readStorageConfig() {
706
+ try {
707
+ const bakPath = path.join(this.dir, "storage.json");
708
+ if (!fs.existsSync(bakPath))
709
+ return;
710
+ const bakJSON = fs.readFileSync(bakPath).toString();
711
+ const bakConfig = JSON.parse(bakJSON);
712
+ this.storage.sig_session = Buffer.from(bakConfig.sig_session, "base64");
713
+ this.storage.session_key = Buffer.from(bakConfig.session_key, "base64");
714
+ this.storage.ip = bakConfig.ip;
715
+ this.storage.port = bakConfig.port;
716
+ } catch (e) {
717
+ this.logger.error(e);
718
+ }
719
+ }
691
720
  }
692
721
 
693
722
  function createDataDir(dir, uin) {
@@ -159,7 +159,7 @@ async function __getGML(group_id, next_uin) {
159
159
  method: "GetTroopMemberListReq",
160
160
  };
161
161
  const body = jce.encodeWrapper({ GTML }, extra);
162
- const blob = await this.sendUni("friendlist.GetTroopMemberListReq", body, 10);
162
+ const blob = await this.sendUni("friendlist.GetTroopMemberList", body, 10);
163
163
  const nested = jce.decode(blob);
164
164
  const map = new Map(), next = nested[4];
165
165
  for (let v of nested[3]) {
@@ -182,6 +182,7 @@ async function __getGML(group_id, next_uin) {
182
182
  card_changeable: true,
183
183
  shutup_time: v[30] * 1000 > Date.now() ? v[30] : 0,
184
184
  update_time: 0,
185
+ uid: v[42] || "",
185
186
  });
186
187
  }
187
188
  try {
package/lib/device.js CHANGED
@@ -432,6 +432,58 @@ const apk = {
432
432
  ssover: 21,
433
433
  qua: 'V1_AND_SQ_9.0.0_5282_YYB_D',
434
434
  },
435
+
436
+ //android phone 9.0.8
437
+ 41: {
438
+ ...DEFAULT_ANDROID_APK_INFO,
439
+ name: "A9.0.8.10368491",
440
+ version: "9.0.8.14755",
441
+ ver: "9.0.8",
442
+ buildtime: 1702888273,
443
+ subid: 537200218,
444
+ sdkver: "6.0.0.2558",
445
+ ssover: 21,
446
+ qua: 'V1_AND_SQ_9.0.8_5540_YYB_D',
447
+ },
448
+
449
+ //android phone 9.0.17
450
+ 43: {
451
+ ...DEFAULT_ANDROID_APK_INFO,
452
+ name: "A9.0.17.6a4a36ca",
453
+ version: "9.0.17.151855",
454
+ ver: "9.0.17",
455
+ buildtime: 1702888273,
456
+ subid: 537204056,
457
+ sdkver: "6.0.0.2558",
458
+ ssover: 21,
459
+ qua: 'V1_AND_SQ_9.0.17_5712_YYB_D',
460
+ },
461
+
462
+ //android phone 9.0.25
463
+ 45: {
464
+ ...DEFAULT_ANDROID_APK_INFO,
465
+ name: "A9.0.25.63b29b33",
466
+ version: "9.0.25.15760",
467
+ ver: "9.0.25",
468
+ buildtime: 1702888273,
469
+ subid: 537210084,
470
+ sdkver: "6.0.0.2558",
471
+ ssover: 21,
472
+ qua: 'V1_AND_SQ_9.0.25_5942_YYB_D',
473
+ },
474
+
475
+ //android phone 9.0.35
476
+ 47: {
477
+ ...DEFAULT_ANDROID_APK_INFO,
478
+ name: "A9.0.35.52ab3f26",
479
+ version: "9.0.35.16275",
480
+ ver: "9.0.35",
481
+ buildtime: 1710769234,
482
+ subid: 537215475,
483
+ sdkver: "6.0.0.2559",
484
+ ssover: 21,
485
+ qua: 'V1_AND_SQ_9.0.35_6150_YYB_D',
486
+ },
435
487
  };
436
488
 
437
489
  //apad
@@ -502,6 +554,22 @@ apk[38].subid = 537187437;
502
554
  apk[40] = { ...apk[39] };
503
555
  apk[40].subid = 537194390;
504
556
 
557
+ //apad 9.0.8
558
+ apk[42] = { ...apk[41] };
559
+ apk[42].subid = 537200257;
560
+
561
+ //apad 9.0.17
562
+ apk[44] = { ...apk[43] };
563
+ apk[44].subid = 537204095;
564
+
565
+ //apad 9.0.25
566
+ apk[46] = { ...apk[45] };
567
+ apk[46].subid = 537210123;
568
+
569
+ //apad 9.0.35
570
+ apk[48] = { ...apk[47] };
571
+ apk[48].subid = 537215514;
572
+
505
573
  //ipad
506
574
  apk[5] = { ...apk[1] };
507
575
  apk[5].subid = 537155074;
@@ -8,6 +8,7 @@ const fs = require("fs");
8
8
  const path = require("path");
9
9
  const { randomBytes } = require("crypto");
10
10
  const probe = require("probe-image-size");
11
+ const axios = require("axios");
11
12
  const pb = require("../algo/pb");
12
13
  const common = require("../common");
13
14
  const { downloadFromWeb, highwayUploadStream, MAX_UPLOAD_SIZE } = require("../service");
@@ -595,7 +596,115 @@ async function offNTPicDown(user_id, fileResId) {
595
596
  return "https://gchat.qpic.cn" + String(rsp[11]);
596
597
  }
597
598
 
599
+ /**
600
+ * 判断图片URL是否有效
601
+ * @this {import("../ref").Client}
602
+ * @param {string} url
603
+ */
604
+ async function checkImgUrl(url) {
605
+ const data = await axios.get(url, {
606
+ headers: { Range: 'bytes=0-1' }
607
+ }).then(res => res.data).catch(() => null);
608
+ return !!data;
609
+ }
610
+
611
+ /**
612
+ * 获取QQNT私聊图URL
613
+ * @this {import("../ref").Client}
614
+ * @param {Buffer} imgInfo
615
+ * @param {String} uid
616
+ */
617
+ async function getOffNTPicURL(imgInfo, uid = String(this.uid || this.uin)) {
618
+ const body = pb.encode({
619
+ 1: {
620
+ 1: {
621
+ 1: 1,
622
+ 2: 200,
623
+ },
624
+ 2: {
625
+ 101: 2,
626
+ 102: 1,
627
+ 200: 1,
628
+ 201: {
629
+ 1: 2,
630
+ 2: uid,
631
+ },
632
+ },
633
+ 3: {
634
+ 1: 2,
635
+ },
636
+ },
637
+ 3: {
638
+ 1: imgInfo,
639
+ 2: {
640
+ 2: {
641
+ 1: 0,
642
+ 3: 0,
643
+ },
644
+ },
645
+ },
646
+ });
647
+ try {
648
+ const rsp = await this.sendOidbSvcTrpcTcp("OidbSvcTrpcTcp.0x11c5_200", body);
649
+ const rkey = rsp[3][1];
650
+ if (!rkey)
651
+ throw new Error("rkey不存在");
652
+ return `https://${rsp[3][3][1]}${rsp[3][3][2]}${rkey}&spec=0`;
653
+ } catch (e) {
654
+ this.logger.warn("获取QQNT图片URL失败", e.message);
655
+ return null;
656
+ }
657
+ }
658
+
659
+ /**
660
+ * 获取QQNT群图URL
661
+ * @this {import("../ref").Client}
662
+ * @param {Buffer} imgInfo
663
+ * @param {number} gid
664
+ */
665
+ async function getGroupNTPicURL(imgInfo, gid = 284840486) {
666
+ const body = pb.encode({
667
+ 1: {
668
+ 1: {
669
+ 1: 1,
670
+ 2: 200,
671
+ },
672
+ 2: {
673
+ 101: 2,
674
+ 102: 1,
675
+ 200: 2,
676
+ 202: {
677
+ 1: gid,
678
+ },
679
+ },
680
+ 3: {
681
+ 1: 2,
682
+ },
683
+ },
684
+ 3: {
685
+ 1: imgInfo,
686
+ 2: {
687
+ 2: {
688
+ 1: 0,
689
+ 3: 0,
690
+ },
691
+ },
692
+ },
693
+ });
694
+ try {
695
+ const rsp = await this.sendOidbSvcTrpcTcp("OidbSvcTrpcTcp.0x11c5_200", body);
696
+ const rkey = rsp[3][1];
697
+ if (!rkey)
698
+ throw new Error("rkey不存在");
699
+ return `https://${rsp[3][3][1]}${rsp[3][3][2]}${rkey}&spec=0`;
700
+ } catch (e) {
701
+ this.logger.warn("获取QQNT图片URL失败", e.message);
702
+ return null;
703
+ }
704
+ }
705
+
598
706
  module.exports = {
599
707
  ImageBuilder, uploadImages, buildImageFileParam,
600
- groupNTPicDown, offNTPicDown,
708
+ groupNTPicDown, offNTPicDown, checkImgUrl,
709
+ getGroupNTPicURL, getOffNTPicURL,
601
710
  };
@@ -334,6 +334,13 @@ async function makeForwardMsg(iterable, dm = false, target = 0) {
334
334
  for (let i of msg[3][1][2]) {
335
335
  if (i[9]) {
336
336
  elements.push(i);
337
+ elements.push({
338
+ 37: {
339
+ 19: {
340
+ 15: 65536
341
+ }
342
+ }
343
+ });
337
344
  break;
338
345
  }
339
346
  }
@@ -348,6 +355,13 @@ async function makeForwardMsg(iterable, dm = false, target = 0) {
348
355
  1: fake.bubble_id
349
356
  }
350
357
  });
358
+ elements.push({
359
+ 37: {
360
+ 19: {
361
+ 15: 65536
362
+ }
363
+ }
364
+ });
351
365
  }
352
366
  nodes.push({
353
367
  1: {
@@ -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 } = require("./image");
13
+ const { buildImageFileParam, groupNTPicDown, offNTPicDown, checkImgUrl, getOffNTPicURL, getGroupNTPicURL } = require("./image");
14
14
  const { Gfs, getC2CFileUrl } = require("./file");
15
15
  const { getVideoUrl, getPttUrl } = require("./ptt");
16
16
  const { genC2CMessageId, genGroupMessageId, timestamp, parseFunString, code2uin, genRandom, unzip } = require("../common");
@@ -203,7 +203,7 @@ class Parser {
203
203
  break;
204
204
  case 3: //flash
205
205
  msg.type = "flash";
206
- msg.data = await this.parseImgElem(elem);
206
+ msg.data = await this.parseImgElem(type, elem);
207
207
  brief = "[闪照]";
208
208
  break;
209
209
  case 0: //ptt
@@ -343,7 +343,7 @@ class Parser {
343
343
  case 4:
344
344
  case 8:
345
345
  msg.type = "image";
346
- msg.data = await this.parseImgElem(elem);
346
+ msg.data = await this.parseImgElem(type, elem);
347
347
  brief = "[图片]";
348
348
  break;
349
349
  case 34: //sface
@@ -366,6 +366,16 @@ class Parser {
366
366
  brief = String(elem[1]);
367
367
  msg.data.markdown = brief;
368
368
  break;
369
+ case 46: //markdown button
370
+ break;
371
+ case 48: //image of qqnt
372
+ const imgData = await this.parseNTImgElem(type, elem);
373
+ if (imgData) {
374
+ msg.type = "image";
375
+ msg.data = imgData;
376
+ brief = "[图片]";
377
+ }
378
+ break;
369
379
  default:
370
380
  return;
371
381
  }
@@ -455,6 +465,7 @@ class Parser {
455
465
  } else if (elem[1] === 20) { //json only for mobileqq
456
466
  await this.parseExclusiveElem(51, elem[2]);
457
467
  } else if (elem[1] == 48) { //image prefix of qqnt
468
+ await this.parsePartialElem(48, elem[2]);
458
469
  if (elem[2] && elem[2][1] && elem[2][1][2] && elem[2][1][2][1] && elem[2][1][2][3])
459
470
  this.imgprefix = "https://" + String(elem[2][1][2][3]) + String(elem[2][1][2][1]);
460
471
  } else if (elem[1] == 45) { //markdown
@@ -472,50 +483,115 @@ class Parser {
472
483
  }
473
484
 
474
485
  /**
475
- * 解析图片
486
+ * 解析QQNT图片
476
487
  * @private
488
+ * @param {number} type 48|3
477
489
  * @param {import("../ref").Proto} elem
478
490
  */
479
- async parseImgElem(elem) {
491
+ async parseNTImgElem(type, elem) {
480
492
  const data = { };
481
- if (elem[29] && elem[29][30] && String(elem[29][30]).startsWith("&rkey=")) {
482
- //QQNT 私图
483
- if (this.imgprefix) {
484
- data.file = buildImageFileParam(elem[7].toHex(), elem[2], elem[9], elem[8], elem[5]);
485
- data.url = this.imgprefix + String(elem[29][30]) + "&spec=0";
486
- this.imgprefix = null;
487
- return data;
493
+ if (!elem[2] || !elem[2][1]) return null;
494
+ const dm = !!elem[2][1][11];
495
+ const extra = dm ? elem[2][1][11] : elem[2][1][12];
496
+ if (!extra) return null;
497
+ const ntPath = String(extra[30] || "");
498
+
499
+ const imgInfo = elem[1][1];
500
+ const md5 = imgInfo[1][2]; //string, not buffer
501
+ const sha1 = imgInfo[1][3];
502
+ const fileName = imgInfo[1][4];
503
+ const size = imgInfo[1][1];
504
+ const width = imgInfo[1][6];
505
+ const height = imgInfo[1][7];
506
+ const imgType = imgInfo[1][5][2];
507
+
508
+ const fileid = imgInfo[2];
509
+ const storeId = imgInfo[3];
510
+ const time = imgInfo[4];
511
+
512
+ data.file = buildImageFileParam(md5.toString(), size, width, height, imgType);
513
+
514
+ if (ntPath) {
515
+ data.url = `https://${elem[1][2][3]}${ntPath}&spec=0`;
516
+ } else {
517
+ data.url = `https://gchat.qpic.cn/gchatpic_new/0/0-0-${md5.toUpperCase()}/0`;
518
+ }
519
+ if (timestamp() - time > 60 * 60 || !(await checkImgUrl(data.url))) {
520
+ // 图片地址失效,尝试发包获取
521
+ let newURL = null;
522
+ if (dm) { //私聊
523
+ newURL = await getOffNTPicURL.call(this.c, imgInfo, String(this.uid));
524
+ } else { //群聊
525
+ newURL = await getGroupNTPicURL.call(this.c, imgInfo, this.gid);
488
526
  }
489
- } else if (elem[34] && elem[34][30] && String(elem[34][30]).startsWith("&rkey=")) {
490
- //QQNT 群图
491
- if (this.imgprefix) {
492
- data.file = buildImageFileParam(elem[13].toHex(), elem[25], elem[22], elem[23], elem[20]);
493
- data.url = this.imgprefix + String(elem[34][30]) + "&spec=0";
494
- this.imgprefix = null;
495
- return data;
527
+ if (newURL) {
528
+ data.url = newURL;
496
529
  }
497
530
  }
498
- if (!elem[7]) { //QQNT 群聊转发到私聊图
499
- data.file = buildImageFileParam(elem[13].toHex(), elem[25], elem[22], elem[23], elem[20]);
500
- data.url = await offNTPicDown.call(this.c, this.uid, elem[2]);
501
- } else if (elem[7].toHex) { //私图
502
- data.file = buildImageFileParam(elem[7].toHex(), elem[2], elem[9], elem[8], elem[5]);
503
- if (elem[10]) {
504
- if (elem[23])
505
- data.url = await groupNTPicDown.call(this.c, this.gid, elem[23], elem[7]);
506
- else
507
- data.url = await offNTPicDown.call(this.c, this.uid, elem[10]);
531
+ return data;
532
+ }
533
+
534
+ /**
535
+ * 解析图片
536
+ * @private
537
+ * @param {number} type 4|8|3
538
+ * @param {import("../ref").Proto} elem
539
+ */
540
+ async parseImgElem(type, elem) {
541
+ const data = { };
542
+ const dm = type === 3 ? (!!elem[1]) : (type === 4); //私图
543
+ const md5 = dm ? elem[7] : elem[13];
544
+ const fileName = dm ? elem[1] : elem[2];
545
+ const fileid = dm ? elem[3] : elem[7];
546
+ const fileResId = dm ? elem[10] : undefined;
547
+ const extra = dm ? elem[29] : elem[34];
548
+ const ntPath = extra ? String(extra[30]) : null;
549
+
550
+ const size = dm ? elem[2] : elem[25];
551
+ const width = dm ? elem[9] : elem[22];
552
+ const height = dm ? elem[8] : elem[23];
553
+ const imgType = dm ? elem[5] : elem[20];
554
+ data.file = buildImageFileParam(md5.toHex(), size, width, height, imgType);
555
+
556
+ if (this.imgprefix && ntPath && ntPath.startsWith("&rkey=")) {
557
+ data.url = this.imgprefix + String(ntPath) + "&spec=0";
558
+ this.imgprefix = null;
559
+ if (await checkImgUrl(data.url))
560
+ return data;
561
+ }
562
+
563
+ if (dm) { //私图
564
+ if (ntPath && ntPath.startsWith("/download") && ntPath.includes("rkey=")) {
565
+ data.url = "https://gchat.qpic.cn" + ntPath + "&spec=0";
566
+ } else if (elem[15]) {
567
+ data.url = "https://gchat.qpic.cn" + elem[15];
568
+ } else {
569
+ data.url = `https://gchat.qpic.cn/gchatpic_new/0/0-0-${md5.toHex().toUpperCase()}/0`
570
+ }
571
+ if (!(await checkImgUrl(data.url))) {
572
+ // 图片地址失效,尝试发包获取
573
+ if (elem[23]) { //QQNT 私聊合并转发到群聊图
574
+ data.url = await groupNTPicDown.call(this.c, this.gid || 284840486, elem[23], md5);
575
+ } else { //纯私聊图片
576
+ data.url = await offNTPicDown.call(this.c, this.uid, fileResId);
577
+ }
508
578
  }
509
- else if (elem[15])
510
- data.url = "https://c2cpicdw.qpic.cn" + elem[15];
511
579
  } else { //群图
512
- data.file = buildImageFileParam(elem[13].toHex(), elem[25], elem[22], elem[23], elem[20]);
513
- if (elem[16])
580
+ if (ntPath && ntPath.startsWith("/download") && ntPath.includes("rkey=")) {
581
+ data.url = "https://gchat.qpic.cn" + ntPath + "&spec=0";
582
+ } else if (elem[16]) {
514
583
  data.url = "https://gchat.qpic.cn" + elem[16];
515
- else
516
- data.url = `https://gchat.qpic.cn/gchatpic_new/${this.uid}/${code2uin(this.gid)||0}-${elem[7]>>>0}-${elem[13].toHex().toUpperCase()}/0?term=2`;
517
- if (!data.url.includes("gchatpic_new"))
518
- data.url = await groupNTPicDown.call(this.c, this.gid, elem[7], elem[13]);
584
+ } else {
585
+ data.url = `https://gchat.qpic.cn/gchatpic_new/0/0-0-${md5.toHex().toUpperCase()}/0`
586
+ }
587
+ if (!(await checkImgUrl(data.url))) {
588
+ // 图片地址失效,尝试发包获取
589
+ if (fileid) { //纯群聊图片
590
+ data.url = await groupNTPicDown.call(this.c, this.gid || 284840486, fileid, md5);
591
+ } else { //QQNT 群聊合并转发到私聊图
592
+ data.url = await offNTPicDown.call(this.c, this.uid, fileName);
593
+ }
594
+ }
519
595
  }
520
596
  return data;
521
597
  }
package/lib/oicq.js CHANGED
@@ -104,6 +104,7 @@ function pushReqListener(blob, seq) {
104
104
  this.storage.ip = int32ip2str(v[2][0][2]);
105
105
  }
106
106
  }
107
+ this.saveStorageConfig();
107
108
  }
108
109
  }
109
110
 
package/lib/wtlogin/wt.js CHANGED
@@ -47,6 +47,8 @@ class WtLogin {
47
47
  set nickname(val) { this.c.nickname = val; }
48
48
  set sex(val) { this.c.sex = val; }
49
49
  set age(val) { this.c.age = val; }
50
+ get uid() { return this.c.uid; }
51
+ set uid(val) { this.c.uid = val; }
50
52
 
51
53
  /**
52
54
  * @param {import("../ref").Client} c
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.24.1",
5
- "app_version": "1.24.1",
6
- "upday": "2024/01/22",
4
+ "version": "1.24.2",
5
+ "app_version": "1.24.2",
6
+ "upday": "2024/04/22",
7
7
  "protocol_version": "v11",
8
8
  "description": "oicq(https://github.com/takayama-lily/oicq) v1 for Icalingua++",
9
9
  "main": "index.js",