@badzz88/baileys 8.3.0 → 8.4.1
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 +27 -27
- package/WAProto/GenerateStatics.sh +3 -0
- package/WAProto/WAProto.proto +6902 -0
- package/WAProto/fix-import.js +29 -0
- package/WAProto/fix-imports.js +85 -0
- package/WAProto/index.d.ts +79257 -0
- package/WAProto/index.js +204305 -131020
- package/engine-requirements.js +3 -4
- package/lib/Defaults/baileys-version.json +1 -1
- package/lib/Defaults/index.js +119 -136
- package/lib/Signal/Group/ciphertext-message.js +2 -5
- package/lib/Signal/Group/group-session-builder.js +7 -41
- package/lib/Signal/Group/group_cipher.js +37 -51
- package/lib/Signal/Group/index.js +12 -57
- package/lib/Signal/Group/keyhelper.js +7 -44
- package/lib/Signal/Group/sender-chain-key.js +7 -15
- package/lib/Signal/Group/sender-key-distribution-message.js +8 -11
- package/lib/Signal/Group/sender-key-message.js +9 -12
- package/lib/Signal/Group/sender-key-name.js +2 -5
- package/lib/Signal/Group/sender-key-record.js +9 -21
- package/lib/Signal/Group/sender-key-state.js +27 -42
- package/lib/Signal/Group/sender-message-key.js +4 -7
- package/lib/Signal/libsignal.js +347 -90
- package/lib/Signal/lid-mapping.js +277 -0
- package/lib/Socket/Client/index.js +3 -19
- package/lib/Socket/Client/types.js +11 -0
- package/lib/Socket/Client/websocket.js +54 -0
- package/lib/Socket/business.js +162 -43
- package/lib/Socket/chats.js +627 -404
- package/lib/Socket/communities.js +431 -0
- package/lib/Socket/community.js +392 -0
- package/lib/Socket/dugong.js +161 -8
- package/lib/Socket/groups.js +154 -160
- package/lib/Socket/index.js +11 -10
- package/lib/Socket/luxu.js +387 -0
- package/lib/Socket/messages-recv.js +1421 -615
- package/lib/Socket/messages-send.js +1150 -624
- package/lib/Socket/mex.js +42 -0
- package/lib/Socket/newsletter.js +139 -314
- package/lib/Socket/registration.js +2 -1
- package/lib/Socket/socket.js +544 -242
- package/lib/Store/index.js +10 -10
- package/lib/Store/keyed-db.js +108 -0
- package/lib/Store/make-cache-manager-store.js +43 -41
- package/lib/Store/make-in-memory-store.js +112 -341
- package/lib/Store/make-ordered-dictionary.js +14 -20
- package/lib/Store/object-repository.js +11 -6
- package/lib/Types/Auth.js +2 -2
- package/lib/Types/Bussines.js +2 -0
- package/lib/Types/Call.js +2 -2
- package/lib/Types/Chat.js +8 -4
- package/lib/Types/Contact.js +2 -2
- package/lib/Types/Events.js +2 -2
- package/lib/Types/GroupMetadata.js +2 -2
- package/lib/Types/Label.js +3 -5
- package/lib/Types/LabelAssociation.js +3 -5
- package/lib/Types/Message.js +11 -9
- package/lib/Types/Mex.js +37 -0
- package/lib/Types/Product.js +2 -2
- package/lib/Types/Signal.js +2 -2
- package/lib/Types/Socket.js +3 -2
- package/lib/Types/State.js +56 -2
- package/lib/Types/USync.js +2 -2
- package/lib/Types/index.js +15 -31
- package/lib/Utils/auth-utils.js +239 -143
- package/lib/Utils/browser-utils.js +48 -0
- package/lib/Utils/business.js +66 -69
- package/lib/Utils/chat-utils.js +396 -253
- package/lib/Utils/companion-reg-client-utils.js +35 -0
- package/lib/Utils/crypto.js +57 -90
- package/lib/Utils/decode-wa-message.js +236 -84
- package/lib/Utils/event-buffer.js +185 -77
- package/lib/Utils/generics.js +188 -283
- package/lib/Utils/history.js +93 -55
- package/lib/Utils/identity-change-handler.js +50 -0
- package/lib/Utils/index.js +23 -33
- package/lib/Utils/link-preview.js +16 -24
- package/lib/Utils/logger.js +3 -7
- package/lib/Utils/lt-hash.js +3 -46
- package/lib/Utils/make-mutex.js +24 -34
- package/lib/Utils/message-composer.js +273 -0
- package/lib/Utils/message-retry-manager.js +265 -0
- package/lib/Utils/messages-media.js +451 -482
- package/lib/Utils/messages.js +796 -359
- package/lib/Utils/noise-handler.js +145 -99
- package/lib/Utils/offline-node-processor.js +40 -0
- package/lib/Utils/pre-key-manager.js +106 -0
- package/lib/Utils/process-message.js +459 -150
- package/lib/Utils/reporting-utils.js +258 -0
- package/lib/Utils/signal.js +120 -72
- package/lib/Utils/stanza-ack.js +38 -0
- package/lib/Utils/sync-action-utils.js +49 -0
- package/lib/Utils/tc-token-utils.js +163 -0
- package/lib/Utils/use-multi-file-auth-state.js +29 -27
- package/lib/Utils/validate-connection.js +73 -99
- package/lib/WABinary/constants.js +1281 -20
- package/lib/WABinary/decode.js +52 -42
- package/lib/WABinary/encode.js +110 -155
- package/lib/WABinary/generic-utils.js +55 -49
- package/lib/WABinary/index.js +6 -21
- package/lib/WABinary/jid-utils.js +76 -40
- package/lib/WABinary/types.js +2 -2
- package/lib/WAM/BinaryInfo.js +2 -5
- package/lib/WAM/constants.js +19071 -11568
- package/lib/WAM/encode.js +17 -22
- package/lib/WAM/index.js +4 -19
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +33 -13
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +11 -14
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +9 -12
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +9 -13
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +20 -22
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +13 -8
- package/lib/WAUSync/Protocols/index.js +6 -20
- package/lib/WAUSync/USyncQuery.js +44 -35
- package/lib/WAUSync/USyncUser.js +10 -5
- package/lib/WAUSync/index.js +4 -19
- package/lib/index.js +39 -53
- package/package.json +134 -101
- package/lib/Defaults/index.d.ts +0 -53
- package/lib/Signal/Group/ciphertext-message.d.ts +0 -9
- package/lib/Signal/Group/group-session-builder.d.ts +0 -14
- package/lib/Signal/Group/group_cipher.d.ts +0 -17
- package/lib/Signal/Group/index.d.ts +0 -11
- package/lib/Signal/Group/keyhelper.d.ts +0 -10
- package/lib/Signal/Group/queue-job.d.ts +0 -1
- package/lib/Signal/Group/sender-chain-key.d.ts +0 -13
- package/lib/Signal/Group/sender-key-distribution-message.d.ts +0 -16
- package/lib/Signal/Group/sender-key-message.d.ts +0 -18
- package/lib/Signal/Group/sender-key-name.d.ts +0 -17
- package/lib/Signal/Group/sender-key-record.d.ts +0 -30
- package/lib/Signal/Group/sender-key-state.d.ts +0 -38
- package/lib/Signal/Group/sender-message-key.d.ts +0 -11
- package/lib/Signal/libsignal.d.ts +0 -3
- package/lib/Socket/Client/abstract-socket-client.d.ts +0 -17
- package/lib/Socket/Client/index.d.ts +0 -3
- package/lib/Socket/Client/mobile-socket-client.d.ts +0 -13
- package/lib/Socket/Client/web-socket-client.d.ts +0 -12
- package/lib/Socket/business.d.ts +0 -171
- package/lib/Socket/chats.d.ts +0 -267
- package/lib/Socket/dugong.d.ts +0 -254
- package/lib/Socket/groups.d.ts +0 -115
- package/lib/Socket/index.d.ts +0 -173
- package/lib/Socket/messages-recv.d.ts +0 -161
- package/lib/Socket/messages-send.d.ts +0 -149
- package/lib/Socket/newsletter.d.ts +0 -134
- package/lib/Socket/registration.d.ts +0 -267
- package/lib/Socket/socket.d.ts +0 -43
- package/lib/Socket/usync.d.ts +0 -36
- package/lib/Store/index.d.ts +0 -3
- package/lib/Store/make-cache-manager-store.d.ts +0 -13
- package/lib/Store/make-in-memory-store.d.ts +0 -118
- package/lib/Store/make-ordered-dictionary.d.ts +0 -13
- package/lib/Store/object-repository.d.ts +0 -10
- package/lib/Types/Auth.d.ts +0 -110
- package/lib/Types/Call.d.ts +0 -13
- package/lib/Types/Chat.d.ts +0 -102
- package/lib/Types/Contact.d.ts +0 -19
- package/lib/Types/Events.d.ts +0 -157
- package/lib/Types/GroupMetadata.d.ts +0 -55
- package/lib/Types/Label.d.ts +0 -35
- package/lib/Types/LabelAssociation.d.ts +0 -29
- package/lib/Types/Message.d.ts +0 -273
- package/lib/Types/Newsletter.d.ts +0 -103
- package/lib/Types/Product.d.ts +0 -78
- package/lib/Types/Signal.d.ts +0 -57
- package/lib/Types/Socket.d.ts +0 -111
- package/lib/Types/State.d.ts +0 -27
- package/lib/Types/USync.d.ts +0 -25
- package/lib/Types/index.d.ts +0 -57
- package/lib/Utils/auth-utils.d.ts +0 -18
- package/lib/Utils/baileys-event-stream.d.ts +0 -16
- package/lib/Utils/business.d.ts +0 -22
- package/lib/Utils/chat-utils.d.ts +0 -71
- package/lib/Utils/crypto.d.ts +0 -41
- package/lib/Utils/decode-wa-message.d.ts +0 -19
- package/lib/Utils/event-buffer.d.ts +0 -35
- package/lib/Utils/generics.d.ts +0 -92
- package/lib/Utils/history.d.ts +0 -15
- package/lib/Utils/index.d.ts +0 -17
- package/lib/Utils/link-preview.d.ts +0 -21
- package/lib/Utils/logger.d.ts +0 -4
- package/lib/Utils/lt-hash.d.ts +0 -12
- package/lib/Utils/make-mutex.d.ts +0 -7
- package/lib/Utils/messages-media.d.ts +0 -116
- package/lib/Utils/messages.d.ts +0 -77
- package/lib/Utils/noise-handler.d.ts +0 -21
- package/lib/Utils/process-message.d.ts +0 -41
- package/lib/Utils/signal.d.ts +0 -32
- package/lib/Utils/use-multi-file-auth-state.d.ts +0 -13
- package/lib/Utils/validate-connection.d.ts +0 -11
- package/lib/WABinary/constants.d.ts +0 -30
- package/lib/WABinary/decode.d.ts +0 -7
- package/lib/WABinary/encode.d.ts +0 -3
- package/lib/WABinary/generic-utils.d.ts +0 -17
- package/lib/WABinary/index.d.ts +0 -5
- package/lib/WABinary/jid-utils.d.ts +0 -31
- package/lib/WABinary/types.d.ts +0 -18
- package/lib/WAM/BinaryInfo.d.ts +0 -17
- package/lib/WAM/constants.d.ts +0 -38
- package/lib/WAM/encode.d.ts +0 -3
- package/lib/WAM/index.d.ts +0 -3
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +0 -9
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +0 -22
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +0 -12
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +0 -12
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +0 -25
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +0 -8
- package/lib/WAUSync/Protocols/index.d.ts +0 -4
- package/lib/WAUSync/USyncQuery.d.ts +0 -28
- package/lib/WAUSync/USyncUser.d.ts +0 -12
- package/lib/WAUSync/index.d.ts +0 -3
- package/lib/index.d.ts +0 -12
|
@@ -1,74 +1,71 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
sendNode,
|
|
39
|
-
groupMetadata,
|
|
40
|
-
groupToggleEphemeral,
|
|
41
|
-
executeUSyncQuery
|
|
42
|
-
} = sock;
|
|
43
|
-
const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
|
|
44
|
-
stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.USER_DEVICES,
|
|
45
|
-
useClones: false
|
|
46
|
-
});
|
|
1
|
+
import NodeCache from '@cacheable/node-cache';
|
|
2
|
+
import { Boom } from '@hapi/boom';
|
|
3
|
+
import { proto } from '../../WAProto/index.js';
|
|
4
|
+
import { DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
|
|
5
|
+
import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds, setBotMessageSecret } from '../Utils/index.js';
|
|
6
|
+
import { getUrlInfo } from '../Utils/link-preview.js';
|
|
7
|
+
import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex.js';
|
|
8
|
+
import { getMessageReportingToken, shouldIncludeReportingToken } from '../Utils/reporting-utils.js';
|
|
9
|
+
import { buildMergedTcTokenIndexWrite, isTcTokenExpired, resolveIssuanceJid, resolveTcTokenJid, shouldSendNewTcToken, storeTcTokensFromIqResult } from '../Utils/tc-token-utils.js';
|
|
10
|
+
import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, isHostedLidUser, isHostedPnUser, isJidBot, isJidGroup, isJidMetaAI, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, PSA_WID, S_WHATSAPP_NET, getAdditionalNode, getBinaryNodeFilter, getBinaryFilteredBizBot, isInteropUser } from '../WABinary/index.js';
|
|
11
|
+
import { USyncQuery, USyncUser } from '../WAUSync/index.js';
|
|
12
|
+
import { makeNewsletterSocket } from './newsletter.js';
|
|
13
|
+
import imup from './luxu.js';
|
|
14
|
+
import * as Utils_1 from '../Utils/index.js';
|
|
15
|
+
import { randomBytes } from 'crypto';
|
|
16
|
+
export const makeMessagesSocket = (config) => {
|
|
17
|
+
const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount, aiLabel } = config;
|
|
18
|
+
const sock = makeNewsletterSocket(config);
|
|
19
|
+
const { ev, authState, messageMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral, registerSocketEndHandler } = sock;
|
|
20
|
+
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
|
|
21
|
+
/**
|
|
22
|
+
* Set of tctoken storage JIDs with a fire-and-forget `issuePrivacyTokens` IQ in flight.
|
|
23
|
+
* Prevents duplicate IQs from rapid back-to-back sends before `senderTimestamp` persists.
|
|
24
|
+
* Entries are always removed in `.finally()`, so the set is bounded by concurrency.
|
|
25
|
+
*/
|
|
26
|
+
const inFlightTcTokenIssuance = new Set();
|
|
27
|
+
const userDevicesCache = config.userDevicesCache ||
|
|
28
|
+
new NodeCache({
|
|
29
|
+
stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
|
|
30
|
+
useClones: false
|
|
31
|
+
});
|
|
32
|
+
/** Serializes writes to userDevicesCache across USync refresh and device-notification handling. */
|
|
33
|
+
const devicesMutex = makeMutex();
|
|
34
|
+
// Initialize message retry manager if enabled
|
|
35
|
+
const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null;
|
|
36
|
+
// Prevent race conditions in Signal session encryption by user
|
|
37
|
+
const encryptionMutex = makeKeyedMutex();
|
|
47
38
|
let mediaConn;
|
|
39
|
+
/** Per-socket media host; updated whenever media_conn is fetched. Defaults to the public WhatsApp host. */
|
|
40
|
+
let mediaHost = DEF_MEDIA_HOST;
|
|
48
41
|
const refreshMediaConn = async (forceGet = false) => {
|
|
49
42
|
const media = await mediaConn;
|
|
50
|
-
if (!media || forceGet ||
|
|
43
|
+
if (!media || forceGet || new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1000) {
|
|
51
44
|
mediaConn = (async () => {
|
|
52
45
|
const result = await query({
|
|
53
46
|
tag: 'iq',
|
|
54
47
|
attrs: {
|
|
55
48
|
type: 'set',
|
|
56
49
|
xmlns: 'w:m',
|
|
57
|
-
to:
|
|
50
|
+
to: S_WHATSAPP_NET
|
|
58
51
|
},
|
|
59
52
|
content: [{ tag: 'media_conn', attrs: {} }]
|
|
60
53
|
});
|
|
61
|
-
const mediaConnNode =
|
|
54
|
+
const mediaConnNode = getBinaryNodeChild(result, 'media_conn');
|
|
55
|
+
// TODO: explore full length of data that whatsapp provides
|
|
62
56
|
const node = {
|
|
63
|
-
hosts:
|
|
57
|
+
hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({
|
|
64
58
|
hostname: attrs.hostname,
|
|
65
|
-
maxContentLengthBytes: +attrs.maxContentLengthBytes
|
|
59
|
+
maxContentLengthBytes: +attrs.maxContentLengthBytes
|
|
66
60
|
})),
|
|
67
61
|
auth: mediaConnNode.attrs.auth,
|
|
68
62
|
ttl: +mediaConnNode.attrs.ttl,
|
|
69
63
|
fetchDate: new Date()
|
|
70
64
|
};
|
|
71
65
|
logger.debug('fetched media conn');
|
|
66
|
+
if (node.hosts[0]) {
|
|
67
|
+
mediaHost = node.hosts[0].hostname;
|
|
68
|
+
}
|
|
72
69
|
return node;
|
|
73
70
|
})();
|
|
74
71
|
}
|
|
@@ -79,17 +76,20 @@ const makeMessagesSocket = (config) => {
|
|
|
79
76
|
* used for receipts of phone call, read, delivery etc.
|
|
80
77
|
* */
|
|
81
78
|
const sendReceipt = async (jid, participant, messageIds, type) => {
|
|
79
|
+
if (!messageIds || messageIds.length === 0) {
|
|
80
|
+
throw new Boom('missing ids in receipt');
|
|
81
|
+
}
|
|
82
82
|
const node = {
|
|
83
83
|
tag: 'receipt',
|
|
84
84
|
attrs: {
|
|
85
|
-
id: messageIds[0]
|
|
86
|
-
}
|
|
85
|
+
id: messageIds[0]
|
|
86
|
+
}
|
|
87
87
|
};
|
|
88
88
|
const isReadReceipt = type === 'read' || type === 'read-self';
|
|
89
89
|
if (isReadReceipt) {
|
|
90
|
-
node.attrs.t =
|
|
90
|
+
node.attrs.t = unixTimestampSeconds().toString();
|
|
91
91
|
}
|
|
92
|
-
if (type === 'sender' &&
|
|
92
|
+
if (type === 'sender' && (isPnUser(jid) || isLidUser(jid))) {
|
|
93
93
|
node.attrs.recipient = jid;
|
|
94
94
|
node.attrs.to = participant;
|
|
95
95
|
}
|
|
@@ -100,7 +100,7 @@ const makeMessagesSocket = (config) => {
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
if (type) {
|
|
103
|
-
node.attrs.type =
|
|
103
|
+
node.attrs.type = type;
|
|
104
104
|
}
|
|
105
105
|
const remainingMessageIds = messageIds.slice(1);
|
|
106
106
|
if (remainingMessageIds.length) {
|
|
@@ -120,7 +120,7 @@ const makeMessagesSocket = (config) => {
|
|
|
120
120
|
};
|
|
121
121
|
/** Correctly bulk send receipts to multiple chats, participants */
|
|
122
122
|
const sendReceipts = async (keys, type) => {
|
|
123
|
-
const recps =
|
|
123
|
+
const recps = aggregateMessageKeysNotFromMe(keys);
|
|
124
124
|
for (const { jid, participant, messageIds } of recps) {
|
|
125
125
|
await sendReceipt(jid, participant, messageIds, type);
|
|
126
126
|
}
|
|
@@ -134,540 +134,829 @@ const makeMessagesSocket = (config) => {
|
|
|
134
134
|
};
|
|
135
135
|
/** Fetch all the devices we've to send a message to */
|
|
136
136
|
const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
|
|
137
|
-
const deviceResults = []
|
|
138
|
-
|
|
137
|
+
const deviceResults = [];
|
|
139
138
|
if (!useCache) {
|
|
140
|
-
logger.debug('not using cache for devices')
|
|
139
|
+
logger.debug('not using cache for devices');
|
|
141
140
|
}
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
141
|
+
const toFetch = [];
|
|
142
|
+
const jidsWithUser = jids
|
|
143
|
+
.map(jid => {
|
|
144
|
+
const decoded = jidDecode(jid);
|
|
145
|
+
const user = decoded?.user;
|
|
146
|
+
const device = decoded?.device;
|
|
147
|
+
const isExplicitDevice = typeof device === 'number' && device >= 0;
|
|
148
|
+
if (isExplicitDevice && user) {
|
|
149
|
+
deviceResults.push({
|
|
150
|
+
user,
|
|
151
|
+
device,
|
|
152
|
+
jid
|
|
153
|
+
});
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
jid = jidNormalizedUser(jid);
|
|
157
|
+
return { jid, user };
|
|
158
|
+
})
|
|
159
|
+
.filter(jid => jid !== null);
|
|
160
|
+
let mgetDevices;
|
|
161
|
+
if (useCache && userDevicesCache.mget) {
|
|
162
|
+
const usersToFetch = jidsWithUser.map(j => j?.user).filter(Boolean);
|
|
163
|
+
mgetDevices = await userDevicesCache.mget(usersToFetch);
|
|
164
|
+
}
|
|
165
|
+
for (const { jid, user } of jidsWithUser) {
|
|
152
166
|
if (useCache) {
|
|
153
|
-
const devices =
|
|
154
|
-
|
|
167
|
+
const devices = mgetDevices?.[user] ||
|
|
168
|
+
(userDevicesCache.mget ? undefined : (await userDevicesCache.get(user)));
|
|
155
169
|
if (devices) {
|
|
156
|
-
|
|
157
|
-
|
|
170
|
+
const devicesWithJid = devices.map(d => ({
|
|
171
|
+
...d,
|
|
172
|
+
jid: jidEncode(d.user, d.server, d.device)
|
|
173
|
+
}));
|
|
174
|
+
deviceResults.push(...devicesWithJid);
|
|
175
|
+
logger.trace({ user }, 'using cache for devices');
|
|
158
176
|
}
|
|
159
|
-
|
|
160
177
|
else {
|
|
161
|
-
toFetch.push(jid)
|
|
178
|
+
toFetch.push(jid);
|
|
162
179
|
}
|
|
163
180
|
}
|
|
164
|
-
|
|
165
181
|
else {
|
|
166
|
-
toFetch.push(jid)
|
|
182
|
+
toFetch.push(jid);
|
|
167
183
|
}
|
|
168
184
|
}
|
|
169
|
-
|
|
170
185
|
if (!toFetch.length) {
|
|
171
|
-
return deviceResults
|
|
186
|
+
return deviceResults;
|
|
172
187
|
}
|
|
173
|
-
|
|
174
|
-
const query = new WAUSync_1.USyncQuery()
|
|
175
|
-
.withContext('message')
|
|
176
|
-
.withDeviceProtocol()
|
|
177
|
-
|
|
188
|
+
const requestedLidUsers = new Set();
|
|
178
189
|
for (const jid of toFetch) {
|
|
179
|
-
|
|
190
|
+
if (isLidUser(jid) || isHostedLidUser(jid)) {
|
|
191
|
+
const user = jidDecode(jid)?.user;
|
|
192
|
+
if (user)
|
|
193
|
+
requestedLidUsers.add(user);
|
|
194
|
+
}
|
|
180
195
|
}
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
|
|
196
|
+
const query = new USyncQuery().withContext('message').withDeviceProtocol().withLIDProtocol();
|
|
197
|
+
for (const jid of toFetch) {
|
|
198
|
+
query.withUser(new USyncUser().withId(jid)); // todo: investigate - the idea here is that <user> should have an inline lid field with the lid being the pn equivalent
|
|
199
|
+
}
|
|
200
|
+
const result = await sock.executeUSyncQuery(query);
|
|
184
201
|
if (result) {
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
202
|
+
// TODO: LID MAP this stuff (lid protocol will now return lid with devices)
|
|
203
|
+
const lidResults = result.list.filter(a => !!a.lid);
|
|
204
|
+
if (lidResults.length > 0) {
|
|
205
|
+
logger.trace('Storing LID maps from device call');
|
|
206
|
+
await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid, pn: a.id })));
|
|
207
|
+
// Force-refresh sessions for newly mapped LIDs to align identity addressing
|
|
208
|
+
try {
|
|
209
|
+
const lids = lidResults.map(a => a.lid);
|
|
210
|
+
if (lids.length) {
|
|
211
|
+
await assertSessions(lids, true);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (e) {
|
|
215
|
+
logger.warn({ e, count: lidResults.length }, 'failed to assert sessions for newly mapped LIDs');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const extracted = extractDeviceJids(result?.list, authState.creds.me.id, authState.creds.me.lid, ignoreZeroDevices);
|
|
219
|
+
const deviceMap = {};
|
|
188
220
|
for (const item of extracted) {
|
|
189
|
-
deviceMap[item.user] = deviceMap[item.user] || []
|
|
190
|
-
deviceMap[item.user]
|
|
191
|
-
deviceResults.push(item)
|
|
221
|
+
deviceMap[item.user] = deviceMap[item.user] || [];
|
|
222
|
+
deviceMap[item.user]?.push(item);
|
|
192
223
|
}
|
|
193
|
-
|
|
194
|
-
for (const
|
|
195
|
-
|
|
224
|
+
// Process each user's devices as a group for bulk LID migration
|
|
225
|
+
for (const [user, userDevices] of Object.entries(deviceMap)) {
|
|
226
|
+
const isLidUser = requestedLidUsers.has(user);
|
|
227
|
+
// Process all devices for this user
|
|
228
|
+
for (const item of userDevices) {
|
|
229
|
+
const finalJid = isLidUser
|
|
230
|
+
? jidEncode(user, item.server, item.device)
|
|
231
|
+
: jidEncode(item.user, item.server, item.device);
|
|
232
|
+
deviceResults.push({
|
|
233
|
+
...item,
|
|
234
|
+
jid: finalJid
|
|
235
|
+
});
|
|
236
|
+
logger.debug({
|
|
237
|
+
user: item.user,
|
|
238
|
+
device: item.device,
|
|
239
|
+
finalJid,
|
|
240
|
+
usedLid: isLidUser
|
|
241
|
+
}, 'Processed device with LID priority');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
await devicesMutex.mutex(async () => {
|
|
245
|
+
if (userDevicesCache.mset) {
|
|
246
|
+
// if the cache supports mset, we can set all devices in one go
|
|
247
|
+
await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })));
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
for (const key in deviceMap) {
|
|
251
|
+
if (deviceMap[key])
|
|
252
|
+
await userDevicesCache.set(key, deviceMap[key]);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
const userDeviceUpdates = {};
|
|
257
|
+
for (const [userId, devices] of Object.entries(deviceMap)) {
|
|
258
|
+
if (devices && devices.length > 0) {
|
|
259
|
+
userDeviceUpdates[userId] = devices.map(d => d.device?.toString() || '0');
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (Object.keys(userDeviceUpdates).length > 0) {
|
|
263
|
+
try {
|
|
264
|
+
await authState.keys.set({ 'device-list': userDeviceUpdates });
|
|
265
|
+
logger.debug({ userCount: Object.keys(userDeviceUpdates).length }, 'stored user device lists for bulk migration');
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
logger.warn({ error }, 'failed to store user device lists');
|
|
269
|
+
}
|
|
196
270
|
}
|
|
197
271
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
272
|
+
return deviceResults;
|
|
273
|
+
};
|
|
274
|
+
/**
|
|
275
|
+
* Update Member Label
|
|
276
|
+
*/
|
|
277
|
+
const updateMemberLabel = (jid, memberLabel) => {
|
|
278
|
+
return relayMessage(jid, {
|
|
279
|
+
protocolMessage: {
|
|
280
|
+
type: proto.Message.ProtocolMessage.Type.GROUP_MEMBER_LABEL_CHANGE,
|
|
281
|
+
memberLabel: {
|
|
282
|
+
label: memberLabel?.slice(0, 30),
|
|
283
|
+
labelTimestamp: unixTimestampSeconds()
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}, {
|
|
287
|
+
additionalNodes: [
|
|
288
|
+
{
|
|
289
|
+
tag: 'meta',
|
|
290
|
+
attrs: {
|
|
291
|
+
tag_reason: 'user_update',
|
|
292
|
+
appdata: 'member_tag'
|
|
293
|
+
},
|
|
294
|
+
content: undefined
|
|
295
|
+
}
|
|
296
|
+
]
|
|
297
|
+
});
|
|
298
|
+
};
|
|
201
299
|
const assertSessions = async (jids, force) => {
|
|
202
300
|
let didFetchNewSession = false;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
.
|
|
210
|
-
|
|
211
|
-
for (const jid of jids) {
|
|
212
|
-
const signalId = signalRepository
|
|
213
|
-
.jidToSignalProtocolAddress(jid);
|
|
214
|
-
if (!sessions[signalId]) {
|
|
215
|
-
jidsRequiringFetch.push(jid);
|
|
301
|
+
const uniqueJids = [...new Set(jids)];
|
|
302
|
+
const jidsRequiringFetch = [];
|
|
303
|
+
logger.debug({ jids }, 'assertSessions call with jids');
|
|
304
|
+
for (const jid of uniqueJids) {
|
|
305
|
+
if (!force) {
|
|
306
|
+
const sessionValidation = await signalRepository.validateSession(jid);
|
|
307
|
+
if (sessionValidation.exists) {
|
|
308
|
+
continue;
|
|
216
309
|
}
|
|
217
310
|
}
|
|
311
|
+
jidsRequiringFetch.push(jid);
|
|
218
312
|
}
|
|
219
313
|
if (jidsRequiringFetch.length) {
|
|
220
|
-
|
|
314
|
+
// LID if mapped, otherwise original
|
|
315
|
+
const wireJids = [
|
|
316
|
+
...jidsRequiringFetch.filter(jid => !!isLidUser(jid) || !!isHostedLidUser(jid)),
|
|
317
|
+
...((await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => !!isPnUser(jid) || !!isHostedPnUser(jid)))) || []).map(a => a.lid)
|
|
318
|
+
];
|
|
319
|
+
logger.debug({ jidsRequiringFetch, wireJids }, 'fetching sessions');
|
|
221
320
|
const result = await query({
|
|
222
321
|
tag: 'iq',
|
|
223
322
|
attrs: {
|
|
224
323
|
xmlns: 'encrypt',
|
|
225
324
|
type: 'get',
|
|
226
|
-
to:
|
|
325
|
+
to: S_WHATSAPP_NET
|
|
227
326
|
},
|
|
228
327
|
content: [
|
|
229
328
|
{
|
|
230
329
|
tag: 'key',
|
|
231
330
|
attrs: {},
|
|
232
|
-
content:
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
331
|
+
content: wireJids.map(jid => {
|
|
332
|
+
const attrs = { jid };
|
|
333
|
+
if (force)
|
|
334
|
+
attrs.reason = 'identity';
|
|
335
|
+
return { tag: 'user', attrs };
|
|
336
|
+
})
|
|
236
337
|
}
|
|
237
338
|
]
|
|
238
339
|
});
|
|
239
|
-
await
|
|
340
|
+
await parseAndInjectE2ESessions(result, signalRepository);
|
|
240
341
|
didFetchNewSession = true;
|
|
241
342
|
}
|
|
242
343
|
return didFetchNewSession;
|
|
243
344
|
};
|
|
244
|
-
|
|
245
|
-
|
|
246
345
|
const sendPeerDataOperationMessage = async (pdoMessage) => {
|
|
346
|
+
//TODO: for later, abstract the logic to send a Peer Message instead of just PDO - useful for App State Key Resync with phone
|
|
247
347
|
if (!authState.creds.me?.id) {
|
|
248
|
-
throw new
|
|
348
|
+
throw new Boom('Not authenticated');
|
|
249
349
|
}
|
|
250
|
-
|
|
251
350
|
const protocolMessage = {
|
|
252
351
|
protocolMessage: {
|
|
253
352
|
peerDataOperationRequestMessage: pdoMessage,
|
|
254
|
-
type:
|
|
353
|
+
type: proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
|
|
255
354
|
}
|
|
256
355
|
};
|
|
257
|
-
const meJid =
|
|
356
|
+
const meJid = jidNormalizedUser(authState.creds.me.id);
|
|
258
357
|
const msgId = await relayMessage(meJid, protocolMessage, {
|
|
259
358
|
additionalAttributes: {
|
|
260
359
|
category: 'peer',
|
|
261
|
-
|
|
262
|
-
push_priority: 'high_force',
|
|
360
|
+
push_priority: 'high_force'
|
|
263
361
|
},
|
|
362
|
+
additionalNodes: [
|
|
363
|
+
{
|
|
364
|
+
tag: 'meta',
|
|
365
|
+
attrs: { appdata: 'default' }
|
|
366
|
+
}
|
|
367
|
+
]
|
|
264
368
|
});
|
|
265
369
|
return msgId;
|
|
266
370
|
};
|
|
267
|
-
const createParticipantNodes = async (
|
|
268
|
-
|
|
269
|
-
|
|
371
|
+
const createParticipantNodes = async (recipientJids, message, extraAttrs, dsmMessage) => {
|
|
372
|
+
if (!recipientJids.length) {
|
|
373
|
+
return { nodes: [], shouldIncludeDeviceIdentity: false };
|
|
374
|
+
}
|
|
375
|
+
const patched = await patchMessageBeforeSending(message, recipientJids);
|
|
376
|
+
const patchedMessages = Array.isArray(patched)
|
|
377
|
+
? patched
|
|
378
|
+
: recipientJids.map(jid => ({ recipientJid: jid, message: patched }));
|
|
270
379
|
let shouldIncludeDeviceIdentity = false;
|
|
271
|
-
const nodes = await Promise.all(jids.map(async (jid) => {
|
|
272
|
-
const { type, ciphertext } = await signalRepository
|
|
273
|
-
.encryptMessage({ jid, data: bytes });
|
|
274
|
-
if (type === 'pkmsg') {
|
|
275
|
-
shouldIncludeDeviceIdentity = true;
|
|
276
|
-
}
|
|
277
|
-
const node = {
|
|
278
|
-
tag: 'to',
|
|
279
|
-
attrs: { jid },
|
|
280
|
-
content: [{
|
|
281
|
-
tag: 'enc',
|
|
282
|
-
attrs: {
|
|
283
|
-
v: '2',
|
|
284
|
-
type,
|
|
285
|
-
...extraAttrs || {}
|
|
286
|
-
},
|
|
287
|
-
content: ciphertext
|
|
288
|
-
}]
|
|
289
|
-
};
|
|
290
|
-
return node;
|
|
291
|
-
}));
|
|
292
|
-
return { nodes, shouldIncludeDeviceIdentity };
|
|
293
|
-
}; //apela
|
|
294
|
-
const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, cachedGroupMetadata, useCachedGroupMetadata, statusJidList, AI = true }) => {
|
|
295
380
|
const meId = authState.creds.me.id;
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const meMsg = {
|
|
313
|
-
deviceSentMessage: {
|
|
314
|
-
destinationJid,
|
|
315
|
-
message
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
const extraAttrs = {}
|
|
319
|
-
const messages = Utils_1.normalizeMessageContent(message)
|
|
320
|
-
const buttonType = getButtonType(messages);
|
|
321
|
-
if (participant) {
|
|
322
|
-
// when the retry request is not for a group
|
|
323
|
-
// only send to the specific device that asked for a retry
|
|
324
|
-
// otherwise the message is sent out to every device that should be a recipient
|
|
325
|
-
if (!isGroup && !isStatus) {
|
|
326
|
-
additionalAttributes = { ...additionalAttributes, 'device_fanout': 'false' };
|
|
327
|
-
}
|
|
328
|
-
const { user, device } = WABinary_1.jidDecode(participant.jid);
|
|
329
|
-
devices.push({ user, device });
|
|
330
|
-
}
|
|
331
|
-
await authState.keys.transaction(async () => {
|
|
332
|
-
const mediaType = getMediaType(messages);
|
|
333
|
-
|
|
334
|
-
if (mediaType) {
|
|
335
|
-
extraAttrs['mediatype'] = mediaType
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (messages.pinInChatMessage || messages.keepInChatMessage || message.reactionMessage || message.protocolMessage?.editedMessage) {
|
|
339
|
-
extraAttrs['decrypt-fail'] = 'hide'
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (messages.interactiveResponseMessage?.nativeFlowResponseMessage) {
|
|
343
|
-
extraAttrs['native_flow_name'] = messages.interactiveResponseMessage?.nativeFlowResponseMessage.name
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (isGroup || isStatus) {
|
|
347
|
-
const [groupData, senderKeyMap] = await Promise.all([
|
|
348
|
-
(async () => {
|
|
349
|
-
let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined
|
|
350
|
-
if (groupData) {
|
|
351
|
-
logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata');
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
else if (!isStatus) {
|
|
355
|
-
groupData = await groupMetadata(jid)
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
return groupData;
|
|
359
|
-
})(),
|
|
360
|
-
(async () => {
|
|
361
|
-
if (!participant && !isStatus) {
|
|
362
|
-
const result = await authState.keys.get('sender-key-memory', [jid])
|
|
363
|
-
return result[jid] || {}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
return {}
|
|
367
|
-
|
|
368
|
-
})()
|
|
369
|
-
]);
|
|
370
|
-
if (!participant) {
|
|
371
|
-
const participantsList = (groupData && !isStatus) ? groupData.participants.map(p => p.id) : []
|
|
372
|
-
|
|
373
|
-
if (isStatus && statusJidList) {
|
|
374
|
-
participantsList.push(...statusJidList)
|
|
381
|
+
const meLid = authState.creds.me?.lid;
|
|
382
|
+
const meLidUser = meLid ? jidDecode(meLid)?.user : null;
|
|
383
|
+
const encryptionPromises = patchedMessages.map(async ({ recipientJid: jid, message: patchedMessage }) => {
|
|
384
|
+
try {
|
|
385
|
+
if (!jid)
|
|
386
|
+
return null;
|
|
387
|
+
let msgToEncrypt = patchedMessage;
|
|
388
|
+
if (dsmMessage) {
|
|
389
|
+
const { user: targetUser } = jidDecode(jid);
|
|
390
|
+
const { user: ownPnUser } = jidDecode(meId);
|
|
391
|
+
const ownLidUser = meLidUser;
|
|
392
|
+
const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser);
|
|
393
|
+
const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
|
|
394
|
+
if (isOwnUser && !isExactSenderDevice) {
|
|
395
|
+
msgToEncrypt = dsmMessage;
|
|
396
|
+
logger.debug({ jid, targetUser }, 'Using DSM for own device');
|
|
375
397
|
}
|
|
376
|
-
|
|
377
|
-
// if (!isStatus) {
|
|
378
|
-
// const expiration = await getEphemeralGroup(jid)
|
|
379
|
-
// additionalAttributes = {
|
|
380
|
-
// ...additionalAttributes,
|
|
381
|
-
// addressing_mode: 'pn',
|
|
382
|
-
// ...expiration ? { expiration: expiration.toString() } : null
|
|
383
|
-
// }
|
|
384
|
-
// }
|
|
385
|
-
|
|
386
|
-
const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false)
|
|
387
|
-
devices.push(...additionalDevices)
|
|
388
398
|
}
|
|
389
|
-
|
|
390
|
-
const
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
data: bytes,
|
|
396
|
-
meId,
|
|
397
|
-
});
|
|
398
|
-
const senderKeyJids = [];
|
|
399
|
-
|
|
400
|
-
for (const { user, device } of devices) {
|
|
401
|
-
const jid = WABinary_1.jidEncode(user, (groupData === null || groupData === void 0 ? void 0 : groupData.addressingMode) === 'lid' ? 'lid' : 's.whatsapp.net', device);
|
|
402
|
-
if (!senderKeyMap[jid] || !!participant) {
|
|
403
|
-
senderKeyJids.push(jid);
|
|
404
|
-
// store that this person has had the sender keys sent to them
|
|
405
|
-
senderKeyMap[jid] = true;
|
|
399
|
+
const bytes = encodeWAMessage(msgToEncrypt);
|
|
400
|
+
const mutexKey = jid;
|
|
401
|
+
const node = await encryptionMutex.mutex(mutexKey, async () => {
|
|
402
|
+
const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes });
|
|
403
|
+
if (type === 'pkmsg') {
|
|
404
|
+
shouldIncludeDeviceIdentity = true;
|
|
406
405
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
406
|
+
return {
|
|
407
|
+
tag: 'to',
|
|
408
|
+
attrs: { jid },
|
|
409
|
+
content: [
|
|
410
|
+
{
|
|
411
|
+
tag: 'enc',
|
|
412
|
+
attrs: { v: '2', type, ...(extraAttrs || {}) },
|
|
413
|
+
content: ciphertext
|
|
414
|
+
}
|
|
415
|
+
]
|
|
417
416
|
};
|
|
418
|
-
await assertSessions(senderKeyJids, false);
|
|
419
|
-
const result = await createParticipantNodes(senderKeyJids, senderKeyMsg, extraAttrs)
|
|
420
|
-
shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity;
|
|
421
|
-
participants.push(...result.nodes);
|
|
422
|
-
}
|
|
423
|
-
binaryNodeContent.push({
|
|
424
|
-
tag: 'enc',
|
|
425
|
-
attrs: { v: '2', type: 'skmsg', ...extraAttrs },
|
|
426
|
-
content: ciphertext
|
|
427
417
|
});
|
|
428
|
-
|
|
418
|
+
return node;
|
|
429
419
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
msgId = message.protocolMessage.key?.id
|
|
434
|
-
message = message.protocolMessage.editedMessage
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// Message delete
|
|
438
|
-
if (message.protocolMessage?.type === WAProto_1.proto.Message.ProtocolMessage.Type.REVOKE) {
|
|
439
|
-
msgId = message.protocolMessage.key?.id
|
|
440
|
-
message = {}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const patched = await patchMessageBeforeSending(message, [])
|
|
444
|
-
const bytes = Utils_1.encodeNewsletterMessage(patched)
|
|
445
|
-
|
|
446
|
-
binaryNodeContent.push({
|
|
447
|
-
tag: 'plaintext',
|
|
448
|
-
attrs: extraAttrs ? extraAttrs : {},
|
|
449
|
-
content: bytes
|
|
450
|
-
})
|
|
420
|
+
catch (err) {
|
|
421
|
+
logger.error({ jid, err }, 'Failed to encrypt for recipient');
|
|
422
|
+
return null;
|
|
451
423
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
424
|
+
});
|
|
425
|
+
const nodes = (await Promise.all(encryptionPromises)).filter(node => node !== null);
|
|
426
|
+
if (recipientJids.length > 0 && nodes.length === 0) {
|
|
427
|
+
throw new Boom('All encryptions failed', { statusCode: 500 });
|
|
428
|
+
}
|
|
429
|
+
return { nodes, shouldIncludeDeviceIdentity };
|
|
430
|
+
};
|
|
431
|
+
const relayMessage = async (
|
|
432
|
+
jid,
|
|
433
|
+
message,
|
|
434
|
+
{
|
|
435
|
+
messageId: msgId,
|
|
436
|
+
participant = false,
|
|
437
|
+
additionalAttributes,
|
|
438
|
+
additionalNodes,
|
|
439
|
+
useUserDevicesCache,
|
|
440
|
+
useCachedGroupMetadata,
|
|
441
|
+
statusJidList
|
|
442
|
+
}
|
|
443
|
+
) => {
|
|
444
|
+
const meId = authState.creds.me.id
|
|
445
|
+
const meLid = authState.creds.me?.lid
|
|
446
|
+
const isRetryResend = Boolean(participant?.jid)
|
|
447
|
+
let shouldIncludeDeviceIdentity = isRetryResend
|
|
448
|
+
const statusJid = 'status@broadcast'
|
|
449
|
+
const { user, server } = jidDecode(jid)
|
|
450
|
+
const isGroup = server === 'g.us'
|
|
451
|
+
const isStatus = jid === statusJid
|
|
452
|
+
const isLid = server === 'lid'
|
|
453
|
+
const isNewsletter = server === 'newsletter'
|
|
454
|
+
const isInterop = isInteropUser(jid)
|
|
455
|
+
const isGroupOrStatus = isGroup || isStatus
|
|
456
|
+
const finalJid = jid
|
|
457
|
+
msgId = msgId || generateMessageIDV2(meId)
|
|
458
|
+
useUserDevicesCache = useUserDevicesCache!== false
|
|
459
|
+
useCachedGroupMetadata = useCachedGroupMetadata!== false &&!isStatus
|
|
460
|
+
const participants = []
|
|
461
|
+
const destinationJid =!isStatus? finalJid : statusJid
|
|
462
|
+
const binaryNodeContent = []
|
|
463
|
+
const devices = []
|
|
464
|
+
let reportingMessage
|
|
465
|
+
const meMsg = {
|
|
466
|
+
deviceSentMessage: { destinationJid, message },
|
|
467
|
+
messageContextInfo: message.messageContextInfo
|
|
468
|
+
}
|
|
469
|
+
const extraAttrs = {}
|
|
470
|
+
const regexGroupOld = /^(\d{1,15})-(\d+)@g\.us$/
|
|
471
|
+
const messages = normalizeMessageContent(message)
|
|
472
|
+
const buttonType = getButtonType(messages)
|
|
473
|
+
const pollMessage =
|
|
474
|
+
messages.pollCreationMessage || messages.pollCreationMessageV2 || messages.pollCreationMessageV3
|
|
475
|
+
await authState.keys.transaction(async () => {
|
|
476
|
+
const mediaType = getMediaType(message)
|
|
477
|
+
if (mediaType) extraAttrs.mediatype = mediaType
|
|
478
|
+
if (isNewsletter) {
|
|
479
|
+
const patched = patchMessageBeforeSending? await patchMessageBeforeSending(message, []) : message
|
|
480
|
+
const bytes = encodeNewsletterMessage(patched)
|
|
481
|
+
binaryNodeContent.push({ tag: 'plaintext', attrs: {}, content: bytes })
|
|
482
|
+
const stanza = {
|
|
483
|
+
tag: 'message',
|
|
484
|
+
attrs: {
|
|
485
|
+
to: jid,
|
|
486
|
+
id: msgId,
|
|
487
|
+
type: getMessageType(message),
|
|
488
|
+
...(additionalAttributes || {})
|
|
489
|
+
},
|
|
490
|
+
content: binaryNodeContent
|
|
491
|
+
}
|
|
492
|
+
logger.debug({ msgId }, `sending newsletter message to ${jid}`)
|
|
493
|
+
await sendNode(stanza)
|
|
494
|
+
return
|
|
495
|
+
}
|
|
496
|
+
if (normalizeMessageContent(message)?.pinInChatMessage || normalizeMessageContent(message)?.reactionMessage) {
|
|
497
|
+
extraAttrs['decrypt-fail'] = 'hide'
|
|
498
|
+
}
|
|
499
|
+
if (isGroupOrStatus &&!isRetryResend) {
|
|
500
|
+
const [groupData, senderKeyMap] = await Promise.all([
|
|
501
|
+
(async () => {
|
|
502
|
+
let groupData = useCachedGroupMetadata && cachedGroupMetadata? await cachedGroupMetadata(jid) : undefined
|
|
503
|
+
if (groupData && Array.isArray(groupData?.participants)) {
|
|
504
|
+
logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata')
|
|
505
|
+
} else if (!isStatus) {
|
|
506
|
+
groupData = await groupMetadata(jid)
|
|
507
|
+
}
|
|
508
|
+
return groupData
|
|
509
|
+
})(),
|
|
510
|
+
(async () => {
|
|
511
|
+
if (!participant &&!isStatus) {
|
|
512
|
+
const result = await authState.keys.get('sender-key-memory', [jid])
|
|
513
|
+
return result[jid] || {}
|
|
514
|
+
}
|
|
515
|
+
return {}
|
|
516
|
+
})()
|
|
517
|
+
])
|
|
518
|
+
const participantsList = groupData? groupData.participants.map(p => p.id) : []
|
|
519
|
+
if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) {
|
|
520
|
+
additionalAttributes = {...additionalAttributes, expiration: groupData.ephemeralDuration.toString() }
|
|
521
|
+
}
|
|
522
|
+
if (isStatus && statusJidList) participantsList.push(...statusJidList)
|
|
523
|
+
const additionalDevices = await getUSyncDevices(participantsList,!!useUserDevicesCache, false)
|
|
524
|
+
devices.push(...additionalDevices)
|
|
525
|
+
if (isGroup) {
|
|
526
|
+
additionalAttributes = {
|
|
527
|
+
...additionalAttributes,
|
|
528
|
+
addressing_mode: groupData?.addressingMode || 'lid'
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (message?.groupStatusMessageV2 &&!message?.messageContextInfo?.messageSecret) {
|
|
532
|
+
message = {
|
|
533
|
+
...message,
|
|
534
|
+
messageContextInfo: {
|
|
535
|
+
...(message.messageContextInfo || {}),
|
|
536
|
+
messageSecret: randomBytes(32)
|
|
537
|
+
},
|
|
538
|
+
groupStatusMessageV2: {
|
|
539
|
+
...message.groupStatusMessageV2,
|
|
540
|
+
message: {
|
|
541
|
+
...(message.groupStatusMessageV2.message || {}),
|
|
542
|
+
messageContextInfo: {
|
|
543
|
+
...(message.groupStatusMessageV2.message?.messageContextInfo || {}),
|
|
544
|
+
messageSecret: message.messageContextInfo?.messageSecret || randomBytes(32)
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// list/buttons/template -> interactiveMessage
|
|
551
|
+
if (message.listMessage) {
|
|
552
|
+
const list = message.listMessage
|
|
553
|
+
message = {
|
|
554
|
+
interactiveMessage: {
|
|
555
|
+
nativeFlowMessage: {
|
|
556
|
+
buttons: [
|
|
557
|
+
{
|
|
558
|
+
name: 'single_select',
|
|
559
|
+
buttonParamsJson: JSON.stringify({
|
|
560
|
+
title: list.buttonText || 'Select',
|
|
561
|
+
sections: (list.sections || []).map(section => ({
|
|
562
|
+
title: section.title || '',
|
|
563
|
+
highlight_label: '',
|
|
564
|
+
rows: (section.rows || []).map(row => ({
|
|
565
|
+
header: '',
|
|
566
|
+
title: row.title || '',
|
|
567
|
+
description: row.description || '',
|
|
568
|
+
id: row.rowId || row.id || ''
|
|
569
|
+
}))
|
|
570
|
+
}))
|
|
571
|
+
})
|
|
572
|
+
}
|
|
573
|
+
],
|
|
574
|
+
messageParamsJson: '',
|
|
575
|
+
messageVersion: 1
|
|
576
|
+
},
|
|
577
|
+
body: { text: list.description || '' },
|
|
578
|
+
footer: list.footerText? { text: list.footerText } : undefined,
|
|
579
|
+
header: list.title? { title: list.title, hasMediaAttachment: false, subtitle: '' } : undefined,
|
|
580
|
+
contextInfo: list.contextInfo
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
} else if (message.buttonsMessage) {
|
|
584
|
+
const bMsg = message.buttonsMessage
|
|
585
|
+
const buttons = (bMsg.buttons || []).map(btn => ({
|
|
586
|
+
name: 'quick_reply',
|
|
587
|
+
buttonParamsJson: JSON.stringify({
|
|
588
|
+
display_text: btn.buttonText?.displayText || btn.buttonText || '',
|
|
589
|
+
id: btn.buttonId || btn.buttonText?.displayText || ''
|
|
590
|
+
})
|
|
591
|
+
}))
|
|
592
|
+
message = {
|
|
593
|
+
interactiveMessage: {
|
|
594
|
+
nativeFlowMessage: { buttons, messageParamsJson: '', messageVersion: 1 },
|
|
595
|
+
body: { text: bMsg.contentText || bMsg.text || '' },
|
|
596
|
+
footer: bMsg.footerText? { text: bMsg.footerText } : undefined,
|
|
597
|
+
header: bMsg.text
|
|
598
|
+
? { title: bMsg.text, hasMediaAttachment: false, subtitle: '' }
|
|
599
|
+
: bMsg.imageMessage || bMsg.videoMessage || bMsg.documentMessage
|
|
600
|
+
? { hasMediaAttachment: true,...(bMsg.imageMessage? { imageMessage: bMsg.imageMessage } : {}),...(bMsg.videoMessage? { videoMessage: bMsg.videoMessage } : {}) }
|
|
601
|
+
: undefined,
|
|
602
|
+
contextInfo: bMsg.contextInfo
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
} else if (message.templateMessage) {
|
|
606
|
+
const tmpl = message.templateMessage.hydratedTemplate || message.templateMessage.fourRowTemplate
|
|
607
|
+
if (tmpl) {
|
|
608
|
+
const buttons = (tmpl.hydratedButtons || [])
|
|
609
|
+
.map(hBtn => {
|
|
610
|
+
if (hBtn.quickReplyButton) {
|
|
611
|
+
return { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: hBtn.quickReplyButton.displayText || '', id: hBtn.quickReplyButton.id || hBtn.quickReplyButton.displayText || '' }) }
|
|
612
|
+
} else if (hBtn.urlButton) {
|
|
613
|
+
return { name: 'cta_url', buttonParamsJson: JSON.stringify({ display_text: hBtn.urlButton.displayText || '', url: hBtn.urlButton.url || '', merchant_url: hBtn.urlButton.url || '' }) }
|
|
614
|
+
} else if (hBtn.callButton) {
|
|
615
|
+
return { name: 'cta_call', buttonParamsJson: JSON.stringify({ display_text: hBtn.callButton.displayText || '', phone_number: hBtn.callButton.phoneNumber || '' }) }
|
|
616
|
+
}
|
|
617
|
+
return null
|
|
618
|
+
})
|
|
619
|
+
.filter(Boolean)
|
|
620
|
+
message = {
|
|
621
|
+
interactiveMessage: {
|
|
622
|
+
nativeFlowMessage: { buttons, messageParamsJson: '', messageVersion: 1 },
|
|
623
|
+
body: { text: tmpl.hydratedContentText || tmpl.contentText || '' },
|
|
624
|
+
footer: tmpl.hydratedFooterText? { text: tmpl.hydratedFooterText } : undefined,
|
|
625
|
+
header: tmpl.hydratedTitleText
|
|
626
|
+
? { title: tmpl.hydratedTitleText, hasMediaAttachment: false, subtitle: '' }
|
|
627
|
+
: tmpl.imageMessage || tmpl.videoMessage || tmpl.documentMessage
|
|
628
|
+
? { hasMediaAttachment: true,...(tmpl.imageMessage? { imageMessage: tmpl.imageMessage } : {}),...(tmpl.videoMessage? { videoMessage: tmpl.videoMessage } : {}) }
|
|
629
|
+
: undefined,
|
|
630
|
+
contextInfo: tmpl.contextInfo
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
476
635
|
|
|
477
|
-
|
|
478
|
-
|
|
636
|
+
const patched = await patchMessageBeforeSending(message)
|
|
637
|
+
if (Array.isArray(patched)) throw new Boom('Per-jid patching is not supported in groups')
|
|
638
|
+
const bytes = encodeWAMessage(patched)
|
|
639
|
+
reportingMessage = patched
|
|
640
|
+
const groupAddressingMode = additionalAttributes?.['addressing_mode'] || groupData?.addressingMode || 'lid'
|
|
641
|
+
const groupSenderIdentity = groupAddressingMode === 'lid' && meLid? meLid : meId
|
|
642
|
+
const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
|
|
643
|
+
group: destinationJid,
|
|
644
|
+
data: bytes,
|
|
645
|
+
meId: groupSenderIdentity
|
|
646
|
+
})
|
|
647
|
+
const senderKeyRecipients = []
|
|
648
|
+
for (const device of devices) {
|
|
649
|
+
const deviceJid = device.jid
|
|
650
|
+
const hasKey =!!senderKeyMap[deviceJid]
|
|
651
|
+
if (!hasKey ||!!participant &&!isHostedLidUser(deviceJid) &&!isHostedPnUser(deviceJid) && device.device!== 99) {
|
|
652
|
+
senderKeyRecipients.push(deviceJid)
|
|
653
|
+
senderKeyMap[deviceJid] = true
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
if (senderKeyRecipients.length) {
|
|
657
|
+
logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending new sender key')
|
|
658
|
+
const senderKeyMsg = {
|
|
659
|
+
senderKeyDistributionMessage: {
|
|
660
|
+
axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage,
|
|
661
|
+
groupId: destinationJid
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
await assertSessions(senderKeyRecipients)
|
|
665
|
+
const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs)
|
|
666
|
+
shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity
|
|
667
|
+
participants.push(...result.nodes)
|
|
668
|
+
}
|
|
669
|
+
binaryNodeContent.push({ tag: 'enc', attrs: { v: '2', type: 'skmsg',...extraAttrs }, content: ciphertext })
|
|
670
|
+
await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } })
|
|
671
|
+
} else {
|
|
672
|
+
let ownId = meId
|
|
673
|
+
if (isLid && meLid) {
|
|
674
|
+
ownId = meLid
|
|
675
|
+
logger.debug({ to: jid, ownId }, 'Using LID identity for @lid conversation')
|
|
676
|
+
} else {
|
|
677
|
+
logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation')
|
|
678
|
+
}
|
|
679
|
+
const { user: ownUser } = jidDecode(ownId)
|
|
680
|
+
if (!participant) {
|
|
681
|
+
const patchedForReporting = await patchMessageBeforeSending(message, [jid])
|
|
682
|
+
reportingMessage = Array.isArray(patchedForReporting)
|
|
683
|
+
? patchedForReporting.find(item => item.recipientJid === jid) || patchedForReporting[0]
|
|
684
|
+
: patchedForReporting
|
|
685
|
+
}
|
|
686
|
+
if (!isRetryResend) {
|
|
687
|
+
const targetUserServer = isLid? 'lid' : isInterop? 'interop' : 's.whatsapp.net'
|
|
688
|
+
devices.push({ user, device: 0, jid: jidEncode(user, targetUserServer, 0) })
|
|
689
|
+
if (user!== ownUser &&!isInterop) {
|
|
690
|
+
const ownUserServer = isLid? 'lid' : 's.whatsapp.net'
|
|
691
|
+
const ownUserForAddressing = isLid && meLid? jidDecode(meLid).user : jidDecode(meId).user
|
|
692
|
+
devices.push({ user: ownUserForAddressing, device: 0, jid: jidEncode(ownUserForAddressing, ownUserServer, 0) })
|
|
693
|
+
}
|
|
694
|
+
if (additionalAttributes?.['category']!== 'peer' &&!isInterop) {
|
|
695
|
+
devices.length = 0
|
|
696
|
+
const senderIdentity = isLid && meLid
|
|
697
|
+
? jidEncode(jidDecode(meLid)?.user, 'lid', undefined)
|
|
698
|
+
: jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined)
|
|
699
|
+
const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false)
|
|
700
|
+
devices.push(...sessionDevices)
|
|
701
|
+
logger.debug({ deviceCount: devices.length, devices: devices.map(d => `${d.user}:${d.device}@${jidDecode(d.jid)?.server}`) }, 'Device enumeration complete with unified addressing')
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
const allRecipients = []
|
|
705
|
+
const meRecipients = []
|
|
706
|
+
const otherRecipients = []
|
|
707
|
+
const { user: mePnUser } = jidDecode(meId)
|
|
708
|
+
const { user: meLidUser } = meLid? jidDecode(meLid) : { user: null }
|
|
709
|
+
for (const { user, jid } of devices) {
|
|
710
|
+
/** participant method by Xzc || Tsm, who's delete the credits = love BBC */
|
|
711
|
+
const isExactSenderDevice = jid === meId || (meLid && jid === meLid)
|
|
712
|
+
if (isExactSenderDevice) {
|
|
713
|
+
logger.debug({ jid, meId, meLid }, 'Skipping exact sender device (whatsmeow pattern)')
|
|
714
|
+
continue
|
|
479
715
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
participants.push(...meNodes);
|
|
489
|
-
participants.push(...otherNodes);
|
|
490
|
-
shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2;
|
|
491
|
-
}
|
|
492
|
-
if (participants.length) {
|
|
493
|
-
if (additionalAttributes?.['category'] === 'peer') {
|
|
494
|
-
const peerNode = participants[0]?.content?.[0]
|
|
495
|
-
|
|
496
|
-
if (peerNode) {
|
|
497
|
-
binaryNodeContent.push(peerNode) // push only enc
|
|
716
|
+
const isMe = user === mePnUser || user === meLidUser
|
|
717
|
+
let ptcp = false
|
|
718
|
+
if (participant) {
|
|
719
|
+
if (!isJidGroup(jid) && !isStatus) {
|
|
720
|
+
if (!(!isMe)) ptcp = true
|
|
721
|
+
} else {
|
|
722
|
+
ptcp = false
|
|
723
|
+
}
|
|
498
724
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
})
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
const stanza = {
|
|
511
|
-
tag: 'message',
|
|
512
|
-
attrs: {
|
|
513
|
-
id: msgId,
|
|
514
|
-
type: getTypeMessage(messages),
|
|
515
|
-
...(additionalAttributes || {})
|
|
516
|
-
},
|
|
517
|
-
content: binaryNodeContent
|
|
518
|
-
}
|
|
519
|
-
// if the participant to send to is explicitly specified (generally retry recp)
|
|
520
|
-
// ensure the message is only sent to that person
|
|
521
|
-
// if a retry receipt is sent to everyone -- it'll fail decryption for everyone else who received the msg
|
|
522
|
-
if (participant) {
|
|
523
|
-
if (WABinary_1.isJidGroup(destinationJid)) {
|
|
524
|
-
stanza.attrs.to = destinationJid;
|
|
525
|
-
stanza.attrs.participant = participant.jid;
|
|
526
|
-
}
|
|
527
|
-
else if (WABinary_1.areJidsSameUser(participant.jid, meId)) {
|
|
528
|
-
stanza.attrs.to = participant.jid;
|
|
529
|
-
stanza.attrs.recipient = destinationJid;
|
|
530
|
-
}
|
|
531
|
-
else {
|
|
532
|
-
stanza.attrs.to = participant.jid;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
else {
|
|
536
|
-
stanza.attrs.to = destinationJid;
|
|
537
|
-
}
|
|
538
|
-
if (shouldIncludeDeviceIdentity) {
|
|
539
|
-
stanza.content.push({
|
|
540
|
-
tag: 'device-identity',
|
|
541
|
-
attrs: {},
|
|
542
|
-
content: (0, Utils_1.encodeSignedDeviceIdentity)(authState.creds.account, true)
|
|
543
|
-
});
|
|
544
|
-
logger.debug({ jid }, 'adding device identity');
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
if (AI && isPrivate) {
|
|
548
|
-
const botNode = {
|
|
549
|
-
tag: 'bot',
|
|
550
|
-
attrs: {
|
|
551
|
-
biz_bot: '1'
|
|
725
|
+
if (!ptcp) {
|
|
726
|
+
if (isMe) {
|
|
727
|
+
meRecipients.push(jid)
|
|
728
|
+
} else {
|
|
729
|
+
otherRecipients.push(jid)
|
|
730
|
+
}
|
|
731
|
+
allRecipients.push(jid)
|
|
552
732
|
}
|
|
553
733
|
}
|
|
734
|
+
await assertSessions(allRecipients)
|
|
735
|
+
const [
|
|
736
|
+
{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 },
|
|
737
|
+
{ nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }
|
|
738
|
+
] = await Promise.all([
|
|
739
|
+
createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
|
|
740
|
+
createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)
|
|
741
|
+
])
|
|
742
|
+
participants.push(...meNodes,...otherNodes)
|
|
743
|
+
if (meRecipients.length > 0 || otherRecipients.length > 0) {
|
|
744
|
+
extraAttrs.phash = generateParticipantHashV2([...meRecipients,...otherRecipients])
|
|
745
|
+
}
|
|
746
|
+
shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2
|
|
747
|
+
}
|
|
748
|
+
if (isRetryResend) {
|
|
749
|
+
const isParticipantLid = jidDecode(participant.jid).server === 'lid'
|
|
750
|
+
const isMe = areJidsSameUser(participant.jid, isParticipantLid? meLid : meId)
|
|
751
|
+
const encodedMessageToSend = isMe
|
|
752
|
+
? encodeWAMessage({ deviceSentMessage: { destinationJid, message } })
|
|
753
|
+
: encodeWAMessage(message)
|
|
754
|
+
const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({
|
|
755
|
+
data: encodedMessageToSend,
|
|
756
|
+
jid: participant.jid
|
|
757
|
+
})
|
|
758
|
+
binaryNodeContent.push({
|
|
759
|
+
tag: 'enc',
|
|
760
|
+
attrs: { v: '2', type, count: (participant.count?? 0).toString() },
|
|
761
|
+
content: encryptedContent
|
|
762
|
+
})
|
|
763
|
+
}
|
|
764
|
+
if (participants.length) {
|
|
765
|
+
if (additionalAttributes?.['category'] === 'peer') {
|
|
766
|
+
const peerNode = participants[0]?.content?.[0]
|
|
767
|
+
if (peerNode) binaryNodeContent.push(peerNode)
|
|
768
|
+
} else if (isInterop) {
|
|
769
|
+
const recipientNode = participants.find(p => isInteropUser(p?.attrs?.jid))
|
|
770
|
+
const encNode = (recipientNode?? participants[0])?.content?.[0]
|
|
771
|
+
if (encNode) binaryNodeContent.push(encNode)
|
|
772
|
+
} else {
|
|
773
|
+
binaryNodeContent.push({ tag: 'participants', attrs: {}, content: participants })
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
const stanza = {
|
|
777
|
+
tag: 'message',
|
|
778
|
+
attrs: { id: msgId, to: destinationJid, type: getMessageType(message),...(additionalAttributes || {}) },
|
|
779
|
+
content: binaryNodeContent
|
|
780
|
+
}
|
|
781
|
+
if (shouldIncludeDeviceIdentity) {
|
|
782
|
+
stanza.content.push({ tag: 'device-identity', attrs: {}, content: encodeSignedDeviceIdentity(authState.creds.account, true) })
|
|
783
|
+
logger.debug({ jid }, 'adding device identity')
|
|
784
|
+
}
|
|
554
785
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
786
|
+
if (isGroup && regexGroupOld.test(jid) &&!message.reactionMessage) {
|
|
787
|
+
stanza.content.push({ tag: 'multicast', attrs: {} })
|
|
788
|
+
}
|
|
789
|
+
if (pollMessage || messages.eventMessage) {
|
|
790
|
+
stanza.content.push({
|
|
791
|
+
tag: 'meta',
|
|
792
|
+
attrs: messages.eventMessage
|
|
793
|
+
? { event_type: 'creation' }
|
|
794
|
+
: isNewsletter
|
|
795
|
+
? { polltype: 'creation', contenttype: pollMessage?.pollContentType === 2? 'image' : 'text' }
|
|
796
|
+
: { polltype: 'creation' }
|
|
797
|
+
})
|
|
798
|
+
}
|
|
799
|
+
if (!isNewsletter &&!isRetryResend && reportingMessage?.messageContextInfo?.messageSecret && shouldIncludeReportingToken(reportingMessage)) {
|
|
800
|
+
try {
|
|
801
|
+
const encoded = encodeWAMessage(reportingMessage)
|
|
802
|
+
const reportingKey = { id: msgId, fromMe: true, remoteJid: destinationJid, participant: participant?.jid }
|
|
803
|
+
const reportingNode = await getMessageReportingToken(encoded, reportingMessage, reportingKey)
|
|
804
|
+
if (reportingNode) {
|
|
805
|
+
stanza.content.push(reportingNode)
|
|
806
|
+
logger.trace({ jid }, 'added reporting token to message')
|
|
807
|
+
}
|
|
808
|
+
} catch (error) {
|
|
809
|
+
logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token')
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
let didPushAdditional = false
|
|
813
|
+
if (!isNewsletter && buttonType) {
|
|
814
|
+
const buttonsNode = getButtonArgs(messages)
|
|
815
|
+
const filteredButtons = getBinaryNodeFilter(additionalNodes? additionalNodes : [])
|
|
816
|
+
if (filteredButtons) {
|
|
817
|
+
stanza.content.push(...additionalNodes)
|
|
818
|
+
didPushAdditional = true
|
|
819
|
+
} else {
|
|
820
|
+
stanza.content.push(buttonsNode)
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
if (!aiLabel && isPnUser(destinationJid)) {
|
|
824
|
+
const alreadyHasBizBot = getBinaryFilteredBizBot(additionalNodes || []) || getBinaryFilteredBizBot(stanza.content)
|
|
825
|
+
if (!alreadyHasBizBot) stanza.content.push({ tag: 'bot', attrs: { biz_bot: '1' } })
|
|
826
|
+
} else if (aiLabel &&!isGroup &&!isStatus &&!isNewsletter) {
|
|
827
|
+
const existingBizBot = getBinaryFilteredBizBot(additionalNodes || [])
|
|
828
|
+
if (!existingBizBot) stanza.content.push({ tag: 'bot', attrs: { biz_bot: '1' } })
|
|
829
|
+
}
|
|
830
|
+
const isPeerMessage = additionalAttributes?.['category'] === 'peer'
|
|
831
|
+
const is1on1Send =!isGroup &&!isRetryResend &&!isStatus &&!isNewsletter &&!isPeerMessage
|
|
832
|
+
const tcTokenJid = is1on1Send? await resolveTcTokenJid(destinationJid, getLIDForPN) : destinationJid
|
|
833
|
+
const contactTcTokenData = is1on1Send? await authState.keys.get('tctoken', [tcTokenJid]) : {}
|
|
834
|
+
const existingTokenEntry = contactTcTokenData[tcTokenJid]
|
|
835
|
+
let tcTokenBuffer = existingTokenEntry?.token
|
|
836
|
+
if (tcTokenBuffer?.length && isTcTokenExpired(existingTokenEntry?.timestamp)) {
|
|
837
|
+
logger.debug({ jid: destinationJid, timestamp: existingTokenEntry?.timestamp }, 'tctoken expired, clearing')
|
|
838
|
+
tcTokenBuffer = undefined
|
|
839
|
+
const cleared = existingTokenEntry?.senderTimestamp!== undefined? { token: Buffer.alloc(0), senderTimestamp: existingTokenEntry.senderTimestamp } : null
|
|
840
|
+
try {
|
|
841
|
+
await authState.keys.set({ tctoken: { [tcTokenJid]: cleared } })
|
|
842
|
+
} catch (err) {
|
|
843
|
+
logger.debug({ jid: destinationJid, err: err?.message }, 'failed to persist tctoken expiry cleanup')
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
if (tcTokenBuffer?.length && sock.serverProps.privacyTokenOn1to1) {
|
|
847
|
+
stanza.content.push({ tag: 'tctoken', attrs: {}, content: tcTokenBuffer })
|
|
848
|
+
}
|
|
849
|
+
if (additionalNodes && additionalNodes.length > 0 &&!didPushAdditional) {
|
|
850
|
+
stanza.content.push(...additionalNodes)
|
|
851
|
+
}
|
|
852
|
+
logger.debug({ msgId }, `sending message to ${participants.length} devices`)
|
|
853
|
+
await sendNode(stanza)
|
|
854
|
+
if (message.messageContextInfo?.messageSecret) {
|
|
855
|
+
setBotMessageSecret(msgId, message.messageContextInfo.messageSecret, destinationJid)
|
|
856
|
+
}
|
|
857
|
+
const isProtocolMsg =!!normalizeMessageContent(message)?.protocolMessage
|
|
858
|
+
const isBotOrPSA = destinationJid === PSA_WID || isJidBot(destinationJid) || isJidMetaAI(destinationJid)
|
|
859
|
+
if (is1on1Send &&!isProtocolMsg &&!isBotOrPSA && shouldSendNewTcToken(existingTokenEntry?.senderTimestamp) &&!inFlightTcTokenIssuance.has(tcTokenJid)) {
|
|
860
|
+
inFlightTcTokenIssuance.add(tcTokenJid)
|
|
861
|
+
const issueTimestamp = unixTimestampSeconds()
|
|
862
|
+
const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping)
|
|
863
|
+
resolveIssuanceJid(destinationJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID)
|
|
864
|
+
.then(issueJid => issuePrivacyTokens([issueJid], issueTimestamp))
|
|
865
|
+
.then(async result => {
|
|
866
|
+
await storeTcTokensFromIqResult({ result, fallbackJid: tcTokenJid, keys: authState.keys, getLIDForPN })
|
|
867
|
+
const currentData = await authState.keys.get('tctoken', [tcTokenJid])
|
|
868
|
+
const currentEntry = currentData[tcTokenJid]
|
|
869
|
+
const indexWrite = await buildMergedTcTokenIndexWrite(authState.keys, [tcTokenJid])
|
|
870
|
+
await authState.keys.set({
|
|
871
|
+
tctoken: {
|
|
872
|
+
[tcTokenJid]: { token: Buffer.alloc(0),...currentEntry, senderTimestamp: issueTimestamp },
|
|
873
|
+
...indexWrite
|
|
874
|
+
}
|
|
875
|
+
})
|
|
876
|
+
})
|
|
877
|
+
.catch(err => logger.debug({ jid: destinationJid, err: err?.message }, 'fire-and-forget tctoken issuance failed'))
|
|
878
|
+
.finally(() => inFlightTcTokenIssuance.delete(tcTokenJid))
|
|
879
|
+
}
|
|
880
|
+
if (messageRetryManager &&!participant) {
|
|
881
|
+
messageRetryManager.addRecentMessage(destinationJid, msgId, message)
|
|
882
|
+
}
|
|
883
|
+
if (isInterop &&!isRetryResend) {
|
|
884
|
+
await trustInteropContact(destinationJid).catch(err => logger.debug({ err, jid: destinationJid }, 'failed to trust interop contact'))
|
|
885
|
+
}
|
|
886
|
+
}, meId)
|
|
887
|
+
return msgId
|
|
888
|
+
}
|
|
889
|
+
const getMessageType = (message) => {
|
|
890
|
+
const normalizedMessage = normalizeMessageContent(message);
|
|
891
|
+
if (!normalizedMessage)
|
|
892
|
+
return 'text';
|
|
893
|
+
if (normalizedMessage.reactionMessage || normalizedMessage.encReactionMessage) {
|
|
894
|
+
return 'reaction';
|
|
602
895
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
return 'reaction'
|
|
611
|
-
}
|
|
612
|
-
else if (getMediaType(message)) {
|
|
613
|
-
return 'media'
|
|
614
|
-
}
|
|
615
|
-
else {
|
|
616
|
-
return 'text'
|
|
896
|
+
if (normalizedMessage.pollCreationMessage ||
|
|
897
|
+
normalizedMessage.pollCreationMessageV2 ||
|
|
898
|
+
normalizedMessage.pollCreationMessageV3 ||
|
|
899
|
+
normalizedMessage.pollCreationMessageV4 ||
|
|
900
|
+
normalizedMessage.pollCreationMessageV5 ||
|
|
901
|
+
normalizedMessage.pollUpdateMessage) {
|
|
902
|
+
return 'poll';
|
|
617
903
|
}
|
|
618
|
-
|
|
619
|
-
|
|
904
|
+
if (normalizedMessage.eventMessage) {
|
|
905
|
+
return 'event';
|
|
906
|
+
}
|
|
907
|
+
if (getMediaType(normalizedMessage) !== '') {
|
|
908
|
+
return 'media';
|
|
909
|
+
}
|
|
910
|
+
return 'text';
|
|
911
|
+
};
|
|
620
912
|
const getMediaType = (message) => {
|
|
621
913
|
if (message.imageMessage) {
|
|
622
|
-
return 'image'
|
|
914
|
+
return 'image';
|
|
623
915
|
}
|
|
624
916
|
else if (message.videoMessage) {
|
|
625
|
-
return message.videoMessage.gifPlayback ? 'gif' : 'video'
|
|
917
|
+
return message.videoMessage.gifPlayback ? 'gif' : 'video';
|
|
626
918
|
}
|
|
627
919
|
else if (message.audioMessage) {
|
|
628
|
-
return message.audioMessage.ptt ? 'ptt' : 'audio'
|
|
920
|
+
return message.audioMessage.ptt ? 'ptt' : 'audio';
|
|
629
921
|
}
|
|
630
922
|
else if (message.contactMessage) {
|
|
631
|
-
return 'vcard'
|
|
923
|
+
return 'vcard';
|
|
632
924
|
}
|
|
633
925
|
else if (message.documentMessage) {
|
|
634
|
-
return 'document'
|
|
926
|
+
return 'document';
|
|
635
927
|
}
|
|
636
928
|
else if (message.contactsArrayMessage) {
|
|
637
|
-
return 'contact_array'
|
|
929
|
+
return 'contact_array';
|
|
638
930
|
}
|
|
639
931
|
else if (message.liveLocationMessage) {
|
|
640
|
-
return 'livelocation'
|
|
932
|
+
return 'livelocation';
|
|
641
933
|
}
|
|
642
934
|
else if (message.stickerMessage) {
|
|
643
|
-
return 'sticker'
|
|
935
|
+
return 'sticker';
|
|
644
936
|
}
|
|
645
937
|
else if (message.listMessage) {
|
|
646
|
-
return 'list'
|
|
938
|
+
return 'list';
|
|
647
939
|
}
|
|
648
940
|
else if (message.listResponseMessage) {
|
|
649
|
-
return 'list_response'
|
|
941
|
+
return 'list_response';
|
|
650
942
|
}
|
|
651
943
|
else if (message.buttonsResponseMessage) {
|
|
652
|
-
return 'buttons_response'
|
|
944
|
+
return 'buttons_response';
|
|
653
945
|
}
|
|
654
946
|
else if (message.orderMessage) {
|
|
655
|
-
return 'order'
|
|
947
|
+
return 'order';
|
|
656
948
|
}
|
|
657
949
|
else if (message.productMessage) {
|
|
658
|
-
return 'product'
|
|
950
|
+
return 'product';
|
|
659
951
|
}
|
|
660
952
|
else if (message.interactiveResponseMessage) {
|
|
661
|
-
return 'native_flow_response'
|
|
953
|
+
return 'native_flow_response';
|
|
662
954
|
}
|
|
663
955
|
else if (message.groupInviteMessage) {
|
|
664
|
-
return 'url'
|
|
956
|
+
return 'url';
|
|
665
957
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
958
|
+
return '';
|
|
959
|
+
};
|
|
671
960
|
const getButtonType = (message) => {
|
|
672
961
|
if (message.listMessage) {
|
|
673
962
|
return 'list'
|
|
@@ -683,26 +972,152 @@ const makeMessagesSocket = (config) => {
|
|
|
683
972
|
}
|
|
684
973
|
else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_info') {
|
|
685
974
|
return 'payment_info'
|
|
686
|
-
}
|
|
687
|
-
|
|
975
|
+
} else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_key_info') {
|
|
976
|
+
return 'payment_key_info'
|
|
977
|
+
} else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_status') {
|
|
688
978
|
return 'payment_status'
|
|
689
979
|
}
|
|
690
980
|
else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_method') {
|
|
691
981
|
return 'payment_method'
|
|
692
982
|
}
|
|
983
|
+
else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'catalog_message') {
|
|
984
|
+
return 'catalog_message'
|
|
985
|
+
}
|
|
693
986
|
else if (message.interactiveMessage && message.interactiveMessage?.nativeFlowMessage) {
|
|
694
987
|
return 'interactive'
|
|
695
988
|
}
|
|
696
989
|
else if (message.interactiveMessage?.nativeFlowMessage) {
|
|
697
990
|
return 'native_flow'
|
|
698
991
|
}
|
|
699
|
-
}
|
|
700
|
-
const
|
|
701
|
-
|
|
992
|
+
};
|
|
993
|
+
const getButtonArgs = (message) => {
|
|
994
|
+
const nativeFlow = message.interactiveMessage?.nativeFlowMessage
|
|
995
|
+
const firstButtonName = nativeFlow?.buttons?.[0]?.name
|
|
996
|
+
const nativeFlowSpecials = [
|
|
997
|
+
'mpm',
|
|
998
|
+
'cta_catalog',
|
|
999
|
+
'send_location',
|
|
1000
|
+
'call_permission_request',
|
|
1001
|
+
'wa_payment_transaction_details',
|
|
1002
|
+
'automated_greeting_message_view_catalog'
|
|
1003
|
+
]
|
|
1004
|
+
|
|
1005
|
+
if (nativeFlow && (firstButtonName === 'review_and_pay' || firstButtonName === 'payment_info')) {
|
|
1006
|
+
return {
|
|
1007
|
+
tag: 'biz',
|
|
1008
|
+
attrs: {
|
|
1009
|
+
native_flow_name: firstButtonName === 'review_and_pay' ? 'order_details' : firstButtonName
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
} else if (nativeFlow && nativeFlowSpecials.includes(firstButtonName)) {
|
|
1013
|
+
// Only works for WhatsApp Original, not WhatsApp Business
|
|
1014
|
+
return {
|
|
1015
|
+
tag: 'biz',
|
|
1016
|
+
attrs: {
|
|
1017
|
+
actual_actors: '2',
|
|
1018
|
+
host_storage: '2',
|
|
1019
|
+
privacy_mode_ts: Utils_1.unixTimestampSeconds().toString()
|
|
1020
|
+
},
|
|
1021
|
+
content: [
|
|
1022
|
+
{
|
|
1023
|
+
tag: 'interactive',
|
|
1024
|
+
attrs: {
|
|
1025
|
+
type: 'native_flow',
|
|
1026
|
+
v: '1'
|
|
1027
|
+
},
|
|
1028
|
+
content: [
|
|
1029
|
+
{
|
|
1030
|
+
tag: 'native_flow',
|
|
1031
|
+
attrs: {
|
|
1032
|
+
v: '2',
|
|
1033
|
+
name: firstButtonName
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
]
|
|
1037
|
+
},
|
|
1038
|
+
{
|
|
1039
|
+
tag: 'quality_control',
|
|
1040
|
+
attrs: {
|
|
1041
|
+
source_type: 'third_party'
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
]
|
|
1045
|
+
}
|
|
1046
|
+
} else if (nativeFlow || message.buttonsMessage) {
|
|
1047
|
+
// It works for whatsapp original and whatsapp business
|
|
1048
|
+
return {
|
|
1049
|
+
tag: 'biz',
|
|
1050
|
+
attrs: {
|
|
1051
|
+
actual_actors: '2',
|
|
1052
|
+
host_storage: '2',
|
|
1053
|
+
privacy_mode_ts: Utils_1.unixTimestampSeconds().toString()
|
|
1054
|
+
},
|
|
1055
|
+
content: [
|
|
1056
|
+
{
|
|
1057
|
+
tag: 'interactive',
|
|
1058
|
+
attrs: {
|
|
1059
|
+
type: 'native_flow',
|
|
1060
|
+
v: '1'
|
|
1061
|
+
},
|
|
1062
|
+
content: [
|
|
1063
|
+
{
|
|
1064
|
+
tag: 'native_flow',
|
|
1065
|
+
attrs: {
|
|
1066
|
+
v: '9',
|
|
1067
|
+
name: 'mixed'
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
]
|
|
1071
|
+
},
|
|
1072
|
+
{
|
|
1073
|
+
tag: 'quality_control',
|
|
1074
|
+
attrs: {
|
|
1075
|
+
source_type: 'third_party'
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
]
|
|
1079
|
+
}
|
|
1080
|
+
} else if (message.listMessage) {
|
|
1081
|
+
return {
|
|
1082
|
+
tag: 'biz',
|
|
1083
|
+
attrs: {
|
|
1084
|
+
actual_actors: '2',
|
|
1085
|
+
host_storage: '2',
|
|
1086
|
+
privacy_mode_ts: Utils_1.unixTimestampSeconds().toString()
|
|
1087
|
+
},
|
|
1088
|
+
content: [
|
|
1089
|
+
{
|
|
1090
|
+
tag: 'list',
|
|
1091
|
+
attrs: {
|
|
1092
|
+
v: '2',
|
|
1093
|
+
type: 'product_list'
|
|
1094
|
+
}
|
|
1095
|
+
},
|
|
1096
|
+
{
|
|
1097
|
+
tag: 'quality_control',
|
|
1098
|
+
attrs: {
|
|
1099
|
+
source_type: 'third_party'
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
]
|
|
1103
|
+
}
|
|
1104
|
+
} else {
|
|
1105
|
+
return {
|
|
1106
|
+
tag: 'biz',
|
|
1107
|
+
attrs: {
|
|
1108
|
+
actual_actors: '2',
|
|
1109
|
+
host_storage: '2',
|
|
1110
|
+
privacy_mode_ts: Utils_1.unixTimestampSeconds().toString()
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
const issuePrivacyTokens = async (jids, timestamp) => {
|
|
1116
|
+
const t = (timestamp ?? unixTimestampSeconds()).toString();
|
|
702
1117
|
const result = await query({
|
|
703
1118
|
tag: 'iq',
|
|
704
1119
|
attrs: {
|
|
705
|
-
to:
|
|
1120
|
+
to: S_WHATSAPP_NET,
|
|
706
1121
|
type: 'set',
|
|
707
1122
|
xmlns: 'privacy'
|
|
708
1123
|
},
|
|
@@ -713,7 +1128,7 @@ const makeMessagesSocket = (config) => {
|
|
|
713
1128
|
content: jids.map(jid => ({
|
|
714
1129
|
tag: 'token',
|
|
715
1130
|
attrs: {
|
|
716
|
-
jid:
|
|
1131
|
+
jid: jidNormalizedUser(jid),
|
|
717
1132
|
t,
|
|
718
1133
|
type: 'trusted_contact'
|
|
719
1134
|
}
|
|
@@ -722,34 +1137,47 @@ const makeMessagesSocket = (config) => {
|
|
|
722
1137
|
]
|
|
723
1138
|
});
|
|
724
1139
|
return result;
|
|
725
|
-
}
|
|
726
|
-
const waUploadToServer =
|
|
727
|
-
const
|
|
728
|
-
|
|
1140
|
+
};
|
|
1141
|
+
const waUploadToServer = getWAUploadToServer(config, refreshMediaConn);
|
|
1142
|
+
const waitForMsgMediaUpdate = bindWaitForEvent(ev, 'messages.media-update');
|
|
1143
|
+
registerSocketEndHandler(() => {
|
|
1144
|
+
if (!config.userDevicesCache && userDevicesCache.close) {
|
|
1145
|
+
userDevicesCache.close();
|
|
1146
|
+
}
|
|
1147
|
+
mediaConn = undefined;
|
|
1148
|
+
if (messageRetryManager) {
|
|
1149
|
+
messageRetryManager.clear();
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
729
1152
|
return {
|
|
730
1153
|
...sock,
|
|
731
|
-
|
|
1154
|
+
userDevicesCache,
|
|
1155
|
+
devicesMutex,
|
|
1156
|
+
issuePrivacyTokens,
|
|
732
1157
|
assertSessions,
|
|
733
1158
|
relayMessage,
|
|
734
1159
|
sendReceipt,
|
|
735
1160
|
sendReceipts,
|
|
736
|
-
rahmi,
|
|
737
1161
|
readMessages,
|
|
738
1162
|
refreshMediaConn,
|
|
739
|
-
|
|
740
|
-
|
|
1163
|
+
// Function (not getter) so the spread in chats.ts preserves the live closure binding.
|
|
1164
|
+
getMediaHost: () => mediaHost,
|
|
741
1165
|
waUploadToServer,
|
|
742
|
-
sendPeerDataOperationMessage,
|
|
743
1166
|
fetchPrivacySettings,
|
|
1167
|
+
sendPeerDataOperationMessage,
|
|
1168
|
+
createParticipantNodes,
|
|
1169
|
+
getUSyncDevices,
|
|
1170
|
+
messageRetryManager,
|
|
1171
|
+
updateMemberLabel,
|
|
744
1172
|
updateMediaMessage: async (message) => {
|
|
745
|
-
const content =
|
|
1173
|
+
const content = assertMediaContent(message.message);
|
|
746
1174
|
const mediaKey = content.mediaKey;
|
|
747
1175
|
const meId = authState.creds.me.id;
|
|
748
|
-
const node =
|
|
1176
|
+
const node = encryptMediaRetryRequest(message.key, mediaKey, meId);
|
|
749
1177
|
let error = undefined;
|
|
750
1178
|
await Promise.all([
|
|
751
1179
|
sendNode(node),
|
|
752
|
-
waitForMsgMediaUpdate(update => {
|
|
1180
|
+
waitForMsgMediaUpdate(async (update) => {
|
|
753
1181
|
const result = update.find(c => c.key.id === message.key.id);
|
|
754
1182
|
if (result) {
|
|
755
1183
|
if (result.error) {
|
|
@@ -757,13 +1185,16 @@ const makeMessagesSocket = (config) => {
|
|
|
757
1185
|
}
|
|
758
1186
|
else {
|
|
759
1187
|
try {
|
|
760
|
-
const media =
|
|
761
|
-
if (media.result !==
|
|
762
|
-
const resultStr =
|
|
763
|
-
throw new
|
|
1188
|
+
const media = decryptMediaRetryData(result.media, mediaKey, result.key.id);
|
|
1189
|
+
if (media.result !== proto.MediaRetryNotification.ResultType.SUCCESS) {
|
|
1190
|
+
const resultStr = proto.MediaRetryNotification.ResultType[media.result];
|
|
1191
|
+
throw new Boom(`Media re-upload failed by device (${resultStr})`, {
|
|
1192
|
+
data: media,
|
|
1193
|
+
statusCode: getStatusCodeForMediaRetry(media.result) || 404
|
|
1194
|
+
});
|
|
764
1195
|
}
|
|
765
1196
|
content.directPath = media.directPath;
|
|
766
|
-
content.url =
|
|
1197
|
+
content.url = getUrlFromDirectPath(content.directPath, mediaHost);
|
|
767
1198
|
logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful');
|
|
768
1199
|
}
|
|
769
1200
|
catch (err) {
|
|
@@ -777,133 +1208,228 @@ const makeMessagesSocket = (config) => {
|
|
|
777
1208
|
if (error) {
|
|
778
1209
|
throw error;
|
|
779
1210
|
}
|
|
780
|
-
ev.emit('messages.update', [
|
|
781
|
-
{
|
|
782
|
-
key: message.key,
|
|
783
|
-
update: {
|
|
784
|
-
message: message.message
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
]);
|
|
1211
|
+
ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
|
|
788
1212
|
return message;
|
|
789
1213
|
},
|
|
1214
|
+
sendTable: async (jid, title, headers, rows, quoted, options = {}) => {
|
|
1215
|
+
const { message, messageId } = Utils_1.generateTableContent(title, headers, rows, quoted, options)
|
|
1216
|
+
await relayMessage(jid, message, { messageId })
|
|
1217
|
+
return { message, messageId }
|
|
1218
|
+
},
|
|
1219
|
+
sendList: async (jid, title, items, quoted, options = {}) => {
|
|
1220
|
+
const { message, messageId } = Utils_1.generateListContent(title, items, quoted, options)
|
|
1221
|
+
await relayMessage(jid, message, { messageId })
|
|
1222
|
+
return { message, messageId }
|
|
1223
|
+
},
|
|
1224
|
+
sendCodeBlock: async (jid, code, quoted, options = {}) => {
|
|
1225
|
+
const { message, messageId } = Utils_1.generateCodeBlockContent(code, quoted, options)
|
|
1226
|
+
await relayMessage(jid, message, { messageId })
|
|
1227
|
+
return { message, messageId }
|
|
1228
|
+
},
|
|
1229
|
+
sendLatex: async (jid, quoted, options) => {
|
|
1230
|
+
const { message, messageId } = Utils_1.generateLatexContent(quoted, options)
|
|
1231
|
+
await relayMessage(jid, message, { messageId })
|
|
1232
|
+
return { message, messageId }
|
|
1233
|
+
},
|
|
1234
|
+
sendLatexImage: async (jid, quoted, options, renderLatexToPng, uploadFn) => {
|
|
1235
|
+
const { message, messageId } = await Utils_1.generateLatexImageContent(
|
|
1236
|
+
quoted,
|
|
1237
|
+
options,
|
|
1238
|
+
uploadFn,
|
|
1239
|
+
renderLatexToPng
|
|
1240
|
+
)
|
|
1241
|
+
await relayMessage(jid, message, { messageId })
|
|
1242
|
+
return { message, messageId }
|
|
1243
|
+
},
|
|
1244
|
+
sendLatexInlineImage: async (jid, quoted, options, renderLatexToPng, uploadFn) => {
|
|
1245
|
+
const { message, messageId } = await Utils_1.generateLatexInlineImageContent(
|
|
1246
|
+
quoted,
|
|
1247
|
+
options,
|
|
1248
|
+
uploadFn,
|
|
1249
|
+
renderLatexToPng
|
|
1250
|
+
)
|
|
1251
|
+
await relayMessage(jid, message, { messageId })
|
|
1252
|
+
return { message, messageId }
|
|
1253
|
+
},
|
|
1254
|
+
captureUnifiedResponse: Utils_1.captureUnifiedResponse,
|
|
1255
|
+
sendUnifiedResponse: async (jid, quoted, captured) => {
|
|
1256
|
+
const { message, messageId } = Utils_1.generateUnifiedResponseContent(quoted, captured)
|
|
1257
|
+
await relayMessage(jid, message, { messageId })
|
|
1258
|
+
return { message, messageId }
|
|
1259
|
+
},
|
|
1260
|
+
sendRichMessage: async (jid, submessages, quoted, options = {}) => {
|
|
1261
|
+
const { message, messageId } = Utils_1.generateRichMessageContent(submessages, quoted, options)
|
|
1262
|
+
await relayMessage(jid, message, { messageId })
|
|
1263
|
+
return { message, messageId }
|
|
1264
|
+
},
|
|
790
1265
|
sendMessage: async (jid, content, options = {}) => {
|
|
791
1266
|
const userJid = authState.creds.me.id;
|
|
792
|
-
|
|
793
|
-
const {
|
|
794
|
-
const
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
typeof content['disappearingMessagesInChat'] !== 'undefined' &&
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
const value = typeof disappearingMessagesInChat === 'boolean'
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
1267
|
+
const luki = new imup(Utils_1, waUploadToServer, relayMessage)
|
|
1268
|
+
const { quoted, participant = false } = options;
|
|
1269
|
+
const messageType = luki.detectType(content);
|
|
1270
|
+
if (typeof content === 'object' &&
|
|
1271
|
+
'disappearingMessagesInChat' in content &&
|
|
1272
|
+
typeof content['disappearingMessagesInChat'] !== 'undefined' &&
|
|
1273
|
+
isJidGroup(jid)) {
|
|
1274
|
+
const { disappearingMessagesInChat } = content;
|
|
1275
|
+
const value = typeof disappearingMessagesInChat === 'boolean'
|
|
1276
|
+
? disappearingMessagesInChat
|
|
1277
|
+
? WA_DEFAULT_EPHEMERAL
|
|
1278
|
+
: 0
|
|
1279
|
+
: disappearingMessagesInChat;
|
|
1280
|
+
await groupToggleEphemeral(jid, value);
|
|
805
1281
|
}
|
|
806
|
-
|
|
807
1282
|
else {
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
case 'ALBUM':
|
|
836
|
-
return await rahmi.handleAlbum(content, jid, quoted)
|
|
837
|
-
case 'EVENT':
|
|
838
|
-
return await rahmi.handleEvent(content, jid, quoted)
|
|
839
|
-
case 'POLL_RESULT':
|
|
840
|
-
return await rahmi.handlePollResult(content, jid, quoted)
|
|
841
|
-
case 'GROUP_STORY':
|
|
842
|
-
return await rahmi.handleGroupStory(content, jid, quoted)
|
|
1283
|
+
if (messageType) {
|
|
1284
|
+
switch(messageType) {
|
|
1285
|
+
case 'PAYMENT':
|
|
1286
|
+
const paymentContent = await luki.handlePayment(content, quoted);
|
|
1287
|
+
return await relayMessage(jid, paymentContent, {
|
|
1288
|
+
messageId: Utils_1.generateMessageID()
|
|
1289
|
+
});
|
|
1290
|
+
case 'PRODUCT':
|
|
1291
|
+
const productContent = await luki.handleProduct(content, jid, quoted);
|
|
1292
|
+
const productMsg = await Utils_1.generateWAMessageFromContent(jid, productContent, { quoted });
|
|
1293
|
+
return await relayMessage(jid, productMsg.message, {
|
|
1294
|
+
messageId: productMsg.key.id,
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
case 'ALBUM':
|
|
1298
|
+
return await luki.handleAlbum(content, jid, quoted)
|
|
1299
|
+
case 'EVENT':
|
|
1300
|
+
return await luki.handleEvent(content, jid, quoted)
|
|
1301
|
+
case 'POLL_RESULT':
|
|
1302
|
+
return await luki.handlePollResult(content, jid, quoted)
|
|
1303
|
+
case 'ORDER':
|
|
1304
|
+
return await luki.handleOrderMessage(content, jid, quoted)
|
|
1305
|
+
case 'GROUP_STATUS':
|
|
1306
|
+
return await luki.handleGroupStory(content, jid, quoted)
|
|
1307
|
+
case 'GROUP_LABEL':
|
|
1308
|
+
return await luki.handleGbLabel(content, jid)
|
|
1309
|
+
}
|
|
843
1310
|
}
|
|
844
|
-
|
|
845
|
-
const fullMsg = await Utils_1.generateWAMessage(jid, content, {
|
|
846
|
-
logger,
|
|
847
|
-
userJid,
|
|
848
|
-
quoted,
|
|
849
|
-
getUrlInfo: text => link_preview_1.getUrlInfo(text, {
|
|
850
|
-
thumbnailWidth: linkPreviewImageThumbnailWidth,
|
|
851
|
-
fetchOpts: {
|
|
852
|
-
timeout: 3000,
|
|
853
|
-
...axiosOptions || {}
|
|
854
|
-
},
|
|
1311
|
+
const fullMsg = await generateWAMessage(jid, content, {
|
|
855
1312
|
logger,
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1313
|
+
userJid,
|
|
1314
|
+
getUrlInfo: text => getUrlInfo(text, {
|
|
1315
|
+
thumbnailWidth: linkPreviewImageThumbnailWidth,
|
|
1316
|
+
fetchOpts: {
|
|
1317
|
+
timeout: 3000,
|
|
1318
|
+
...(httpRequestOptions || {})
|
|
1319
|
+
},
|
|
1320
|
+
logger,
|
|
1321
|
+
uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
|
|
1322
|
+
}),
|
|
1323
|
+
//TODO: CACHE
|
|
1324
|
+
getProfilePicUrl: sock.profilePictureUrl,
|
|
1325
|
+
getCallLink: sock.createCallLink,
|
|
1326
|
+
upload: waUploadToServer,
|
|
1327
|
+
mediaCache: config.mediaCache,
|
|
1328
|
+
options: config.options,
|
|
1329
|
+
messageId: generateMessageIDV2(sock.user?.id),
|
|
1330
|
+
...options
|
|
1331
|
+
});
|
|
1332
|
+
const isEventMsg = 'event' in content && !!content.event;
|
|
1333
|
+
const isDeleteMsg = 'delete' in content && !!content.delete;
|
|
1334
|
+
const isEditMsg = 'edit' in content && !!content.edit;
|
|
1335
|
+
const isPinMsg = 'pin' in content && !!content.pin;
|
|
1336
|
+
const isPollMessage = 'poll' in content && !!content.poll;
|
|
1337
|
+
const additionalAttributes = {};
|
|
1338
|
+
const additionalNodes = [];
|
|
1339
|
+
// required for delete
|
|
1340
|
+
if (isDeleteMsg) {
|
|
1341
|
+
// if the chat is a group, and I am not the author, then delete the message as an admin
|
|
1342
|
+
if (isJidGroup(content.delete?.remoteJid) && !content.delete?.fromMe) {
|
|
1343
|
+
additionalAttributes.edit = '8';
|
|
1344
|
+
}
|
|
1345
|
+
else {
|
|
1346
|
+
additionalAttributes.edit = '7';
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
else if (isEditMsg) {
|
|
1350
|
+
additionalAttributes.edit = '1';
|
|
1351
|
+
}
|
|
1352
|
+
else if (isPinMsg) {
|
|
1353
|
+
additionalAttributes.edit = '2';
|
|
1354
|
+
}
|
|
1355
|
+
else if (isPollMessage) {
|
|
1356
|
+
additionalNodes.push({
|
|
1357
|
+
tag: 'meta',
|
|
1358
|
+
attrs: {
|
|
1359
|
+
polltype: 'creation'
|
|
1360
|
+
}
|
|
862
1361
|
});
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
const isGroup = WABinary_1.isJidGroup(content.delete?.remoteJid);
|
|
880
|
-
additionalAttributes.edit = (isGroup && !fromMe) || WABinary_1.isJidNewsLetter(jid) ? '8' : '7';
|
|
881
|
-
} else if (isEditMsg) {
|
|
882
|
-
additionalAttributes.edit = WABinary_1.isJidNewsLetter(jid) ? '3' : '1';
|
|
883
|
-
} else if (isAiMsg) {
|
|
884
|
-
additionalNodes.push({
|
|
885
|
-
attrs: {
|
|
886
|
-
biz_bot: '1'
|
|
887
|
-
}, tag: "bot"
|
|
1362
|
+
}
|
|
1363
|
+
else if (isEventMsg) {
|
|
1364
|
+
additionalNodes.push({
|
|
1365
|
+
tag: 'meta',
|
|
1366
|
+
attrs: {
|
|
1367
|
+
event_type: 'creation'
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
await relayMessage(jid, fullMsg.message, {
|
|
1372
|
+
messageId: fullMsg.key.id,
|
|
1373
|
+
useCachedGroupMetadata: options.useCachedGroupMetadata,
|
|
1374
|
+
additionalAttributes,
|
|
1375
|
+
statusJidList: options.statusJidList,
|
|
1376
|
+
additionalNodes: aiLabel ? additionalNodes : options.additionalNodes,
|
|
1377
|
+
participant
|
|
888
1378
|
});
|
|
1379
|
+
if (config.emitOwnEvents) {
|
|
1380
|
+
process.nextTick(async () => {
|
|
1381
|
+
await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
return fullMsg;
|
|
889
1385
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1386
|
+
},
|
|
1387
|
+
sendMessageMembers: async (jid, message, options = {}) => {
|
|
1388
|
+
const {
|
|
1389
|
+
messageId: idm,
|
|
1390
|
+
quoted,
|
|
1391
|
+
delayMs = 1500,
|
|
1392
|
+
useUserDevicesCache = true,
|
|
1393
|
+
cachedGroupMetadata,
|
|
1394
|
+
onlyMember = true
|
|
1395
|
+
} = options;
|
|
1396
|
+
const { server } = jidDecode(jid);
|
|
1397
|
+
if (server !== "g.us") throw new Error("@g.us server required");
|
|
1398
|
+
const meId = authState.creds.me.id;
|
|
1399
|
+
const messages = Utils_1.normalizeMessageContent(message);
|
|
1400
|
+
const groupData = cachedGroupMetadata? await cachedGroupMetadata(jid) : await groupMetadata(jid);
|
|
1401
|
+
const isLid = groupData.addressingMode === "lid";
|
|
1402
|
+
const isAdmin = groupData.participants.filter((x) => x.admin !== null).map((y) => y.id)
|
|
1403
|
+
let participantJids = groupData.participants.map(z => z.id);
|
|
1404
|
+
if (onlyMember) {
|
|
1405
|
+
participantJids = isAdmin ? isAdmin : participantJids;
|
|
903
1406
|
}
|
|
904
|
-
|
|
1407
|
+
logger.info(`Sending message to ${participantJids.length} members from ${jid}`);
|
|
1408
|
+
for (let i = 0; i < participantJids.length; i++) {
|
|
1409
|
+
const jid = participantJids[i];
|
|
1410
|
+
if (areJidsSameUser(jid, meId)) continue;
|
|
1411
|
+
try {
|
|
1412
|
+
const msgId = `${idm || Utils_1.generateMessageID()}_${i}`;
|
|
1413
|
+
const fullMsg = await Utils_1.generateWAMessageFromContent(jid, message, {
|
|
1414
|
+
messageId: msgId,
|
|
1415
|
+
quoted
|
|
1416
|
+
})
|
|
1417
|
+
await relayMessage(jid, fullMsg.message, {
|
|
1418
|
+
messageId: fullMsg.key.id
|
|
1419
|
+
});
|
|
1420
|
+
logger.debug(`Message successfully sent to ${jid}`);
|
|
1421
|
+
if (delayMs && i < participantJids.length - 1) {
|
|
1422
|
+
await new Promise(z => setTimeout(z, delayMs));
|
|
1423
|
+
}
|
|
1424
|
+
} catch (e) {
|
|
1425
|
+
logger.error({ jid, e }, "Error sending message to");
|
|
1426
|
+
}
|
|
905
1427
|
}
|
|
1428
|
+
return JSON.stringify({
|
|
1429
|
+
members_total: participantJids.length,
|
|
1430
|
+
message
|
|
1431
|
+
}, null, 4);
|
|
906
1432
|
}
|
|
907
|
-
}
|
|
1433
|
+
};
|
|
908
1434
|
};
|
|
909
|
-
|
|
1435
|
+
//# sourceMappingURL=messages-send.js.map
|