@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.
- package/README.md +25 -2
- package/dist/apis/business/blockOA.d.ts +2 -0
- package/dist/apis/business/blockOA.js +28 -0
- package/dist/apis/business/convertOAIds.d.ts +4 -0
- package/dist/apis/business/convertOAIds.js +26 -0
- package/dist/apis/business/getOAProfile.d.ts +13 -0
- package/dist/apis/business/getOAProfile.js +64 -0
- package/dist/apis/business/unblockOA.d.ts +2 -0
- package/dist/apis/business/unblockOA.js +28 -0
- package/dist/apis/chat/preloadConversations.d.ts +3 -2
- package/dist/apis/chat/preloadConversations.js +14 -0
- package/dist/apis/sync/getSyncData.js +1 -1
- package/dist/apis.d.ts +12 -0
- package/dist/apis.js +20 -0
- package/dist/cjs/apis/business/blockOA.cjs +33 -0
- package/dist/cjs/apis/business/convertOAIds.cjs +31 -0
- package/dist/cjs/apis/business/getOAProfile.cjs +69 -0
- package/dist/cjs/apis/business/unblockOA.cjs +33 -0
- package/dist/cjs/apis/chat/preloadConversations.cjs +14 -0
- package/dist/cjs/apis/sync/getSyncData.cjs +20 -1
- package/dist/cjs/apis.cjs +20 -0
- package/dist/cjs/constants/defaults.cjs +2 -2
- package/dist/cjs/constants/protocol.cjs +1 -1
- package/dist/cjs/index.cjs +10 -0
- package/dist/cjs/models/Message.cjs +16 -1
- package/dist/cjs/models/OAMessage.cjs +375 -0
- package/dist/cjs/realtime/handlers/control.handler.cjs +9 -1
- package/dist/cjs/realtime/handlers/message.handler.cjs +103 -37
- package/dist/cjs/realtime/listener.cjs +39 -18
- package/dist/cjs/utils.cjs +21 -2
- package/dist/cjs/zalo.cjs +21 -2
- package/dist/constants/defaults.d.ts +2 -2
- package/dist/constants/defaults.js +2 -2
- package/dist/constants/protocol.d.ts +1 -1
- package/dist/constants/protocol.js +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/models/Message.d.ts +12 -1
- package/dist/models/Message.js +16 -1
- package/dist/models/OAMessage.d.ts +221 -0
- package/dist/models/OAMessage.js +363 -0
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +1 -0
- package/dist/realtime/handlers/control.handler.js +9 -1
- package/dist/realtime/handlers/message.handler.js +103 -37
- package/dist/realtime/listener.d.ts +9 -3
- package/dist/realtime/listener.js +39 -18
- package/dist/realtime/types.d.ts +15 -1
- package/dist/utils.js +1 -1
- package/dist/zalo.d.ts +1 -1
- package/dist/zalo.js +1 -1
- 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
|
|
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: "
|
|
41
|
-
INITIAL_OLD_MESSAGES_CURSOR: "
|
|
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.
|
|
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) */
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|