@softtynet/zca-js 2.3.6 → 2.3.7

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.
Files changed (51) hide show
  1. package/README.md +25 -2
  2. package/dist/apis/business/blockOA.d.ts +2 -0
  3. package/dist/apis/business/blockOA.js +28 -0
  4. package/dist/apis/business/convertOAIds.d.ts +4 -0
  5. package/dist/apis/business/convertOAIds.js +26 -0
  6. package/dist/apis/business/getOAProfile.d.ts +13 -0
  7. package/dist/apis/business/getOAProfile.js +64 -0
  8. package/dist/apis/business/unblockOA.d.ts +2 -0
  9. package/dist/apis/business/unblockOA.js +28 -0
  10. package/dist/apis/chat/preloadConversations.d.ts +3 -2
  11. package/dist/apis/chat/preloadConversations.js +14 -0
  12. package/dist/apis/sync/getSyncData.js +1 -1
  13. package/dist/apis.d.ts +12 -0
  14. package/dist/apis.js +20 -0
  15. package/dist/cjs/apis/business/blockOA.cjs +33 -0
  16. package/dist/cjs/apis/business/convertOAIds.cjs +31 -0
  17. package/dist/cjs/apis/business/getOAProfile.cjs +69 -0
  18. package/dist/cjs/apis/business/unblockOA.cjs +33 -0
  19. package/dist/cjs/apis/chat/preloadConversations.cjs +14 -0
  20. package/dist/cjs/apis/sync/getSyncData.cjs +20 -1
  21. package/dist/cjs/apis.cjs +20 -0
  22. package/dist/cjs/constants/defaults.cjs +2 -2
  23. package/dist/cjs/constants/protocol.cjs +1 -1
  24. package/dist/cjs/index.cjs +10 -0
  25. package/dist/cjs/models/Message.cjs +16 -1
  26. package/dist/cjs/models/OAMessage.cjs +375 -0
  27. package/dist/cjs/realtime/handlers/control.handler.cjs +9 -1
  28. package/dist/cjs/realtime/handlers/message.handler.cjs +103 -37
  29. package/dist/cjs/realtime/listener.cjs +39 -18
  30. package/dist/cjs/utils.cjs +21 -2
  31. package/dist/cjs/zalo.cjs +21 -2
  32. package/dist/constants/defaults.d.ts +2 -2
  33. package/dist/constants/defaults.js +2 -2
  34. package/dist/constants/protocol.d.ts +1 -1
  35. package/dist/constants/protocol.js +1 -1
  36. package/dist/index.d.ts +4 -0
  37. package/dist/models/Message.d.ts +12 -1
  38. package/dist/models/Message.js +16 -1
  39. package/dist/models/OAMessage.d.ts +221 -0
  40. package/dist/models/OAMessage.js +363 -0
  41. package/dist/models/index.d.ts +1 -0
  42. package/dist/models/index.js +1 -0
  43. package/dist/realtime/handlers/control.handler.js +9 -1
  44. package/dist/realtime/handlers/message.handler.js +103 -37
  45. package/dist/realtime/listener.d.ts +9 -3
  46. package/dist/realtime/listener.js +39 -18
  47. package/dist/realtime/types.d.ts +15 -1
  48. package/dist/utils.js +1 -1
  49. package/dist/zalo.d.ts +1 -1
  50. package/dist/zalo.js +1 -1
  51. package/package.json +2 -1
@@ -43,6 +43,7 @@ const preloadConversationsFactory = utils.apiFactory()((api, ctx, utils) => {
43
43
  data = JSON.parse(data);
44
44
  }
45
45
  const messages = [];
46
+ const pageMessages = [];
46
47
  const seenMsgIds = new Set();
47
48
  if (Array.isArray(data.msgs)) {
48
49
  for (const msg of data.msgs) {
@@ -54,6 +55,18 @@ const preloadConversationsFactory = utils.apiFactory()((api, ctx, utils) => {
54
55
  messages.push(new Message.UserMessage(ctx.uid, msg));
55
56
  }
56
57
  }
58
+ if (Array.isArray(data.pageMsgs)) {
59
+ for (const msg of data.pageMsgs) {
60
+ const id = String(msg.msgId ?? "");
61
+ if (id && seenMsgIds.has(id))
62
+ continue;
63
+ if (id)
64
+ seenMsgIds.add(id);
65
+ const userMsg = new Message.UserMessage(ctx.uid, msg, true);
66
+ pageMessages.push(userMsg);
67
+ messages.push(userMsg);
68
+ }
69
+ }
57
70
  if (Array.isArray(data.groupMsgs)) {
58
71
  for (const msg of data.groupMsgs) {
59
72
  const id = String(msg.msgId ?? "");
@@ -87,6 +100,7 @@ const preloadConversationsFactory = utils.apiFactory()((api, ctx, utils) => {
87
100
  return {
88
101
  ...data,
89
102
  messages,
103
+ pageMessages,
90
104
  unreadCleared,
91
105
  };
92
106
  });
@@ -7,10 +7,29 @@ var ZaloApiError = require('../../Errors/ZaloApiError.cjs');
7
7
  var utils = require('../../utils.cjs');
8
8
  var domains = require('../../constants/domains.cjs');
9
9
 
10
+ function _interopNamespaceDefault(e) {
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var toughCookie__namespace = /*#__PURE__*/_interopNamespaceDefault(toughCookie);
28
+
10
29
  const getSyncDataFactory = utils.apiFactory()((api, ctx, utils) => {
11
30
  async function downloadFileStream(url) {
12
31
  if (!ctx.cookie)
13
- ctx.cookie = new toughCookie.CookieJar();
32
+ ctx.cookie = new toughCookie__namespace.CookieJar();
14
33
  const cookie = ctx.cookie;
15
34
  const manualHeaders = {
16
35
  "Cache-Control": "no-store, must-revalidate",
package/dist/cjs/apis.cjs CHANGED
@@ -15,6 +15,7 @@ var analyseGroup = require('./apis/group/analyseGroup.cjs');
15
15
  var answerAckVoiceCall = require('./apis/call/answerAckVoiceCall.cjs');
16
16
  var answerGroupCall = require('./apis/call/answerGroupCall.cjs');
17
17
  var answerVoiceCall = require('./apis/call/answerVoiceCall.cjs');
18
+ var blockOA = require('./apis/business/blockOA.cjs');
18
19
  var blockUser = require('./apis/friend/blockUser.cjs');
19
20
  var blockViewFeed = require('./apis/friend/blockViewFeed.cjs');
20
21
  var broadcastGroupCallState = require('./apis/call/broadcastGroupCallState.cjs');
@@ -28,6 +29,7 @@ var changeGroupName = require('./apis/group/changeGroupName.cjs');
28
29
  var changeGroupOwner = require('./apis/group/changeGroupOwner.cjs');
29
30
  var checkQuotaCommunityMember = require('./apis/group/checkQuotaCommunityMember.cjs');
30
31
  var clearUnreadFriendRequests = require('./apis/friend/clearUnreadFriendRequests.cjs');
32
+ var convertOAIds = require('./apis/business/convertOAIds.cjs');
31
33
  var createAutoReply = require('./apis/business/createAutoReply.cjs');
32
34
  var createBankAccount = require('./apis/business/createBankAccount.cjs');
33
35
  var createCatalog = require('./apis/business/createCatalog.cjs');
@@ -129,6 +131,7 @@ var getMultiGroupCloudMessages = require('./apis/group/getMultiGroupCloudMessage
129
131
  var getMultiUsersByPhones = require('./apis/friend/getMultiUsersByPhones.cjs');
130
132
  var getMute = require('./apis/chat/getMute.cjs');
131
133
  var getNoisedIds = require('./apis/auth/getNoisedIds.cjs');
134
+ var getOAProfile = require('./apis/business/getOAProfile.cjs');
132
135
  var getOwnId = require('./apis/auth/getOwnId.cjs');
133
136
  var getPendingGroupMembers = require('./apis/group/getPendingGroupMembers.cjs');
134
137
  var getPersonalizedStickerList = require('./apis/sticker/getPersonalizedStickerList.cjs');
@@ -224,6 +227,7 @@ var setMute = require('./apis/chat/setMute.cjs');
224
227
  var setPinnedConversations = require('./apis/chat/setPinnedConversations.cjs');
225
228
  var sharePoll = require('./apis/board/sharePoll.cjs');
226
229
  var shareScreenGroupCall = require('./apis/call/shareScreenGroupCall.cjs');
230
+ var unblockOA = require('./apis/business/unblockOA.cjs');
227
231
  var unblockUser = require('./apis/friend/unblockUser.cjs');
228
232
  var undo = require('./apis/chat/undo.cjs');
229
233
  var undoFriendRequest = require('./apis/friend/undoFriendRequest.cjs');
@@ -322,6 +326,8 @@ class API {
322
326
  if (!this._domains.business) {
323
327
  const createNamespace = (api) => ({
324
328
  get addQuickMessage() { return api.addQuickMessage; },
329
+ get blockOA() { return api.blockOA; },
330
+ get convertOAIds() { return api.convertOAIds; },
325
331
  get createAutoReply() { return api.createAutoReply; },
326
332
  get createBankAccount() { return api.createBankAccount; },
327
333
  get createCatalog() { return api.createCatalog; },
@@ -336,10 +342,12 @@ class API {
336
342
  get getLabels() { return api.getLabels; },
337
343
  get getListBank() { return api.getListBank; },
338
344
  get getListBankAccount() { return api.getListBankAccount; },
345
+ get getOAProfile() { return api.getOAProfile; },
339
346
  get getProductCatalogList() { return api.getProductCatalogList; },
340
347
  get getQuickMessageList() { return api.getQuickMessageList; },
341
348
  get registerCatalog() { return api.registerCatalog; },
342
349
  get removeQuickMessage() { return api.removeQuickMessage; },
350
+ get unblockOA() { return api.unblockOA; },
343
351
  get updateAutoReply() { return api.updateAutoReply; },
344
352
  get updateBankAccount() { return api.updateBankAccount; },
345
353
  get updateCatalog() { return api.updateCatalog; },
@@ -662,6 +670,9 @@ class API {
662
670
  get answerVoiceCall() {
663
671
  return (this._cache.answerVoiceCall ??= answerVoiceCall.answerVoiceCallFactory(this.ctx, this));
664
672
  }
673
+ get blockOA() {
674
+ return (this._cache.blockOA ??= blockOA.blockOAFactory(this.ctx, this));
675
+ }
665
676
  get blockUser() {
666
677
  return (this._cache.blockUser ??= blockUser.blockUserFactory(this.ctx, this));
667
678
  }
@@ -701,6 +712,9 @@ class API {
701
712
  get clearUnreadFriendRequests() {
702
713
  return (this._cache.clearUnreadFriendRequests ??= clearUnreadFriendRequests.clearUnreadFriendRequestsFactory(this.ctx, this));
703
714
  }
715
+ get convertOAIds() {
716
+ return (this._cache.convertOAIds ??= convertOAIds.convertOAIdsFactory(this.ctx, this));
717
+ }
704
718
  get createAutoReply() {
705
719
  return (this._cache.createAutoReply ??= createAutoReply.createAutoReplyFactory(this.ctx, this));
706
720
  }
@@ -1004,6 +1018,9 @@ class API {
1004
1018
  get getNoisedIds() {
1005
1019
  return (this._cache.getNoisedIds ??= getNoisedIds.getNoisedIdsFactory(this.ctx, this));
1006
1020
  }
1021
+ get getOAProfile() {
1022
+ return (this._cache.getOAProfile ??= getOAProfile.getOAProfileFactory(this.ctx, this));
1023
+ }
1007
1024
  get getOwnId() {
1008
1025
  return (this._cache.getOwnId ??= getOwnId.getOwnIdFactory(this.ctx, this));
1009
1026
  }
@@ -1289,6 +1306,9 @@ class API {
1289
1306
  get shareScreenGroupCall() {
1290
1307
  return (this._cache.shareScreenGroupCall ??= shareScreenGroupCall.shareScreenGroupCallFactory(this.ctx, this));
1291
1308
  }
1309
+ get unblockOA() {
1310
+ return (this._cache.unblockOA ??= unblockOA.unblockOAFactory(this.ctx, this));
1311
+ }
1292
1312
  get unblockUser() {
1293
1313
  return (this._cache.unblockUser ??= unblockUser.unblockUserFactory(this.ctx, this));
1294
1314
  }
@@ -37,8 +37,8 @@ const ZALO_DEFAULTS = {
37
37
  /** Số lần lỗi 1006 liên tiếp trước khi xoay sang WebSocket endpoint tiếp theo */
38
38
  CONSECUTIVE_1006_ROTATE_THRESHOLD: 2,
39
39
  /** Con trỏ mặc định khi bắt đầu kéo hàng đợi tin nhắn cũ (cmd 510 / 511) */
40
- OLD_MSG_INITIAL_CURSOR: "10000000000000000",
41
- INITIAL_OLD_MESSAGES_CURSOR: "10000000000000000",
40
+ OLD_MSG_INITIAL_CURSOR: "1",
41
+ INITIAL_OLD_MESSAGES_CURSOR: "1",
42
42
  },
43
43
  UPLOAD: {
44
44
  /** Thời gian sống tối đa của uploadCallback trong bộ nhớ trước khi tự hủy (5 phút) */
@@ -12,7 +12,7 @@ const ZALO_PROTOCOL = {
12
12
  API_VERSION: 689,
13
13
  DEFAULT_API_VERSION: 689,
14
14
  /** Phiên bản SDK hiện tại */
15
- SDK_VERSION: "2.3.6",
15
+ SDK_VERSION: "2.3.7",
16
16
  /** Mã zpw_type đặc thù dành riêng cho phân hệ Gọi Thoại & Video (binary C++ ZaloCall) */
17
17
  VOICECALL_API_TYPE: 24,
18
18
  /** Phiên bản mã hóa ZCID (ParamDecryptor) */
@@ -22,6 +22,7 @@ var ZBusiness = require('./models/ZBusiness.cjs');
22
22
  var ClearUnread = require('./models/ClearUnread.cjs');
23
23
  var SyncEvent = require('./models/SyncEvent.cjs');
24
24
  var CallEvent = require('./models/CallEvent.cjs');
25
+ var OAMessage = require('./models/OAMessage.cjs');
25
26
  var protocol = require('./constants/protocol.cjs');
26
27
  var domains = require('./constants/domains.cjs');
27
28
  var defaults = require('./constants/defaults.cjs');
@@ -145,6 +146,15 @@ Object.defineProperty(exports, "CallEventType", {
145
146
  get: function () { return CallEvent.CallEventType; }
146
147
  });
147
148
  exports.initializeCallEvent = CallEvent.initializeCallEvent;
149
+ exports.formatOAHighlightText = OAMessage.formatOAHighlightText;
150
+ exports.initializeOAEvent = OAMessage.initializeOAEvent;
151
+ exports.isOAProfile = OAMessage.isOAProfile;
152
+ exports.isOAVerified = OAMessage.isOAVerified;
153
+ exports.isZaloBot = OAMessage.isZaloBot;
154
+ exports.normalizeOAProfile = OAMessage.normalizeOAProfile;
155
+ exports.parseOADnrStatus = OAMessage.parseOADnrStatus;
156
+ exports.parseOAFooter = OAMessage.parseOAFooter;
157
+ exports.parseOAMessage = OAMessage.parseOAMessage;
148
158
  exports.ZALO_PROTOCOL = protocol.ZALO_PROTOCOL;
149
159
  exports.ZALO_DOMAINS = domains.ZALO_DOMAINS;
150
160
  exports.ZALO_DEFAULTS = defaults.ZALO_DEFAULTS;
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var Enum = require('./Enum.cjs');
4
+ var OAMessage = require('./OAMessage.cjs');
4
5
 
5
6
  class UserMessage {
6
7
  type = Enum.ThreadType.User;
@@ -10,7 +11,11 @@ class UserMessage {
10
11
  * true if the message is sent by the logged in account
11
12
  */
12
13
  isSelf;
13
- constructor(uid, data) {
14
+ /**
15
+ * Dữ liệu đã giải mã nếu đây là tin nhắn từ Zalo Official Account (OA) (`pageMsgs`, `footerv2`, `chat.list.action`, `msginfo.actionlist`, hoặc `chat.ecard`).
16
+ */
17
+ oaData;
18
+ constructor(uid, data, isPageMsg = false) {
14
19
  this.data = data;
15
20
  this.threadId = data.uidFrom == "0" ? data.idTo : data.uidFrom;
16
21
  this.isSelf = data.uidFrom == "0";
@@ -21,6 +26,16 @@ class UserMessage {
21
26
  if (data.quote) {
22
27
  data.quote.ownerId = String(data.quote.ownerId);
23
28
  }
29
+ const parsedOA = OAMessage.parseOAMessage(data, { isPageMsg });
30
+ if (parsedOA) {
31
+ this.oaData = parsedOA;
32
+ }
33
+ }
34
+ /**
35
+ * Kiểm tra xem tin nhắn có phải là tin nhắn Zalo Official Account (OA) hay không.
36
+ */
37
+ isOAMessage() {
38
+ return this.oaData !== undefined;
24
39
  }
25
40
  }
26
41
  class GroupMessage {
@@ -0,0 +1,375 @@
1
+ 'use strict';
2
+
3
+ function safeJsonParse(input) {
4
+ if (input === null || input === undefined)
5
+ return undefined;
6
+ if (typeof input === "object")
7
+ return input;
8
+ if (typeof input !== "string")
9
+ return undefined;
10
+ const trimmed = input.trim();
11
+ if (!trimmed)
12
+ return undefined;
13
+ try {
14
+ return JSON.parse(trimmed);
15
+ }
16
+ catch {
17
+ return undefined;
18
+ }
19
+ }
20
+ function resolveHighlightValue(item) {
21
+ if (typeof item === "string")
22
+ return item;
23
+ if (typeof item === "number")
24
+ return String(item);
25
+ if (item && typeof item === "object") {
26
+ if (typeof item.dpn === "string" && item.dpn.length > 0)
27
+ return item.dpn;
28
+ if (typeof item.text === "string" && item.text.length > 0)
29
+ return item.text;
30
+ if (typeof item.value === "string" && item.value.length > 0)
31
+ return item.value;
32
+ if (typeof item.uid === "string" && item.uid.length > 0)
33
+ return item.uid;
34
+ if (typeof item.ts === "number")
35
+ return new Date(item.ts).toISOString();
36
+ }
37
+ return "";
38
+ }
39
+ /**
40
+ * Điền các biến `%1$s`, `%2$s`... và `$1$s`, `$2$s`... trong mẫu chuỗi thông báo Zalo OA
41
+ * theo chuẩn hàm `_renderItemReplace` trên Zalo PC.
42
+ */
43
+ function formatOAHighlightText(templateText, highLightsV2) {
44
+ if (!templateText || typeof templateText !== "string")
45
+ return "";
46
+ if (!Array.isArray(highLightsV2) || highLightsV2.length === 0)
47
+ return templateText;
48
+ let result = templateText;
49
+ highLightsV2.forEach((item, index) => {
50
+ const val = resolveHighlightValue(item);
51
+ const idx1 = index + 1;
52
+ result = result.replaceAll(`%${idx1}$s`, val).replaceAll(`$${idx1}$s`, val);
53
+ });
54
+ return result;
55
+ }
56
+ /**
57
+ * Bóc tách cấu hình chân trang tương tác `footerv2` / `footer` của tin nhắn Zalo OA
58
+ * (`footerList`, `canForward`, `type`).
59
+ */
60
+ function parseOAFooter(rawFooter) {
61
+ const parsed = safeJsonParse(rawFooter);
62
+ if (!parsed || typeof parsed !== "object")
63
+ return undefined;
64
+ const dataObj = safeJsonParse(parsed.data) ?? parsed;
65
+ const footerListRaw = Array.isArray(dataObj.footerList)
66
+ ? dataObj.footerList
67
+ : Array.isArray(parsed.footerList)
68
+ ? parsed.footerList
69
+ : [];
70
+ const footerList = footerListRaw
71
+ .map((entry) => safeJsonParse(entry))
72
+ .filter((entry) => Boolean(entry && typeof entry === "object"));
73
+ const canForwardRaw = dataObj.canForward ?? parsed.canForward;
74
+ const canForward = canForwardRaw === undefined ? true : Boolean(Number(canForwardRaw));
75
+ const type = typeof parsed.type === "number" ? parsed.type : undefined;
76
+ return {
77
+ type,
78
+ canForward,
79
+ footerList,
80
+ raw: parsed,
81
+ };
82
+ }
83
+ /**
84
+ * Kiểm tra xem một hồ sơ người dùng có phải là Zalo Official Account (OA) hay không.
85
+ * Theo chuẩn `isOA(profile)` trên Zalo PC: `profile.type > 0` hoặc có `profile.oaInfo`.
86
+ */
87
+ function isOAProfile(profile) {
88
+ if (!profile || typeof profile !== "object")
89
+ return false;
90
+ if (typeof profile.type === "number" && profile.type > 0)
91
+ return true;
92
+ return Boolean(profile.oaInfo && typeof profile.oaInfo === "object");
93
+ }
94
+ /**
95
+ * Kiểm tra xem Zalo OA có tích vàng xác thực (Verified OA) hay không.
96
+ * Theo chuẩn `isOAVerifiedByInfo(profile)` trên Zalo PC (`@10388120`):
97
+ * Chuyển `profile.type` sang nhị phân (`Number(type).toString(2)`), kiểm tra độ dài >= 3 và ký tự index `2` là `"1"`.
98
+ * Ví dụ: `type = 7` (`"111"`) -> Verified OA; `type = 5` (`"101"`) -> Verified OA.
99
+ */
100
+ function isOAVerified(profileOrType) {
101
+ const typeNum = typeof profileOrType === "number"
102
+ ? profileOrType
103
+ : typeof profileOrType?.type === "number"
104
+ ? profileOrType.type
105
+ : 0;
106
+ if (!(typeNum > 0))
107
+ return false;
108
+ const bin = Number(typeNum).toString(2);
109
+ return bin.length >= 3 && bin[2] === "1";
110
+ }
111
+ /**
112
+ * Kiểm tra xem tài khoản có phải là Zalo Bot hay không.
113
+ * Theo chuẩn `isZaloBotByInfo(profile)` trên Zalo PC (`@10388180`):
114
+ * Chuyển `profile.type` sang nhị phân (`Number(type).toString(2)`), kiểm tra độ dài >= 4 và ký tự đầu tiên là `"1"`.
115
+ */
116
+ function isZaloBot(profileOrType) {
117
+ const typeNum = typeof profileOrType === "number"
118
+ ? profileOrType
119
+ : typeof profileOrType?.type === "number"
120
+ ? profileOrType.type
121
+ : 0;
122
+ if (!(typeNum > 0))
123
+ return false;
124
+ const bin = Number(typeNum).toString(2);
125
+ return bin.length >= 4 && bin[0] === "1";
126
+ }
127
+ /**
128
+ * Giải mã trạng thái chặn & Do-Not-Disturb (`oa_status`) của Zalo OA
129
+ * theo chuẩn `_getOAStatus` trên Zalo PC (`@1719856`).
130
+ */
131
+ function parseOADnrStatus(oaStatus, fallbackIsBlocked) {
132
+ const parsed = safeJsonParse(oaStatus);
133
+ const isBlocked = parsed && "blocked" in parsed
134
+ ? Boolean(Number(parsed.blocked))
135
+ : Boolean(Number(fallbackIsBlocked ?? 0));
136
+ const enableDnr = parsed && "enable_dnr" in parsed ? Boolean(Number(parsed.enable_dnr)) : false;
137
+ const dnrType = parsed && typeof parsed.dnr_type === "number" ? parsed.dnr_type : null;
138
+ let dnrText = null;
139
+ if (parsed && parsed.dnr_string) {
140
+ if (typeof parsed.dnr_string === "string") {
141
+ dnrText = parsed.dnr_string;
142
+ }
143
+ else if (typeof parsed.dnr_string === "object" && parsed.dnr_string !== null) {
144
+ const langObj = parsed.dnr_string;
145
+ dnrText =
146
+ (typeof langObj.vi === "string" && langObj.vi) ||
147
+ (typeof langObj.en === "string" && langObj.en) ||
148
+ null;
149
+ }
150
+ }
151
+ return {
152
+ isBlocked,
153
+ enableDnr,
154
+ dnrType,
155
+ dnrText,
156
+ raw: parsed ?? oaStatus ?? null,
157
+ };
158
+ }
159
+ /**
160
+ * Chuẩn hóa dữ liệu hồ sơ Zalo Official Account (`getUserInfo` + `getBizAccount`) thành cấu trúc `OAProfileInfo` đồng nhất.
161
+ */
162
+ function normalizeOAProfile(oaId, rawProfile, bizAccount) {
163
+ const prof = rawProfile ?? {};
164
+ const oaInfo = (safeJsonParse(prof.oaInfo) ?? null);
165
+ const biz = safeJsonParse(bizAccount?.biz) ?? {};
166
+ const type = typeof prof.type === "number" ? prof.type : oaInfo ? 1 : 0;
167
+ const dnrStatus = parseOADnrStatus(prof.oa_status, prof.isBlocked);
168
+ const pickFieldValue = (entry, fallback) => {
169
+ if (entry && typeof entry === "object" && typeof entry.value === "string" && entry.value.trim()) {
170
+ return entry.value.trim();
171
+ }
172
+ if (typeof fallback === "string" && fallback.trim()) {
173
+ return fallback.trim();
174
+ }
175
+ return "";
176
+ };
177
+ const cateObj = safeJsonParse(biz.cate);
178
+ const fallbackCategory = (typeof cateObj?.name === "string" && cateObj.name) ||
179
+ (typeof cateObj?.vi === "string" && cateObj.vi) ||
180
+ (typeof biz.cate === "string" ? biz.cate : "");
181
+ const displayName = (typeof prof.displayName === "string" && prof.displayName) ||
182
+ (typeof prof.zaloName === "string" && prof.zaloName) ||
183
+ oaId;
184
+ const zaloName = (typeof prof.zaloName === "string" && prof.zaloName) ||
185
+ displayName;
186
+ return {
187
+ oaId: String(prof.userId ?? oaId),
188
+ displayName,
189
+ zaloName,
190
+ username: typeof prof.username === "string" ? prof.username : String(prof.userId ?? oaId),
191
+ avatar: typeof prof.avatar === "string" ? prof.avatar : "",
192
+ cover: typeof prof.cover === "string" ? prof.cover : "",
193
+ globalId: typeof prof.globalId === "string" ? prof.globalId : "",
194
+ type,
195
+ isOA: isOAProfile({ type, oaInfo }),
196
+ isVerified: isOAVerified(type),
197
+ isZaloBot: isZaloBot(type),
198
+ isBlocked: dnrStatus.isBlocked,
199
+ isGroupedMsg: Number(oaInfo?.group_msg ?? 0) === 1,
200
+ description: (typeof oaInfo?.description === "string" && oaInfo.description.trim()) ||
201
+ (typeof biz.desc === "string" && biz.desc.trim()) ||
202
+ (typeof prof.status === "string" && prof.status.trim()) ||
203
+ "",
204
+ category: pickFieldValue(oaInfo?.category, fallbackCategory),
205
+ address: pickFieldValue(oaInfo?.address, biz.addr),
206
+ website: pickFieldValue(oaInfo?.website, biz.website),
207
+ hotline: pickFieldValue(oaInfo?.hotline, prof.phoneNumber),
208
+ openTime: pickFieldValue(oaInfo?.openTime),
209
+ email: typeof biz.email === "string" ? biz.email.trim() : "",
210
+ appUrl: typeof oaInfo?.appUrl === "string" ? oaInfo.appUrl : null,
211
+ dnrStatus,
212
+ oaInfo,
213
+ rawProfile: prof,
214
+ bizAccount: bizAccount ?? null,
215
+ };
216
+ }
217
+ /**
218
+ * Bóc tách và giải mã các loại tin nhắn từ Zalo Official Account (OA):
219
+ * - Danh sách bài viết / thẻ hành động OA (`chat.list.action`, bao gồm `l.a.header.full`, `l.a.normal`)
220
+ * - Thông báo sự kiện OA (`webchat` với `action === "msginfo.actionlist"` hoặc `"msginfo.action"`)
221
+ * - Thiệp chúc mừng / sự kiện OA (`chat.ecard`)
222
+ * - Tin nhắn hội thoại trực tiếp 2 chiều với OA (`pageMsgs`, có `footerv2`, hoặc `topOut === "1"`)
223
+ *
224
+ * Trả về `undefined` nếu tin nhắn là tin nhắn cá nhân thông thường không thuộc Zalo OA.
225
+ */
226
+ function parseOAMessage(msg, options) {
227
+ if (!msg || typeof msg.msgType !== "string")
228
+ return undefined;
229
+ const msgType = msg.msgType.trim();
230
+ const rawContent = msg.content;
231
+ const contentObj = typeof rawContent === "object" && rawContent !== null
232
+ ? rawContent
233
+ : safeJsonParse(rawContent);
234
+ const footer = parseOAFooter(msg.footerv2 ?? msg.footer);
235
+ const topOut = msg.topOut === "1" || msg.topOut === 1 ? true : undefined;
236
+ // 1. Danh sách bài viết / Thẻ hành động OA (`chat.list.action`)
237
+ if (msgType === "chat.list.action") {
238
+ let items = [];
239
+ if (Array.isArray(rawContent)) {
240
+ items = rawContent;
241
+ }
242
+ else if (Array.isArray(contentObj)) {
243
+ items = contentObj;
244
+ }
245
+ else if (contentObj && typeof contentObj === "object") {
246
+ if (Array.isArray(contentObj.items)) {
247
+ items = contentObj.items;
248
+ }
249
+ else if (Array.isArray(contentObj.elements)) {
250
+ items = contentObj.elements;
251
+ }
252
+ else {
253
+ const parsedParams = safeJsonParse(contentObj.params);
254
+ if (Array.isArray(parsedParams)) {
255
+ items = parsedParams;
256
+ }
257
+ else {
258
+ items = [contentObj];
259
+ }
260
+ }
261
+ }
262
+ const itemLines = items
263
+ .map((it) => {
264
+ const t = typeof it.title === "string" ? it.title.trim() : "";
265
+ const d = typeof it.description === "string" ? it.description.trim() : "";
266
+ if (t && d)
267
+ return `${t}: ${d}`;
268
+ return t || d;
269
+ })
270
+ .filter(Boolean);
271
+ return {
272
+ kind: "oa_list",
273
+ text: itemLines.join("\n"),
274
+ bundleData: contentObj ?? {},
275
+ items,
276
+ footer,
277
+ topOut,
278
+ canReply: true,
279
+ };
280
+ }
281
+ // 2. Tin nhắn thông báo / ActionList OA (`webchat` với `action === "msginfo.actionlist"` hoặc `"msginfo.action"`)
282
+ if ((msgType === "webchat" || msgType === "msginfo.actionlist" || msgType === "msginfo.action") &&
283
+ contentObj &&
284
+ (contentObj.action === "msginfo.actionlist" ||
285
+ contentObj.action === "msginfo.action" ||
286
+ msgType.startsWith("msginfo."))) {
287
+ const paramsObj = safeJsonParse(contentObj.params) ?? contentObj;
288
+ const highLights = paramsObj.highLightsV2;
289
+ const textVi = paramsObj.msg?.vi ? formatOAHighlightText(paramsObj.msg.vi, highLights) : undefined;
290
+ const textEn = paramsObj.msg?.en ? formatOAHighlightText(paramsObj.msg.en, highLights) : undefined;
291
+ const previewTxt = typeof paramsObj.previewTxt === "string"
292
+ ? formatOAHighlightText(paramsObj.previewTxt, highLights)
293
+ : undefined;
294
+ const fallbackTitle = typeof contentObj.title === "string" ? contentObj.title : "";
295
+ const fallbackDesc = typeof contentObj.description === "string" ? contentObj.description : "";
296
+ return {
297
+ kind: "info_chat",
298
+ text: textVi || textEn || previewTxt || [fallbackTitle, fallbackDesc].filter(Boolean).join(" — "),
299
+ textVi,
300
+ textEn,
301
+ bundleData: paramsObj,
302
+ footer,
303
+ topOut,
304
+ metadata: { customMsg: paramsObj },
305
+ };
306
+ }
307
+ // 3. Thiệp chúc mừng / ECard OA (`chat.ecard`)
308
+ if (msgType === "chat.ecard") {
309
+ const paramsObj = safeJsonParse(contentObj?.params) ?? {};
310
+ const highLights = paramsObj.highLightsV2;
311
+ const textVi = paramsObj.msg?.vi ? formatOAHighlightText(paramsObj.msg.vi, highLights) : undefined;
312
+ const textEn = paramsObj.msg?.en ? formatOAHighlightText(paramsObj.msg.en, highLights) : undefined;
313
+ const previewTxt = typeof paramsObj.previewTxt === "string"
314
+ ? formatOAHighlightText(paramsObj.previewTxt, highLights)
315
+ : undefined;
316
+ const fallbackTitle = typeof contentObj?.title === "string" ? contentObj.title.trim() : "";
317
+ const fallbackDesc = typeof contentObj?.description === "string" ? contentObj.description.trim() : "";
318
+ return {
319
+ kind: "ecard",
320
+ text: textVi || textEn || previewTxt || [fallbackTitle, fallbackDesc].filter(Boolean).join(" — "),
321
+ textVi,
322
+ textEn,
323
+ bundleData: (contentObj ?? paramsObj),
324
+ footer,
325
+ topOut,
326
+ metadata: { customMsg: paramsObj },
327
+ };
328
+ }
329
+ // 4. Tin nhắn hội thoại trực tiếp 2 chiều với Zalo Official Account (`pageMsgs` / `footerv2` / `topOut === "1"`)
330
+ const hasOAMarkers = Boolean(options?.isPageMsg) || footer !== undefined || Boolean(topOut);
331
+ if (hasOAMarkers) {
332
+ let text = "";
333
+ if (typeof rawContent === "string") {
334
+ text = rawContent;
335
+ }
336
+ else if (contentObj && typeof contentObj === "object") {
337
+ const title = typeof contentObj.title === "string" ? contentObj.title.trim() : "";
338
+ const desc = typeof contentObj.description === "string" ? contentObj.description.trim() : "";
339
+ const href = typeof contentObj.href === "string" ? contentObj.href.trim() : "";
340
+ text = [title, desc].filter(Boolean).join(" — ") || href || JSON.stringify(contentObj);
341
+ }
342
+ return {
343
+ kind: "oa_chat",
344
+ text,
345
+ bundleData: contentObj ?? {},
346
+ footer,
347
+ topOut,
348
+ canReply: true,
349
+ };
350
+ }
351
+ return undefined;
352
+ }
353
+ function initializeOAEvent(data, act) {
354
+ const uids = Array.isArray(data?.uids) ? data.uids.map(String) : [];
355
+ return {
356
+ act,
357
+ act_type: "oa",
358
+ data: {
359
+ ...data,
360
+ uids,
361
+ },
362
+ isBlocked: act === "block",
363
+ oaIds: uids,
364
+ };
365
+ }
366
+
367
+ exports.formatOAHighlightText = formatOAHighlightText;
368
+ exports.initializeOAEvent = initializeOAEvent;
369
+ exports.isOAProfile = isOAProfile;
370
+ exports.isOAVerified = isOAVerified;
371
+ exports.isZaloBot = isZaloBot;
372
+ exports.normalizeOAProfile = normalizeOAProfile;
373
+ exports.parseOADnrStatus = parseOADnrStatus;
374
+ exports.parseOAFooter = parseOAFooter;
375
+ exports.parseOAMessage = parseOAMessage;
@@ -3,12 +3,13 @@
3
3
  var CallEvent = require('../../models/CallEvent.cjs');
4
4
  var FriendEvent = require('../../models/FriendEvent.cjs');
5
5
  var GroupEvent = require('../../models/GroupEvent.cjs');
6
+ var OAMessage = require('../../models/OAMessage.cjs');
6
7
  var SyncEvent = require('../../models/SyncEvent.cjs');
7
8
  var utils = require('../../utils.cjs');
8
9
  var wsCipher = require('../../crypto/ws-cipher.cjs');
9
10
 
10
11
  async function handleRealtimeControlPacket(listener, ctx, selfListen, version, cmd, subCmd, parsed, cipherKey) {
11
- if (version == 1 && (cmd == 601 || cmd == 603 || cmd == 604) && subCmd == 0) {
12
+ if (version == 1 && (cmd == 601 || cmd == 602 || cmd == 603 || cmd == 604) && (subCmd == 0 || subCmd == 1)) {
12
13
  const decoded = await wsCipher.decodeEventData(parsed, cipherKey);
13
14
  const parsedData = decoded?.data;
14
15
  const controls = parsedData?.controls;
@@ -89,6 +90,13 @@ async function handleRealtimeControlPacket(listener, ctx, selfListen, version, c
89
90
  data: v2tData,
90
91
  });
91
92
  }
93
+ else if (control.content.act_type == "oa") {
94
+ const oaData = typeof control.content.data == "string"
95
+ ? JSON.parse(control.content.data)
96
+ : control.content.data;
97
+ const oaEvent = OAMessage.initializeOAEvent(oaData, control.content.act);
98
+ listener.emit("oa_event", oaEvent);
99
+ }
92
100
  }
93
101
  return true;
94
102
  }