@itsliaaa/baileys 0.1.4 â 0.1.6
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 +9 -1
- package/lib/Defaults/index.js +1 -1
- package/lib/Socket/messages-recv.js +88 -130
- package/lib/Socket/messages-send.js +100 -2
- package/lib/Utils/index.js +2 -1
- package/lib/Utils/messages.js +26 -28
- package/lib/Utils/offline-node-processor.js +39 -0
- package/lib/Utils/stanza-ack.js +37 -0
- package/lib/WABinary/generic-utils.js +54 -41
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ A lightweight fork of Baileys with a few fixes and a small adjustment.
|
|
|
14
14
|
|
|
15
15
|
#### đ¨ Message Handling & Compatibility
|
|
16
16
|
- đđť Added support for sending [interactive message](#-sending-interactive-messages) types (button, list, interactive, template, carousel).
|
|
17
|
-
- đŠ Added support for [album messages](#%EF%B8%8F-album-image--video), [group status messages](#4%EF%B8%8FâŁ-group-status), [sticker pack messages](#-sticker-pack), and several [payment-related messages](#-sending-payment-messages) (request payment, payment invite, order, invoice).
|
|
17
|
+
- đŠ Added support for [album messages](#%EF%B8%8F-album-image--video), [group status messages](#4%EF%B8%8FâŁ-group-status), [status mention messages](#%EF%B8%8F-status-mention), [sticker pack messages](#-sticker-pack), and several [payment-related messages](#-sending-payment-messages) (request payment, payment invite, order, invoice).
|
|
18
18
|
- đ° Simplified sending messages with ad thumbnails via [`externalAdReply`](#3%EF%B8%8FâŁ-external-ad-reply) without requiring manual `contextInfo`.
|
|
19
19
|
|
|
20
20
|
#### đ§Š Additional Message Options
|
|
@@ -361,6 +361,14 @@ sock.sendMessage(jid, {
|
|
|
361
361
|
})
|
|
362
362
|
```
|
|
363
363
|
|
|
364
|
+
##### đď¸ Status Mention
|
|
365
|
+
|
|
366
|
+
```javascript
|
|
367
|
+
sock.sendMessage([jidA, jidB, jidC], {
|
|
368
|
+
text: 'Hello! đđť'
|
|
369
|
+
})
|
|
370
|
+
```
|
|
371
|
+
|
|
364
372
|
#### đ Sending Media Messages
|
|
365
373
|
|
|
366
374
|
> [!NOTE]
|
package/lib/Defaults/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { Browsers } from '../Utils/browser-utils.js';
|
|
|
4
4
|
import logger from '../Utils/logger.js';
|
|
5
5
|
const version = [2, 3000, 1033846690];
|
|
6
6
|
export const UNAUTHORIZED_CODES = [401, 403, 419];
|
|
7
|
-
export const BIZ_BOT_SUPPORT_PAYLOAD = '{"version":1,"is_ai_message":true,"should_show_system_message":
|
|
7
|
+
export const BIZ_BOT_SUPPORT_PAYLOAD = '{"version":1,"is_ai_message":true,"should_show_system_message":false,"ticket_id":"7004947587700716","citation_items":[],"ticket_locale":"us"}';
|
|
8
8
|
export const DEFAULT_ORIGIN = 'https://web.whatsapp.com';
|
|
9
9
|
export const CALL_VIDEO_PREFIX = 'https://call.whatsapp.com/video/';
|
|
10
10
|
export const CALL_AUDIO_PREFIX = 'https://call.whatsapp.com/voice/';
|
|
@@ -7,6 +7,8 @@ import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT, PLACEHOLDER_MAX_
|
|
|
7
7
|
import { WAMessageStatus, WAMessageStubType } from '../Types/index.js';
|
|
8
8
|
import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
|
|
9
9
|
import { makeMutex } from '../Utils/make-mutex.js';
|
|
10
|
+
import { makeOfflineNodeProcessor } from '../Utils/offline-node-processor.js';
|
|
11
|
+
import { buildAckStanza } from '../Utils/stanza-ack.js';
|
|
10
12
|
import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
11
13
|
import { extractGroupMetadata } from './groups.js';
|
|
12
14
|
import { makeMessagesSocket } from './messages-send.js';
|
|
@@ -217,32 +219,9 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
217
219
|
break;
|
|
218
220
|
}
|
|
219
221
|
};
|
|
220
|
-
const sendMessageAck = async (
|
|
221
|
-
const stanza =
|
|
222
|
-
|
|
223
|
-
attrs: {
|
|
224
|
-
id: attrs.id,
|
|
225
|
-
to: attrs.from,
|
|
226
|
-
class: tag
|
|
227
|
-
}
|
|
228
|
-
};
|
|
229
|
-
if (!!errorCode) {
|
|
230
|
-
stanza.attrs.error = errorCode.toString();
|
|
231
|
-
}
|
|
232
|
-
if (!!attrs.participant) {
|
|
233
|
-
stanza.attrs.participant = attrs.participant;
|
|
234
|
-
}
|
|
235
|
-
if (!!attrs.recipient) {
|
|
236
|
-
stanza.attrs.recipient = attrs.recipient;
|
|
237
|
-
}
|
|
238
|
-
if (!!attrs.type &&
|
|
239
|
-
(tag !== 'message' || getBinaryNodeChild({ tag, attrs, content }, 'unavailable') || errorCode !== 0)) {
|
|
240
|
-
stanza.attrs.type = attrs.type;
|
|
241
|
-
}
|
|
242
|
-
if (tag === 'message' && getBinaryNodeChild({ tag, attrs, content }, 'unavailable')) {
|
|
243
|
-
stanza.attrs.from = authState.creds.me.id;
|
|
244
|
-
}
|
|
245
|
-
logger.debug({ recv: { tag, attrs }, sent: stanza.attrs }, 'sent ack');
|
|
222
|
+
const sendMessageAck = async (node, errorCode) => {
|
|
223
|
+
const stanza = buildAckStanza(node, errorCode, authState.creds.me.id);
|
|
224
|
+
logger.debug({ recv: { tag: node.tag, attrs: node.attrs }, sent: stanza.attrs }, 'sent ack');
|
|
246
225
|
await sendNode(stanza);
|
|
247
226
|
};
|
|
248
227
|
const rejectCall = async (callId, callFrom) => {
|
|
@@ -995,7 +974,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
995
974
|
]);
|
|
996
975
|
}
|
|
997
976
|
finally {
|
|
998
|
-
await sendMessageAck(node);
|
|
977
|
+
await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack receipt'));
|
|
999
978
|
}
|
|
1000
979
|
};
|
|
1001
980
|
const handleNotification = async (node) => {
|
|
@@ -1030,7 +1009,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1030
1009
|
]);
|
|
1031
1010
|
}
|
|
1032
1011
|
finally {
|
|
1033
|
-
await sendMessageAck(node);
|
|
1012
|
+
await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack notification'));
|
|
1034
1013
|
}
|
|
1035
1014
|
};
|
|
1036
1015
|
const handleMessage = async (node) => {
|
|
@@ -1046,36 +1025,34 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1046
1025
|
await sendMessageAck(node, NACK_REASONS.MissingMessageSecret);
|
|
1047
1026
|
return;
|
|
1048
1027
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
await signalRepository.
|
|
1028
|
+
let acked = false;
|
|
1029
|
+
try {
|
|
1030
|
+
const { fullMessage: msg, category, author, decrypt } = decryptMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger);
|
|
1031
|
+
const alt = msg.key.participantAlt || msg.key.remoteJidAlt;
|
|
1032
|
+
// store new mappings we didn't have before
|
|
1033
|
+
if (!!alt) {
|
|
1034
|
+
const altServer = jidDecode(alt)?.server;
|
|
1035
|
+
const primaryJid = msg.key.participant || msg.key.remoteJid;
|
|
1036
|
+
if (altServer === 'lid') {
|
|
1037
|
+
if (!(await signalRepository.lidMapping.getPNForLID(alt))) {
|
|
1038
|
+
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: alt, pn: primaryJid }]);
|
|
1039
|
+
await signalRepository.migrateSession(primaryJid, alt);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
else {
|
|
1043
|
+
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]);
|
|
1044
|
+
await signalRepository.migrateSession(alt, primaryJid);
|
|
1059
1045
|
}
|
|
1060
1046
|
}
|
|
1061
|
-
else {
|
|
1062
|
-
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]);
|
|
1063
|
-
await signalRepository.migrateSession(alt, primaryJid);
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
if (msg.key?.remoteJid && msg.key?.id && messageRetryManager) {
|
|
1067
|
-
messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message);
|
|
1068
|
-
logger.debug({
|
|
1069
|
-
jid: msg.key.remoteJid,
|
|
1070
|
-
id: msg.key.id
|
|
1071
|
-
}, 'Added message to recent cache for retry receipts');
|
|
1072
|
-
}
|
|
1073
|
-
try {
|
|
1074
1047
|
await messageMutex.mutex(async () => {
|
|
1075
1048
|
await decrypt();
|
|
1049
|
+
if (msg.key?.remoteJid && msg.key?.id && msg.message && messageRetryManager) {
|
|
1050
|
+
messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message);
|
|
1051
|
+
}
|
|
1076
1052
|
// message failed to decrypt
|
|
1077
1053
|
if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT && msg.category !== 'peer') {
|
|
1078
1054
|
if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT) {
|
|
1055
|
+
acked = true;
|
|
1079
1056
|
return sendMessageAck(node, NACK_REASONS.ParsingError);
|
|
1080
1057
|
}
|
|
1081
1058
|
if (msg.messageStubParameters?.[0] === NO_MESSAGE_FOUND_ERROR_TEXT) {
|
|
@@ -1087,11 +1064,13 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1087
1064
|
unavailableType === 'hosted_unavailable_fanout' ||
|
|
1088
1065
|
unavailableType === 'view_once_unavailable_fanout') {
|
|
1089
1066
|
logger.debug({ msgId: msg.key.id, unavailableType }, 'skipping placeholder resend for excluded unavailable type');
|
|
1067
|
+
acked = true;
|
|
1090
1068
|
return sendMessageAck(node);
|
|
1091
1069
|
}
|
|
1092
1070
|
const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp);
|
|
1093
1071
|
if (messageAge > PLACEHOLDER_MAX_AGE_SECONDS) {
|
|
1094
1072
|
logger.debug({ msgId: msg.key.id, messageAge }, 'skipping placeholder resend for old message');
|
|
1073
|
+
acked = true;
|
|
1095
1074
|
return sendMessageAck(node);
|
|
1096
1075
|
}
|
|
1097
1076
|
// Request the real content from the phone via placeholder resend PDO.
|
|
@@ -1128,6 +1107,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1128
1107
|
.catch(err => {
|
|
1129
1108
|
logger.warn({ err, msgId: msg.key.id }, 'failed to request placeholder resend for unavailable message');
|
|
1130
1109
|
});
|
|
1110
|
+
acked = true;
|
|
1131
1111
|
await sendMessageAck(node);
|
|
1132
1112
|
// Don't return â fall through to upsertMessage so the stub is emitted
|
|
1133
1113
|
}
|
|
@@ -1137,6 +1117,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1137
1117
|
const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp);
|
|
1138
1118
|
if (messageAge > STATUS_EXPIRY_SECONDS) {
|
|
1139
1119
|
logger.debug({ msgId: msg.key.id, messageAge, remoteJid: msg.key.remoteJid }, 'skipping retry for expired status message');
|
|
1120
|
+
acked = true;
|
|
1140
1121
|
return sendMessageAck(node);
|
|
1141
1122
|
}
|
|
1142
1123
|
}
|
|
@@ -1180,6 +1161,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1180
1161
|
logger.error({ retryErr }, 'Failed to send retry after error handling');
|
|
1181
1162
|
}
|
|
1182
1163
|
}
|
|
1164
|
+
acked = true;
|
|
1183
1165
|
await sendMessageAck(node, NACK_REASONS.UnhandledError);
|
|
1184
1166
|
});
|
|
1185
1167
|
}
|
|
@@ -1208,6 +1190,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1208
1190
|
else if (!sendActiveReceipts) {
|
|
1209
1191
|
type = 'inactive';
|
|
1210
1192
|
}
|
|
1193
|
+
acked = true;
|
|
1211
1194
|
await sendReceipt(msg.key.remoteJid, participant, [msg.key.id], type);
|
|
1212
1195
|
// send ack for history message
|
|
1213
1196
|
const isAnyHistoryMsg = getHistoryMsg(msg.message);
|
|
@@ -1217,6 +1200,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1217
1200
|
}
|
|
1218
1201
|
}
|
|
1219
1202
|
else {
|
|
1203
|
+
acked = true;
|
|
1220
1204
|
await sendMessageAck(node);
|
|
1221
1205
|
logger.debug({ key: msg.key }, 'processed newsletter message without receipts');
|
|
1222
1206
|
}
|
|
@@ -1227,45 +1211,55 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1227
1211
|
}
|
|
1228
1212
|
catch (error) {
|
|
1229
1213
|
logger.error({ error, node: binaryNodeToString(node) }, 'error in handling message');
|
|
1214
|
+
if (!acked) {
|
|
1215
|
+
await sendMessageAck(node, NACK_REASONS.UnhandledError).catch(ackErr => logger.error({ ackErr }, 'failed to ack message after error'));
|
|
1216
|
+
}
|
|
1230
1217
|
}
|
|
1231
1218
|
};
|
|
1232
1219
|
const handleCall = async (node) => {
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1220
|
+
try {
|
|
1221
|
+
const { attrs } = node;
|
|
1222
|
+
const [infoChild] = getAllBinaryNodeChildren(node);
|
|
1223
|
+
if (!infoChild) {
|
|
1224
|
+
throw new Boom('Missing call info in call node');
|
|
1225
|
+
}
|
|
1226
|
+
const status = getCallStatusFromNode(infoChild);
|
|
1227
|
+
const callId = infoChild.attrs['call-id'];
|
|
1228
|
+
const from = infoChild.attrs.from || infoChild.attrs['call-creator'];
|
|
1229
|
+
const call = {
|
|
1230
|
+
chatId: attrs.from,
|
|
1231
|
+
from,
|
|
1232
|
+
callerPn: infoChild.attrs['caller_pn'],
|
|
1233
|
+
id: callId,
|
|
1234
|
+
date: new Date(+attrs.t * 1000),
|
|
1235
|
+
offline: !!attrs.offline,
|
|
1236
|
+
status
|
|
1237
|
+
};
|
|
1238
|
+
if (status === 'offer') {
|
|
1239
|
+
call.isVideo = !!getBinaryNodeChild(infoChild, 'video');
|
|
1240
|
+
call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'];
|
|
1241
|
+
call.groupJid = infoChild.attrs['group-jid'];
|
|
1242
|
+
await callOfferCache.set(call.id, call);
|
|
1243
|
+
}
|
|
1244
|
+
const existingCall = await callOfferCache.get(call.id);
|
|
1245
|
+
// use existing call info to populate this event
|
|
1246
|
+
if (existingCall) {
|
|
1247
|
+
call.isVideo = existingCall.isVideo;
|
|
1248
|
+
call.isGroup = existingCall.isGroup;
|
|
1249
|
+
call.callerPn = call.callerPn || existingCall.callerPn;
|
|
1250
|
+
}
|
|
1251
|
+
// delete data once call has ended
|
|
1252
|
+
if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
|
|
1253
|
+
await callOfferCache.del(call.id);
|
|
1254
|
+
}
|
|
1255
|
+
ev.emit('call', [call]);
|
|
1255
1256
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
if (existingCall) {
|
|
1259
|
-
call.isVideo = existingCall.isVideo;
|
|
1260
|
-
call.isGroup = existingCall.isGroup;
|
|
1261
|
-
call.callerPn = call.callerPn || existingCall.callerPn;
|
|
1257
|
+
catch (error) {
|
|
1258
|
+
logger.error({ error, node: binaryNodeToString(node) }, 'error in handling call');
|
|
1262
1259
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
await callOfferCache.del(call.id);
|
|
1260
|
+
finally {
|
|
1261
|
+
await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack call'));
|
|
1266
1262
|
}
|
|
1267
|
-
ev.emit('call', [call]);
|
|
1268
|
-
await sendMessageAck(node);
|
|
1269
1263
|
};
|
|
1270
1264
|
const handleBadAck = async ({ attrs }) => {
|
|
1271
1265
|
const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id };
|
|
@@ -1320,52 +1314,16 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1320
1314
|
return exec(node, false).catch(err => onUnexpectedError(err, identifier));
|
|
1321
1315
|
}
|
|
1322
1316
|
};
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
]);
|
|
1334
|
-
const nodes = [];
|
|
1335
|
-
let isProcessing = false;
|
|
1336
|
-
// Number of nodes to process before yielding to event loop
|
|
1337
|
-
const BATCH_SIZE = 10;
|
|
1338
|
-
const enqueue = (type, node) => {
|
|
1339
|
-
nodes.push({ type, node });
|
|
1340
|
-
if (isProcessing) {
|
|
1341
|
-
return;
|
|
1342
|
-
}
|
|
1343
|
-
isProcessing = true;
|
|
1344
|
-
const promise = async () => {
|
|
1345
|
-
let processedInBatch = 0;
|
|
1346
|
-
while (nodes.length && ws.isOpen) {
|
|
1347
|
-
const { type, node } = nodes.shift();
|
|
1348
|
-
const nodeProcessor = nodeProcessorMap.get(type);
|
|
1349
|
-
if (!nodeProcessor) {
|
|
1350
|
-
onUnexpectedError(new Error(`unknown offline node type: ${type}`), 'processing offline node');
|
|
1351
|
-
continue;
|
|
1352
|
-
}
|
|
1353
|
-
await nodeProcessor(node);
|
|
1354
|
-
processedInBatch++;
|
|
1355
|
-
// Yield to event loop after processing a batch
|
|
1356
|
-
// This prevents blocking the event loop for too long when there are many offline nodes
|
|
1357
|
-
if (processedInBatch >= BATCH_SIZE) {
|
|
1358
|
-
processedInBatch = 0;
|
|
1359
|
-
await yieldToEventLoop();
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
isProcessing = false;
|
|
1363
|
-
};
|
|
1364
|
-
promise().catch(error => onUnexpectedError(error, 'processing offline nodes'));
|
|
1365
|
-
};
|
|
1366
|
-
return { enqueue };
|
|
1367
|
-
};
|
|
1368
|
-
const offlineNodeProcessor = makeOfflineNodeProcessor();
|
|
1317
|
+
const offlineNodeProcessor = makeOfflineNodeProcessor(new Map([
|
|
1318
|
+
['message', handleMessage],
|
|
1319
|
+
['call', handleCall],
|
|
1320
|
+
['receipt', handleReceipt],
|
|
1321
|
+
['notification', handleNotification]
|
|
1322
|
+
]), {
|
|
1323
|
+
isWsOpen: () => ws.isOpen,
|
|
1324
|
+
onUnexpectedError,
|
|
1325
|
+
yieldToEventLoop: () => new Promise(resolve => setImmediate(resolve))
|
|
1326
|
+
});
|
|
1369
1327
|
const processNode = async (type, node, identifier, exec) => {
|
|
1370
1328
|
const isOffline = !!node.attrs.offline;
|
|
1371
1329
|
if (isOffline) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import NodeCache from '@cacheable/node-cache';
|
|
2
2
|
import { Boom } from '@hapi/boom';
|
|
3
|
+
import { randomBytes } from 'crypto';
|
|
3
4
|
import { proto } from '../../WAProto/index.js';
|
|
4
5
|
import { BIZ_BOT_SUPPORT_PAYLOAD, DEFAULT_CACHE_TTLS, OLD_GROUP_ID_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
|
|
5
6
|
import { aggregateMessageKeysNotFromMe, assertMediaContent, bindWaitForEvent, decryptMediaRetryData, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessageFromContent, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, hasValidAlbumMedia, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, shouldIncludeBizBinaryNode, unixTimestampSeconds } from '../Utils/index.js';
|
|
@@ -830,7 +831,9 @@ export const makeMessagesSocket = (config) => {
|
|
|
830
831
|
}
|
|
831
832
|
if (normalizedMessage.pollCreationMessage ||
|
|
832
833
|
normalizedMessage.pollCreationMessageV2 ||
|
|
833
|
-
normalizedMessage.pollCreationMessageV3 ||
|
|
834
|
+
normalizedMessage.pollCreationMessageV3 ||
|
|
835
|
+
normalizedMessage.pollCreationMessageV5 ||
|
|
836
|
+
normalizedMessage.pollCreationMessageV6 ||
|
|
834
837
|
normalizedMessage.pollUpdateMessage) {
|
|
835
838
|
return 'poll';
|
|
836
839
|
}
|
|
@@ -992,7 +995,102 @@ export const makeMessagesSocket = (config) => {
|
|
|
992
995
|
// Lia@Changes 30-01-26 --- Add support for modifying additionalNodes and additionalAttributes using "options" in sendMessage()
|
|
993
996
|
sendMessage: async (jid, content, options = {}) => {
|
|
994
997
|
const userJid = authState.creds.me.id;
|
|
995
|
-
|
|
998
|
+
// Lia@Changes 13-03-26 --- Add status mentions!
|
|
999
|
+
if (Array.isArray(jid)) {
|
|
1000
|
+
const { delayMs = 1500 } = options;
|
|
1001
|
+
const allUsers = new Set();
|
|
1002
|
+
const fullMsg = await generateWAMessage('status@broadcast', content, {
|
|
1003
|
+
logger,
|
|
1004
|
+
userJid,
|
|
1005
|
+
upload: waUploadToServer,
|
|
1006
|
+
mediaCache: config.mediaCache,
|
|
1007
|
+
options: config.options,
|
|
1008
|
+
...options,
|
|
1009
|
+
messageId: generateMessageIDV2(userJid)
|
|
1010
|
+
});
|
|
1011
|
+
for (const id of jid) {
|
|
1012
|
+
if (isJidGroup(id)) {
|
|
1013
|
+
try {
|
|
1014
|
+
const groupData = (cachedGroupMetadata ? await cachedGroupMetadata(id) : null) || await groupMetadata(id);
|
|
1015
|
+
for (const participant of groupData.participants) {
|
|
1016
|
+
if (allUsers.has(participant.id))
|
|
1017
|
+
continue;
|
|
1018
|
+
allUsers.add(participant.id);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
catch (error) {
|
|
1022
|
+
logger.error(`Error getting metadata group from ${id}: ${error}`);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
else if (!allUsers.has(id)) {
|
|
1026
|
+
allUsers.add(id);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
await relayMessage('status@broadcast', fullMsg.message, {
|
|
1030
|
+
messageId: fullMsg.key.id,
|
|
1031
|
+
statusJidList: Array.from(allUsers),
|
|
1032
|
+
additionalNodes: [
|
|
1033
|
+
{
|
|
1034
|
+
tag: 'meta',
|
|
1035
|
+
attrs: {},
|
|
1036
|
+
content: [
|
|
1037
|
+
{
|
|
1038
|
+
tag: 'mentioned_users',
|
|
1039
|
+
attrs: {},
|
|
1040
|
+
content: jid.map(id => ({
|
|
1041
|
+
tag: 'to',
|
|
1042
|
+
attrs: { jid: id },
|
|
1043
|
+
content: undefined
|
|
1044
|
+
}))
|
|
1045
|
+
}
|
|
1046
|
+
]
|
|
1047
|
+
}
|
|
1048
|
+
]
|
|
1049
|
+
});
|
|
1050
|
+
if (config.emitOwnEvents) {
|
|
1051
|
+
process.nextTick(async () => {
|
|
1052
|
+
await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
for (const id of jid) {
|
|
1056
|
+
const isGroup = isJidGroup(id)
|
|
1057
|
+
const sendType = isGroup ? 'groupStatusMentionMessage' : 'statusMentionMessage';
|
|
1058
|
+
const mentionMsg = generateWAMessageFromContent(id, {
|
|
1059
|
+
messageContextInfo: {
|
|
1060
|
+
messageSecret: randomBytes(32)
|
|
1061
|
+
},
|
|
1062
|
+
[sendType]: {
|
|
1063
|
+
message: {
|
|
1064
|
+
protocolMessage: {
|
|
1065
|
+
key: fullMsg.key,
|
|
1066
|
+
type: 25
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}, {
|
|
1071
|
+
userJid
|
|
1072
|
+
});
|
|
1073
|
+
await relayMessage(id, mentionMsg.message, {
|
|
1074
|
+
additionalNodes: [
|
|
1075
|
+
{
|
|
1076
|
+
tag: 'meta',
|
|
1077
|
+
attrs: isGroup ?
|
|
1078
|
+
{ is_group_status_mention: 'true' } :
|
|
1079
|
+
{ is_status_mention: 'true' },
|
|
1080
|
+
content: undefined
|
|
1081
|
+
}
|
|
1082
|
+
]
|
|
1083
|
+
});
|
|
1084
|
+
if (config.emitOwnEvents) {
|
|
1085
|
+
process.nextTick(async () => {
|
|
1086
|
+
await messageMutex.mutex(() => upsertMessage(mentionMsg, 'append'));
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
await delay(delayMs);
|
|
1090
|
+
}
|
|
1091
|
+
return fullMsg;
|
|
1092
|
+
}
|
|
1093
|
+
else if ('disappearingMessagesInChat' in content && isJidGroup(jid)) {
|
|
996
1094
|
const { disappearingMessagesInChat } = content;
|
|
997
1095
|
const value = typeof disappearingMessagesInChat === 'boolean'
|
|
998
1096
|
? disappearingMessagesInChat
|
package/lib/Utils/index.js
CHANGED
|
@@ -16,4 +16,5 @@ export * from './event-buffer.js';
|
|
|
16
16
|
export * from './process-message.js';
|
|
17
17
|
export * from './message-retry-manager.js';
|
|
18
18
|
export * from './browser-utils.js';
|
|
19
|
-
export * from './identity-change-handler.js';
|
|
19
|
+
export * from './identity-change-handler.js';
|
|
20
|
+
export * from './stanza-ack.js';
|
package/lib/Utils/messages.js
CHANGED
|
@@ -26,33 +26,28 @@ const MessageTypeProto = {
|
|
|
26
26
|
sticker: WAProto.Message.StickerMessage,
|
|
27
27
|
document: WAProto.Message.DocumentMessage
|
|
28
28
|
};
|
|
29
|
-
const mediaAnnotation = [
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
contentType: proto.ContextInfo.ForwardedNewsletterMessageInfo.ContentType.UPDATE,
|
|
52
|
-
accessibilityText: process.env.NEWSLETTER_ACCESSIBILITY_TEXT ||
|
|
53
|
-
''
|
|
54
|
-
}
|
|
55
|
-
}];
|
|
29
|
+
const mediaAnnotation = [
|
|
30
|
+
{
|
|
31
|
+
polygonVertices: [
|
|
32
|
+
{ x: 60.71664810180664, y: -36.39784622192383 },
|
|
33
|
+
{ x: -16.710189819335938, y: 49.263675689697266 },
|
|
34
|
+
{ x: -56.585853576660156, y: 37.85963439941406 },
|
|
35
|
+
{ x: 20.840980529785156, y: -47.80188751220703 }
|
|
36
|
+
],
|
|
37
|
+
newsletter: {
|
|
38
|
+
// Lia@Note 03-02-26 --- You can change jid, message id, and name via .env (â â§â â˝â âŚâ )
|
|
39
|
+
newsletterJid: process.env.NEWSLETTER_ID ||
|
|
40
|
+
Buffer.from('313230333633343034303036363434313339406e6577736c6574746572', 'hex').toString(),
|
|
41
|
+
serverMessageId: process.env.NEWSLETTER_MESSAGE_ID ||
|
|
42
|
+
Buffer.from('313033', 'hex').toString(),
|
|
43
|
+
newsletterName: process.env.NEWSLETTER_NAME ||
|
|
44
|
+
Buffer.from('f09d96b2f09d978df09d96baf09d978bf09d96bff09d96baf09d9785f09d9785', 'hex').toString(),
|
|
45
|
+
contentType: proto.ContextInfo.ForwardedNewsletterMessageInfo.ContentType.UPDATE,
|
|
46
|
+
accessibilityText: process.env.NEWSLETTER_ACCESSIBILITY_TEXT ||
|
|
47
|
+
'@itsliaaa/baileys'
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
];
|
|
56
51
|
/**
|
|
57
52
|
* Uses a regex to test whether the string contains a URL, and returns the URL if it does.
|
|
58
53
|
* @param text eg. hello https://google.com
|
|
@@ -552,7 +547,10 @@ const prepareNativeFlowButtons = (message) => {
|
|
|
552
547
|
Object.assign(messageParamsJson, {
|
|
553
548
|
bottom_sheet: {
|
|
554
549
|
in_thread_buttons_limit: 1,
|
|
555
|
-
|
|
550
|
+
divider_indices: Array.from(
|
|
551
|
+
{ length: correctedField.length },
|
|
552
|
+
(_, index) => index
|
|
553
|
+
),
|
|
556
554
|
list_title: message.optionTitle || 'đ Select Options',
|
|
557
555
|
button_title: message.optionText
|
|
558
556
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a processor for offline stanza nodes that:
|
|
3
|
+
* - Queues nodes for sequential processing
|
|
4
|
+
* - Yields to the event loop periodically to avoid blocking
|
|
5
|
+
* - Catches handler errors to prevent the processing loop from crashing
|
|
6
|
+
*/
|
|
7
|
+
export function makeOfflineNodeProcessor(nodeProcessorMap, deps, batchSize = 10) {
|
|
8
|
+
const nodes = [];
|
|
9
|
+
let isProcessing = false;
|
|
10
|
+
const enqueue = (type, node) => {
|
|
11
|
+
nodes.push({ type, node });
|
|
12
|
+
if (isProcessing) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
isProcessing = true;
|
|
16
|
+
const promise = async () => {
|
|
17
|
+
let processedInBatch = 0;
|
|
18
|
+
while (nodes.length && deps.isWsOpen()) {
|
|
19
|
+
const { type, node } = nodes.shift();
|
|
20
|
+
const nodeProcessor = nodeProcessorMap.get(type);
|
|
21
|
+
if (!nodeProcessor) {
|
|
22
|
+
deps.onUnexpectedError(new Error(`unknown offline node type: ${type}`), 'processing offline node');
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
await nodeProcessor(node).catch(err => deps.onUnexpectedError(err, `processing offline ${type}`));
|
|
26
|
+
processedInBatch++;
|
|
27
|
+
// Yield to event loop after processing a batch
|
|
28
|
+
// This prevents blocking the event loop for too long when there are many offline nodes
|
|
29
|
+
if (processedInBatch >= batchSize) {
|
|
30
|
+
processedInBatch = 0;
|
|
31
|
+
await deps.yieldToEventLoop();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
isProcessing = false;
|
|
35
|
+
};
|
|
36
|
+
promise().catch(error => deps.onUnexpectedError(error, 'processing offline nodes'));
|
|
37
|
+
};
|
|
38
|
+
return { enqueue };
|
|
39
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds an ACK stanza for a received node.
|
|
3
|
+
* Pure function -- no I/O, no side effects.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors WhatsApp Web's ACK construction:
|
|
6
|
+
* - WAWebHandleMsgSendAck.sendAck / sendNack
|
|
7
|
+
* - WAWebCreateNackFromStanza.createNackFromStanza
|
|
8
|
+
*/
|
|
9
|
+
export function buildAckStanza(node, errorCode, meId) {
|
|
10
|
+
const { tag, attrs } = node;
|
|
11
|
+
const stanza = {
|
|
12
|
+
tag: 'ack',
|
|
13
|
+
attrs: {
|
|
14
|
+
id: attrs.id,
|
|
15
|
+
to: attrs.from,
|
|
16
|
+
class: tag
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
if (errorCode) {
|
|
20
|
+
stanza.attrs.error = errorCode.toString();
|
|
21
|
+
}
|
|
22
|
+
if (attrs.participant) {
|
|
23
|
+
stanza.attrs.participant = attrs.participant;
|
|
24
|
+
}
|
|
25
|
+
if (attrs.recipient) {
|
|
26
|
+
stanza.attrs.recipient = attrs.recipient;
|
|
27
|
+
}
|
|
28
|
+
// WA Web always includes type when present: `n.type || DROP_ATTR`
|
|
29
|
+
if (attrs.type) {
|
|
30
|
+
stanza.attrs.type = attrs.type;
|
|
31
|
+
}
|
|
32
|
+
// WA Web WAWebHandleMsgSendAck.sendAck/sendNack always include `from` for message-class ACKs
|
|
33
|
+
if (tag === 'message' && meId) {
|
|
34
|
+
stanza.attrs.from = meId;
|
|
35
|
+
}
|
|
36
|
+
return stanza;
|
|
37
|
+
}
|
|
@@ -123,75 +123,88 @@ export function binaryNodeToString(node, i = 0) {
|
|
|
123
123
|
* @param {object} message Normalized message content (after Baileys normalization).
|
|
124
124
|
* @returns {object} A node with shape { tag, attrs, [content] } to inject into additionalNodes.
|
|
125
125
|
*/
|
|
126
|
-
const
|
|
127
|
-
mpm:
|
|
128
|
-
cta_catalog:
|
|
129
|
-
send_location:
|
|
130
|
-
call_permission_request:
|
|
131
|
-
wa_payment_transaction_details:
|
|
132
|
-
automated_greeting_message_view_catalog:
|
|
126
|
+
const FLOWS_MAP = {
|
|
127
|
+
mpm: true,
|
|
128
|
+
cta_catalog: true,
|
|
129
|
+
send_location: true,
|
|
130
|
+
call_permission_request: true,
|
|
131
|
+
wa_payment_transaction_details: true,
|
|
132
|
+
automated_greeting_message_view_catalog: true
|
|
133
133
|
};
|
|
134
|
-
const
|
|
134
|
+
const qualityAttribute = {
|
|
135
135
|
tag: 'quality_control',
|
|
136
|
-
attrs: { source_type: 'third_party' }
|
|
137
|
-
content: undefined
|
|
136
|
+
attrs: { source_type: 'third_party' }
|
|
138
137
|
};
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return [{
|
|
148
|
-
tag: 'interactive',
|
|
149
|
-
attrs: { type: 'native_flow', v: '1' },
|
|
150
|
-
content: [{
|
|
151
|
-
tag: 'native_flow',
|
|
152
|
-
attrs: { v, name },
|
|
153
|
-
content: undefined
|
|
154
|
-
}]
|
|
155
|
-
}, bizBinaryQualityAttribute];
|
|
156
|
-
}
|
|
138
|
+
const defaultContent = [qualityAttribute]
|
|
139
|
+
const listContent = [
|
|
140
|
+
{
|
|
141
|
+
tag: 'list',
|
|
142
|
+
attrs: { v: '2', type: 'product_list' }
|
|
143
|
+
},
|
|
144
|
+
qualityAttribute
|
|
145
|
+
];
|
|
157
146
|
export const getBizBinaryNode = (message) => {
|
|
158
|
-
const
|
|
159
|
-
const buttonName =
|
|
147
|
+
const flowMsg = message.interactiveMessage?.nativeFlowMessage;
|
|
148
|
+
const buttonName = flowMsg?.buttons?.[0]?.name;
|
|
160
149
|
if (buttonName === 'review_and_pay' || buttonName === 'payment_info') {
|
|
161
150
|
return {
|
|
162
151
|
tag: 'biz',
|
|
163
152
|
attrs: {
|
|
164
|
-
native_flow_name: buttonName === 'review_and_pay'
|
|
165
|
-
|
|
166
|
-
|
|
153
|
+
native_flow_name: buttonName === 'review_and_pay' ?
|
|
154
|
+
'order_details' :
|
|
155
|
+
buttonName
|
|
167
156
|
},
|
|
168
157
|
content: defaultContent
|
|
169
158
|
};
|
|
170
159
|
}
|
|
171
|
-
if (buttonName &&
|
|
160
|
+
if (buttonName && FLOWS_MAP[buttonName]) {
|
|
172
161
|
return {
|
|
173
162
|
tag: 'biz',
|
|
174
|
-
attrs:
|
|
175
|
-
content:
|
|
163
|
+
attrs: {},
|
|
164
|
+
content: [
|
|
165
|
+
{
|
|
166
|
+
tag: 'interactive',
|
|
167
|
+
attrs: { type: 'native_flow', v: '1' },
|
|
168
|
+
content: [
|
|
169
|
+
{
|
|
170
|
+
tag: 'native_flow',
|
|
171
|
+
attrs: { v: '2', name: buttonName }
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
},
|
|
175
|
+
qualityAttribute
|
|
176
|
+
]
|
|
176
177
|
};
|
|
177
178
|
}
|
|
178
|
-
if (
|
|
179
|
+
if (flowMsg || message.buttonsMessage || message.templateMessage) {
|
|
179
180
|
return {
|
|
180
181
|
tag: 'biz',
|
|
181
|
-
attrs:
|
|
182
|
-
content:
|
|
182
|
+
attrs: {},
|
|
183
|
+
content: [
|
|
184
|
+
{
|
|
185
|
+
tag: 'interactive',
|
|
186
|
+
attrs: { type: 'native_flow', v: '1' },
|
|
187
|
+
content: [
|
|
188
|
+
{
|
|
189
|
+
tag: 'native_flow',
|
|
190
|
+
attrs: { v: '9', name: 'mixed' }
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
},
|
|
194
|
+
qualityAttribute
|
|
195
|
+
]
|
|
183
196
|
};
|
|
184
197
|
}
|
|
185
198
|
if (message.listMessage) {
|
|
186
199
|
return {
|
|
187
200
|
tag: 'biz',
|
|
188
|
-
attrs:
|
|
201
|
+
attrs: {},
|
|
189
202
|
content: listContent
|
|
190
203
|
};
|
|
191
204
|
}
|
|
192
205
|
return {
|
|
193
206
|
tag: 'biz',
|
|
194
|
-
attrs:
|
|
207
|
+
attrs: {},
|
|
195
208
|
content: defaultContent
|
|
196
209
|
};
|
|
197
210
|
}
|