oicq-icalingua-plus-plus 1.26.16 → 1.26.18

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
@@ -215,7 +215,7 @@ export interface MemberInfo extends MemberBaseInfo {
215
215
  export type MessageElem = TextElem | AtElem | FaceElem | BfaceElem | MfaceElem |
216
216
  ImgPttElem | LocationElem | MusicElem | ShareElem | JsonElem | XmlElem |
217
217
  AnonymousElem | ReplyElem | NodeElem | ShakeElem | PokeElem | FileElem | VideoElem |
218
- MiraiElem | RawElem | MarkdownElem;
218
+ MiraiElem | RawElem | MarkdownElem | ButtonElem;
219
219
 
220
220
  export type Sendable = string | MessageElem | Iterable<MessageElem | string>
221
221
 
@@ -409,6 +409,60 @@ export interface MarkdownElem {
409
409
  }
410
410
  }
411
411
  }
412
+
413
+ /** Markdown按钮消息 */
414
+ export interface ButtonElem {
415
+ type: "button",
416
+ data: {
417
+ /** 机器人appid */
418
+ appid: number,
419
+ rows: {
420
+ buttons: Button[]
421
+ }[]
422
+ }
423
+ }
424
+
425
+ export interface Button {
426
+ /** 按钮ID:在一个keyboard消息内设置唯一 */
427
+ id?: string;
428
+ render_data: {
429
+ /** 按钮上的文字 */
430
+ label: string;
431
+ /** 点击后按钮的上文字 */
432
+ visited_label: string;
433
+ /** 按钮样式:0 灰色线框,1 蓝色线框 */
434
+ style: number;
435
+ };
436
+ action: {
437
+ /** 设置 0 跳转按钮:http 或 小程序 客户端识别 scheme,设置 1 回调按钮:回调后台接口, data 传给后台,设置 2 指令按钮:自动在输入框插入 @bot data */
438
+ type: number;
439
+ permission: {
440
+ /** 0 指定用户可操作,1 仅管理者可操作,2 所有人可操作,3 指定身份组可操作(仅频道可用) */
441
+ type: number;
442
+ /** 有权限的用户 id 的列表 */
443
+ specify_user_ids?: Array<string>;
444
+ /** 有权限的身份组 id 的列表(仅频道可用) */
445
+ specify_role_ids?: Array<string>;
446
+ };
447
+ /** 操作相关的数据 */
448
+ data: string;
449
+ /** 指令按钮可用,指令是否带引用回复本消息,默认 false。支持版本 8983 */
450
+ reply?: boolean;
451
+ /** 指令按钮可用,点击按钮后直接自动发送 data,默认 false。支持版本 8983 */
452
+ enter?: boolean;
453
+ /** 本字段仅在指令按钮下有效,设置后后会忽略 action.enter 配置。
454
+ 设置为 1 时 ,点击按钮自动唤起启手Q选图器,其他值暂无效果。
455
+ (仅支持手机端版本 8983+ 的单聊场景,桌面端不支持) */
456
+ anchor?: number;
457
+ /** 【已弃用】可操作点击的次数,默认不限 */
458
+ click_limit?: number;
459
+ /** 【已弃用】指令按钮可用,弹出子频道选择器,默认 false */
460
+ at_bot_show_channel_list?: boolean;
461
+ /** 客户端不支持本action的时候,弹出的toast文案 */
462
+ unsupport_tips: string;
463
+ };
464
+ }
465
+
412
466
  /** 该元素为需要直接发送的原始消息体,JSON 格式 */
413
467
  export interface RawElem {
414
468
  type: "raw",
package/lib/core/nessy.js CHANGED
@@ -134,6 +134,7 @@ async function getRoamingStamp(no_cache = false) {
134
134
  },
135
135
  2: this.uin,
136
136
  3: 1,
137
+ 6: 1,
137
138
  });
138
139
  const blob = await this.sendUni("Faceroam.OpReq", body);
139
140
  const rsp = pb.decode(blob);
@@ -423,8 +423,54 @@ class Parser {
423
423
  }
424
424
  break;
425
425
  case 46: //markdown button
426
- //TODO
427
- return;
426
+ msg.type = "button";
427
+ try {
428
+ const body = elem[2][1];
429
+ const appid = body[2];
430
+ const rows = Array.isArray(body[1]) ? body[1] : [body[1]];
431
+ msg.data = {
432
+ appid: appid,
433
+ rows: [],
434
+ };
435
+ for (const row of rows) {
436
+ const cols = Array.isArray(row[1]) ? row[1] : [row[1]];
437
+ const buttons = [];
438
+ for (const col of cols) {
439
+ /**
440
+ * @type {import("../../index").Button}
441
+ */
442
+ const btn = {
443
+ id: "",
444
+ render_data: {},
445
+ action: {
446
+ permission: {},
447
+ },
448
+ };
449
+ if (col[1]) btn.id = String(col[1]);
450
+ if (col[2]) {
451
+ btn.render_data.label = String(col[2][1] || "");
452
+ btn.render_data.visited_label = String(col[2][2] || "");
453
+ btn.render_data.style = Number(col[2][3]) || 0;
454
+ }
455
+ if (col[3]) {
456
+ btn.action.type = Number(col[3][1]) || 0;
457
+ btn.action.unsupport_tips = String(col[3][4] || "");
458
+ btn.action.data = String(col[3][5] || "");
459
+ btn.action.reply = col[3][7] === 1;
460
+ btn.action.enter = col[3][8] === 1;
461
+ if (col[3][2]) {
462
+ btn.action.permission.type = Number(col[3][2][1]) || 0;
463
+ btn.action.permission.specify_role_ids = col[3][2][2] || [];
464
+ btn.action.permission.specify_user_ids = col[3][2][3] || [];
465
+ }
466
+ }
467
+ buttons.push(btn);
468
+ }
469
+ msg.data.rows.push({ buttons });
470
+ }
471
+ } catch (e) {
472
+ this.c.logger.error("解析markdown button失败", e);
473
+ }
428
474
  break;
429
475
  case 48: //ntv2richmedia
430
476
  const businessType = elem[3];
@@ -567,6 +613,8 @@ class Parser {
567
613
  await this.parsePartialElem(48, elem);
568
614
  } else if (elem[1] == 45) { //markdown
569
615
  await this.parsePartialElem(45, elem[2]);
616
+ } else if (elem[1] == 46) { //markdown button
617
+ await this.parsePartialElem(46, elem[2]);
570
618
  }
571
619
  break;
572
620
  case 45: //reply
package/lib/onlinepush.js CHANGED
@@ -404,8 +404,8 @@ const push732 = {
404
404
  const seq = data[2];
405
405
  const random = data[3];
406
406
  const add = data[4] === 1;
407
- const user_id = data[5];
408
- const operator_id = data[6];
407
+ const user_id = data[5]; //这里NT上线也是uin,不需要转换
408
+ const operator_id = data[6]; //这里NT上线也是uin,不需要转换
409
409
  const user_name = String(data[10]);
410
410
  const operator_name = String(data[9]);
411
411
  this.em("notice.group.essence", {
@@ -497,13 +497,57 @@ const push732 = {
497
497
  set,
498
498
  });
499
499
  }
500
+ case 38: { // 机器人变动
501
+ const data = sub[47];
502
+ const is_join = !!data[2];
503
+ let user_id = is_join ? data[2] : data[3];
504
+ let nickname = "";
505
+ user_id = await uid2uin.call(this, String(user_id)); // 这里全是uid,老上线也是
506
+ try {
507
+ if (is_join) {
508
+ const ginfo = (await this.getGroupInfo(group_id)).data;
509
+ if (!ginfo) return;
510
+ ginfo.member_count++;
511
+ ginfo.last_join_time = timestamp();
512
+ const info = await this.getGroupMemberInfo(group_id, user_id);
513
+ nickname = info.data.nickname || info.data.card || "";
514
+ try {
515
+ if (this.gml.get(group_id).size)
516
+ ginfo.member_count = this.gml.get(group_id).size;
517
+ } catch { }
518
+ this.logger.info(`${user_id}(${nickname}) 加入了群 ${group_id}`);
519
+ this.getGroupMemberList(group_id, true);
520
+ this.em("notice.group.increase", {
521
+ group_id, user_id, nickname
522
+ });
523
+ } else {
524
+ let member, group;
525
+ try {
526
+ member = this.gml.get(group_id).get(user_id);
527
+ this.gml.get(group_id).delete(user_id);
528
+ } catch { }
529
+ this.logger.info(`${user_id}离开了群${group_id}`);
530
+ try {
531
+ this.gl.get(group_id).member_count--;
532
+ } catch { }
533
+ this.em("notice.group.decrease", {
534
+ group_id, user_id,
535
+ operator_id: user_id, dismiss: false,
536
+ member, group, time
537
+ });
538
+ }
539
+ } catch (e) {
540
+ this.logger.debug(e);
541
+ }
542
+ break;
543
+ }
500
544
  default:
501
545
  break;
502
546
  }
503
547
  if (sub[5] && sub[5][2]) {
504
548
  let str = String(sub[5][2]);
505
549
  if (str.includes("获得群主授予的")) {
506
- const user_id = sub[5][5];
550
+ const user_id = sub[5][5]; // 这里NT上线也是uin,不需要转换
507
551
  str = str.substr(0, str.length - 2);
508
552
  let title = str.substr(str.lastIndexOf("获得群主授予的") + 7);
509
553
  const _title = title;
@@ -1025,6 +1069,9 @@ async function NTMsgListener(blob, seq, sso) {
1025
1069
  const user_id = await uid2uin.call(this, String(msg[3]));
1026
1070
  const exit_type = msg[4];
1027
1071
  let operator_id, dismiss = false, member, group;
1072
+ try {
1073
+ member = this.gml.get(group_id).get(user_id);
1074
+ } catch { }
1028
1075
  if (exit_type === 0x82 || exit_type === 0x2) {
1029
1076
  operator_id = user_id;
1030
1077
  try {
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.16",
5
- "app_version": "1.26.16",
6
- "upday": "2026/08/09",
4
+ "version": "1.26.18",
5
+ "app_version": "1.26.18",
6
+ "upday": "2026/08/17",
7
7
  "protocol_version": "v11",
8
8
  "description": "oicq(https://github.com/takayama-lily/oicq) v1 for Icalingua++",
9
9
  "main": "index.js",