violetics 7.0.1-alpha → 7.0.2-alpha
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/LICENSE +3 -2
- package/README.md +1001 -232
- package/WAProto/index.js +75379 -142631
- package/engine-requirements.js +11 -8
- package/lib/Defaults/index.js +132 -146
- package/lib/Signal/Group/ciphertext-message.js +2 -6
- package/lib/Signal/Group/group-session-builder.js +7 -42
- package/lib/Signal/Group/group_cipher.js +37 -52
- package/lib/Signal/Group/index.js +11 -57
- package/lib/Signal/Group/keyhelper.js +7 -45
- package/lib/Signal/Group/sender-chain-key.js +7 -16
- package/lib/Signal/Group/sender-key-distribution-message.js +8 -12
- package/lib/Signal/Group/sender-key-message.js +9 -13
- package/lib/Signal/Group/sender-key-name.js +2 -6
- package/lib/Signal/Group/sender-key-record.js +9 -22
- package/lib/Signal/Group/sender-key-state.js +27 -43
- package/lib/Signal/Group/sender-message-key.js +4 -8
- package/lib/Signal/libsignal.js +319 -94
- package/lib/Signal/lid-mapping.js +224 -139
- package/lib/Socket/Client/index.js +2 -19
- package/lib/Socket/Client/types.js +10 -0
- package/lib/Socket/Client/websocket.js +53 -0
- package/lib/Socket/business.js +162 -44
- package/lib/Socket/chats.js +477 -418
- package/lib/Socket/communities.js +430 -0
- package/lib/Socket/groups.js +110 -99
- package/lib/Socket/index.js +10 -10
- package/lib/Socket/messages-recv.js +884 -561
- package/lib/Socket/messages-send.js +859 -428
- package/lib/Socket/mex.js +41 -0
- package/lib/Socket/newsletter.js +195 -390
- package/lib/Socket/socket.js +465 -315
- package/lib/Store/index.js +3 -10
- package/lib/Store/make-in-memory-store.js +73 -79
- package/lib/Store/make-ordered-dictionary.js +4 -7
- package/lib/Store/object-repository.js +2 -6
- package/lib/Types/Auth.js +1 -2
- package/lib/Types/Bussines.js +1 -0
- package/lib/Types/Call.js +1 -2
- package/lib/Types/Chat.js +7 -4
- package/lib/Types/Contact.js +1 -2
- package/lib/Types/Events.js +1 -2
- package/lib/Types/GroupMetadata.js +1 -2
- package/lib/Types/Label.js +2 -5
- package/lib/Types/LabelAssociation.js +2 -5
- package/lib/Types/Message.js +17 -9
- package/lib/Types/Newsletter.js +33 -38
- package/lib/Types/Product.js +1 -2
- package/lib/Types/Signal.js +1 -2
- package/lib/Types/Socket.js +2 -2
- package/lib/Types/State.js +12 -2
- package/lib/Types/USync.js +1 -2
- package/lib/Types/index.js +14 -31
- package/lib/Utils/auth-utils.js +228 -152
- package/lib/Utils/browser-utils.js +28 -0
- package/lib/Utils/business.js +66 -70
- package/lib/Utils/chat-utils.js +331 -249
- package/lib/Utils/crypto.js +57 -91
- package/lib/Utils/decode-wa-message.js +168 -84
- package/lib/Utils/event-buffer.js +138 -80
- package/lib/Utils/generics.js +180 -297
- package/lib/Utils/history.js +83 -49
- package/lib/Utils/identity-change-handler.js +48 -0
- package/lib/Utils/index.js +19 -33
- package/lib/Utils/link-preview.js +14 -23
- package/lib/Utils/logger.js +2 -7
- package/lib/Utils/lt-hash.js +2 -46
- package/lib/Utils/make-mutex.js +24 -47
- package/lib/Utils/message-retry-manager.js +224 -0
- package/lib/Utils/messages-media.js +501 -496
- package/lib/Utils/messages.js +1428 -362
- package/lib/Utils/noise-handler.js +145 -100
- package/lib/Utils/pre-key-manager.js +105 -0
- package/lib/Utils/process-message.js +356 -150
- package/lib/Utils/reporting-utils.js +257 -0
- package/lib/Utils/signal.js +78 -73
- package/lib/Utils/sync-action-utils.js +47 -0
- package/lib/Utils/tc-token-utils.js +17 -0
- package/lib/Utils/use-multi-file-auth-state.js +35 -45
- package/lib/Utils/validate-connection.js +91 -107
- package/lib/WABinary/constants.js +1300 -1304
- package/lib/WABinary/decode.js +26 -48
- package/lib/WABinary/encode.js +109 -155
- package/lib/WABinary/generic-utils.js +161 -149
- package/lib/WABinary/index.js +5 -21
- package/lib/WABinary/jid-utils.js +73 -40
- package/lib/WABinary/types.js +1 -2
- package/lib/WAM/BinaryInfo.js +2 -6
- package/lib/WAM/constants.js +19070 -11568
- package/lib/WAM/encode.js +17 -23
- package/lib/WAM/index.js +3 -19
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +8 -12
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +11 -15
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +9 -13
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +9 -14
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +20 -23
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +13 -9
- package/lib/WAUSync/Protocols/index.js +4 -20
- package/lib/WAUSync/USyncQuery.js +40 -36
- package/lib/WAUSync/USyncUser.js +2 -6
- package/lib/WAUSync/index.js +3 -19
- package/lib/index.js +11 -44
- package/package.json +74 -107
- package/lib/Defaults/baileys-version.json +0 -3
- package/lib/Defaults/phonenumber-mcc.json +0 -223
- package/lib/Signal/Group/queue-job.js +0 -57
- package/lib/Socket/Client/abstract-socket-client.js +0 -13
- package/lib/Socket/Client/mobile-socket-client.js +0 -65
- package/lib/Socket/Client/web-socket-client.js +0 -118
- package/lib/Socket/groupStatus.js +0 -637
- package/lib/Socket/registration.js +0 -166
- package/lib/Socket/usync.js +0 -70
- package/lib/Store/make-cache-manager-store.js +0 -83
- package/lib/Utils/baileys-event-stream.js +0 -63
|
@@ -1,65 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} = config;
|
|
26
|
-
const sock = (0, newsletter_1.makeNewsletterSocket)(config);
|
|
27
|
-
const {
|
|
28
|
-
ev,
|
|
29
|
-
authState,
|
|
30
|
-
processingMutex,
|
|
31
|
-
signalRepository,
|
|
32
|
-
upsertMessage,
|
|
33
|
-
query,
|
|
34
|
-
fetchPrivacySettings,
|
|
35
|
-
sendNode,
|
|
36
|
-
groupMetadata,
|
|
37
|
-
groupToggleEphemeral,
|
|
38
|
-
executeUSyncQuery
|
|
39
|
-
} = sock;
|
|
40
|
-
const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
|
|
41
|
-
stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.USER_DEVICES,
|
|
1
|
+
import NodeCache from '@cacheable/node-cache';
|
|
2
|
+
import { Boom } from '@hapi/boom';
|
|
3
|
+
import { randomBytes } from 'crypto';
|
|
4
|
+
import { proto } from '../../WAProto/index.js';
|
|
5
|
+
import { BIZ_BOT_SUPPORT_PAYLOAD, DEFAULT_CACHE_TTLS, OLD_GROUP_ID_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
|
|
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';
|
|
7
|
+
import { AssociationType } from '../Types/index.js';
|
|
8
|
+
import { getUrlInfo } from '../Utils/link-preview.js';
|
|
9
|
+
import { makeKeyedMutex } from '../Utils/make-mutex.js';
|
|
10
|
+
import { getMessageReportingToken, shouldIncludeReportingToken } from '../Utils/reporting-utils.js';
|
|
11
|
+
import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, getBizBinaryNode, isHostedLidUser, isHostedPnUser, isJidGroup, isJidNewsletter, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
12
|
+
import { USyncQuery, USyncUser } from '../WAUSync/index.js';
|
|
13
|
+
import { makeNewsletterSocket } from './newsletter.js';
|
|
14
|
+
export const makeMessagesSocket = (config) => {
|
|
15
|
+
const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount } = config;
|
|
16
|
+
const sock = makeNewsletterSocket(config);
|
|
17
|
+
const { ev, authState, messageMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral } = sock;
|
|
18
|
+
const userDevicesCache = config.userDevicesCache ||
|
|
19
|
+
new NodeCache({
|
|
20
|
+
stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
|
|
21
|
+
useClones: false
|
|
22
|
+
});
|
|
23
|
+
const peerSessionsCache = new NodeCache({
|
|
24
|
+
stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES,
|
|
42
25
|
useClones: false
|
|
43
26
|
});
|
|
27
|
+
// Initialize message retry manager if enabled
|
|
28
|
+
const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null;
|
|
29
|
+
// Prevent race conditions in Signal session encryption by user
|
|
30
|
+
const encryptionMutex = makeKeyedMutex();
|
|
44
31
|
let mediaConn;
|
|
45
32
|
const refreshMediaConn = async (forceGet = false) => {
|
|
46
33
|
const media = await mediaConn;
|
|
47
|
-
if (!media || forceGet ||
|
|
34
|
+
if (!media || forceGet || new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1000) {
|
|
48
35
|
mediaConn = (async () => {
|
|
49
36
|
const result = await query({
|
|
50
37
|
tag: 'iq',
|
|
51
38
|
attrs: {
|
|
52
39
|
type: 'set',
|
|
53
40
|
xmlns: 'w:m',
|
|
54
|
-
to:
|
|
41
|
+
to: S_WHATSAPP_NET
|
|
55
42
|
},
|
|
56
43
|
content: [{ tag: 'media_conn', attrs: {} }]
|
|
57
44
|
});
|
|
58
|
-
const mediaConnNode =
|
|
45
|
+
const mediaConnNode = getBinaryNodeChild(result, 'media_conn');
|
|
46
|
+
// TODO: explore full length of data that whatsapp provides
|
|
59
47
|
const node = {
|
|
60
|
-
hosts:
|
|
48
|
+
hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({
|
|
61
49
|
hostname: attrs.hostname,
|
|
62
|
-
maxContentLengthBytes: +attrs.maxContentLengthBytes
|
|
50
|
+
maxContentLengthBytes: +attrs.maxContentLengthBytes
|
|
63
51
|
})),
|
|
64
52
|
auth: mediaConnNode.attrs.auth,
|
|
65
53
|
ttl: +mediaConnNode.attrs.ttl,
|
|
@@ -71,18 +59,25 @@ const makeMessagesSocket = (config) => {
|
|
|
71
59
|
}
|
|
72
60
|
return mediaConn;
|
|
73
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* generic send receipt function
|
|
64
|
+
* used for receipts of phone call, read, delivery etc.
|
|
65
|
+
* */
|
|
74
66
|
const sendReceipt = async (jid, participant, messageIds, type) => {
|
|
67
|
+
if (!messageIds || messageIds.length === 0) {
|
|
68
|
+
throw new Boom('missing ids in receipt');
|
|
69
|
+
}
|
|
75
70
|
const node = {
|
|
76
71
|
tag: 'receipt',
|
|
77
72
|
attrs: {
|
|
78
|
-
id: messageIds[0]
|
|
79
|
-
}
|
|
73
|
+
id: messageIds[0]
|
|
74
|
+
}
|
|
80
75
|
};
|
|
81
76
|
const isReadReceipt = type === 'read' || type === 'read-self';
|
|
82
77
|
if (isReadReceipt) {
|
|
83
|
-
node.attrs.t =
|
|
78
|
+
node.attrs.t = unixTimestampSeconds().toString();
|
|
84
79
|
}
|
|
85
|
-
if (type === 'sender' &&
|
|
80
|
+
if (type === 'sender' && (isPnUser(jid) || isLidUser(jid))) {
|
|
86
81
|
node.attrs.recipient = jid;
|
|
87
82
|
node.attrs.to = participant;
|
|
88
83
|
}
|
|
@@ -93,7 +88,7 @@ const makeMessagesSocket = (config) => {
|
|
|
93
88
|
}
|
|
94
89
|
}
|
|
95
90
|
if (type) {
|
|
96
|
-
node.attrs.type =
|
|
91
|
+
node.attrs.type = type;
|
|
97
92
|
}
|
|
98
93
|
const remainingMessageIds = messageIds.slice(1);
|
|
99
94
|
if (remainingMessageIds.length) {
|
|
@@ -111,252 +106,492 @@ const makeMessagesSocket = (config) => {
|
|
|
111
106
|
logger.debug({ attrs: node.attrs, messageIds }, 'sending receipt for messages');
|
|
112
107
|
await sendNode(node);
|
|
113
108
|
};
|
|
109
|
+
/** Correctly bulk send receipts to multiple chats, participants */
|
|
114
110
|
const sendReceipts = async (keys, type) => {
|
|
115
|
-
const recps =
|
|
111
|
+
const recps = aggregateMessageKeysNotFromMe(keys);
|
|
116
112
|
for (const { jid, participant, messageIds } of recps) {
|
|
117
113
|
await sendReceipt(jid, participant, messageIds, type);
|
|
118
114
|
}
|
|
119
115
|
};
|
|
116
|
+
/** Bulk read messages. Keys can be from different chats & participants */
|
|
120
117
|
const readMessages = async (keys) => {
|
|
121
118
|
const privacySettings = await fetchPrivacySettings();
|
|
119
|
+
// based on privacy settings, we have to change the read type
|
|
122
120
|
const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self';
|
|
123
121
|
await sendReceipts(keys, readType);
|
|
124
122
|
};
|
|
123
|
+
/** Fetch all the devices we've to send a message to */
|
|
125
124
|
const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
|
|
126
|
-
const deviceResults = []
|
|
125
|
+
const deviceResults = [];
|
|
127
126
|
if (!useCache) {
|
|
128
|
-
logger.debug('not using cache for devices')
|
|
127
|
+
logger.debug('not using cache for devices');
|
|
129
128
|
}
|
|
130
|
-
const toFetch = []
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
const toFetch = [];
|
|
130
|
+
const jidsWithUser = jids
|
|
131
|
+
.map(jid => {
|
|
132
|
+
const decoded = jidDecode(jid);
|
|
133
|
+
const user = decoded?.user;
|
|
134
|
+
const device = decoded?.device;
|
|
135
|
+
const isExplicitDevice = typeof device === 'number' && device >= 0;
|
|
136
|
+
if (isExplicitDevice && user) {
|
|
137
|
+
deviceResults.push({
|
|
138
|
+
user,
|
|
139
|
+
device,
|
|
140
|
+
jid
|
|
141
|
+
});
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
jid = jidNormalizedUser(jid);
|
|
145
|
+
return { jid, user };
|
|
146
|
+
})
|
|
147
|
+
.filter(jid => jid !== null);
|
|
148
|
+
let mgetDevices;
|
|
149
|
+
if (useCache && userDevicesCache.mget) {
|
|
150
|
+
const usersToFetch = jidsWithUser.map(j => j?.user).filter(Boolean);
|
|
151
|
+
mgetDevices = await userDevicesCache.mget(usersToFetch);
|
|
152
|
+
}
|
|
153
|
+
for (const { jid, user } of jidsWithUser) {
|
|
135
154
|
if (useCache) {
|
|
136
|
-
const devices =
|
|
155
|
+
const devices = mgetDevices?.[user] ||
|
|
156
|
+
(userDevicesCache.mget ? undefined : (await userDevicesCache.get(user)));
|
|
137
157
|
if (devices) {
|
|
138
|
-
|
|
139
|
-
|
|
158
|
+
const devicesWithJid = devices.map(d => ({
|
|
159
|
+
...d,
|
|
160
|
+
jid: jidEncode(d.user, d.server, d.device)
|
|
161
|
+
}));
|
|
162
|
+
deviceResults.push(...devicesWithJid);
|
|
163
|
+
logger.trace({ user }, 'using cache for devices');
|
|
140
164
|
}
|
|
141
165
|
else {
|
|
142
|
-
toFetch.push(jid)
|
|
166
|
+
toFetch.push(jid);
|
|
143
167
|
}
|
|
144
168
|
}
|
|
145
169
|
else {
|
|
146
|
-
toFetch.push(jid)
|
|
170
|
+
toFetch.push(jid);
|
|
147
171
|
}
|
|
148
172
|
}
|
|
149
173
|
if (!toFetch.length) {
|
|
150
|
-
return deviceResults
|
|
174
|
+
return deviceResults;
|
|
175
|
+
}
|
|
176
|
+
const requestedLidUsers = new Set();
|
|
177
|
+
for (const jid of toFetch) {
|
|
178
|
+
if (isLidUser(jid) || isHostedLidUser(jid)) {
|
|
179
|
+
const user = jidDecode(jid)?.user;
|
|
180
|
+
if (user)
|
|
181
|
+
requestedLidUsers.add(user);
|
|
182
|
+
}
|
|
151
183
|
}
|
|
152
|
-
const query = new
|
|
153
|
-
.withContext('message')
|
|
154
|
-
.withDeviceProtocol()
|
|
184
|
+
const query = new USyncQuery().withContext('message').withDeviceProtocol().withLIDProtocol();
|
|
155
185
|
for (const jid of toFetch) {
|
|
156
|
-
query.withUser(new
|
|
186
|
+
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
|
|
157
187
|
}
|
|
158
|
-
const result = await executeUSyncQuery(query)
|
|
188
|
+
const result = await sock.executeUSyncQuery(query);
|
|
159
189
|
if (result) {
|
|
160
|
-
|
|
161
|
-
const
|
|
190
|
+
// TODO: LID MAP this stuff (lid protocol will now return lid with devices)
|
|
191
|
+
const lidResults = result.list.filter(a => !!a.lid);
|
|
192
|
+
if (lidResults.length > 0) {
|
|
193
|
+
logger.trace('Storing LID maps from device call');
|
|
194
|
+
await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid, pn: a.id })));
|
|
195
|
+
// Force-refresh sessions for newly mapped LIDs to align identity addressing
|
|
196
|
+
try {
|
|
197
|
+
const lids = lidResults.map(a => a.lid);
|
|
198
|
+
if (lids.length) {
|
|
199
|
+
await assertSessions(lids, true);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
logger.warn({ e, count: lidResults.length }, 'failed to assert sessions for newly mapped LIDs');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const extracted = extractDeviceJids(result?.list, authState.creds.me.id, authState.creds.me.lid, ignoreZeroDevices);
|
|
207
|
+
const deviceMap = {};
|
|
162
208
|
for (const item of extracted) {
|
|
163
|
-
deviceMap[item.user] = deviceMap[item.user] || []
|
|
164
|
-
deviceMap[item.user]
|
|
165
|
-
|
|
209
|
+
deviceMap[item.user] = deviceMap[item.user] || [];
|
|
210
|
+
deviceMap[item.user]?.push(item);
|
|
211
|
+
}
|
|
212
|
+
// Process each user's devices as a group for bulk LID migration
|
|
213
|
+
for (const [user, userDevices] of Object.entries(deviceMap)) {
|
|
214
|
+
const isLidUser = requestedLidUsers.has(user);
|
|
215
|
+
// Process all devices for this user
|
|
216
|
+
for (const item of userDevices) {
|
|
217
|
+
const finalJid = isLidUser
|
|
218
|
+
? jidEncode(user, item.server, item.device)
|
|
219
|
+
: jidEncode(item.user, item.server, item.device);
|
|
220
|
+
deviceResults.push({
|
|
221
|
+
...item,
|
|
222
|
+
jid: finalJid
|
|
223
|
+
});
|
|
224
|
+
logger.debug({
|
|
225
|
+
user: item.user,
|
|
226
|
+
device: item.device,
|
|
227
|
+
finalJid,
|
|
228
|
+
usedLid: isLidUser
|
|
229
|
+
}, 'Processed device with LID priority');
|
|
230
|
+
}
|
|
166
231
|
}
|
|
167
|
-
|
|
168
|
-
|
|
232
|
+
if (userDevicesCache.mset) {
|
|
233
|
+
// if the cache supports mset, we can set all devices in one go
|
|
234
|
+
await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })));
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
for (const key in deviceMap) {
|
|
238
|
+
if (deviceMap[key])
|
|
239
|
+
await userDevicesCache.set(key, deviceMap[key]);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const userDeviceUpdates = {};
|
|
243
|
+
for (const [userId, devices] of Object.entries(deviceMap)) {
|
|
244
|
+
if (devices && devices.length > 0) {
|
|
245
|
+
userDeviceUpdates[userId] = devices.map(d => d.device?.toString());
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (Object.keys(userDeviceUpdates).length > 0) {
|
|
249
|
+
try {
|
|
250
|
+
await authState.keys.set({ 'device-list': userDeviceUpdates });
|
|
251
|
+
logger.debug({ userCount: Object.keys(userDeviceUpdates).length }, 'stored user device lists for bulk migration');
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
logger.warn({ error }, 'failed to store user device lists');
|
|
255
|
+
}
|
|
169
256
|
}
|
|
170
257
|
}
|
|
171
|
-
return deviceResults
|
|
172
|
-
}
|
|
258
|
+
return deviceResults;
|
|
259
|
+
};
|
|
260
|
+
/**
|
|
261
|
+
* Update Member Label
|
|
262
|
+
*/
|
|
263
|
+
const updateMemberLabel = (jid, memberLabel) => {
|
|
264
|
+
return relayMessage(jid, {
|
|
265
|
+
protocolMessage: {
|
|
266
|
+
type: proto.Message.ProtocolMessage.Type.GROUP_MEMBER_LABEL_CHANGE,
|
|
267
|
+
memberLabel: {
|
|
268
|
+
label: memberLabel?.slice(0, 30),
|
|
269
|
+
labelTimestamp: unixTimestampSeconds()
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}, {
|
|
273
|
+
additionalNodes: [
|
|
274
|
+
{
|
|
275
|
+
tag: 'meta',
|
|
276
|
+
attrs: {
|
|
277
|
+
tag_reason: 'user_update',
|
|
278
|
+
appdata: 'member_tag'
|
|
279
|
+
},
|
|
280
|
+
content: undefined
|
|
281
|
+
}
|
|
282
|
+
]
|
|
283
|
+
});
|
|
284
|
+
};
|
|
173
285
|
const assertSessions = async (jids, force) => {
|
|
174
286
|
let didFetchNewSession = false;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
.jidToSignalProtocolAddress(jid);
|
|
186
|
-
if (!sessions[signalId]) {
|
|
187
|
-
jidsRequiringFetch.push(jid);
|
|
287
|
+
const uniqueJids = [...new Set(jids)]; // Deduplicate JIDs
|
|
288
|
+
const jidsRequiringFetch = [];
|
|
289
|
+
logger.debug({ jids }, 'assertSessions call with jids');
|
|
290
|
+
// Check peerSessionsCache and validate sessions using libsignal loadSession
|
|
291
|
+
for (const jid of uniqueJids) {
|
|
292
|
+
const signalId = signalRepository.jidToSignalProtocolAddress(jid);
|
|
293
|
+
const cachedSession = peerSessionsCache.get(signalId);
|
|
294
|
+
if (cachedSession !== undefined) {
|
|
295
|
+
if (cachedSession && !force) {
|
|
296
|
+
continue; // Session exists in cache
|
|
188
297
|
}
|
|
189
298
|
}
|
|
299
|
+
else {
|
|
300
|
+
const sessionValidation = await signalRepository.validateSession(jid);
|
|
301
|
+
const hasSession = sessionValidation.exists;
|
|
302
|
+
peerSessionsCache.set(signalId, hasSession);
|
|
303
|
+
if (hasSession && !force) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
jidsRequiringFetch.push(jid);
|
|
190
308
|
}
|
|
191
309
|
if (jidsRequiringFetch.length) {
|
|
192
|
-
|
|
310
|
+
// LID if mapped, otherwise original
|
|
311
|
+
const wireJids = [
|
|
312
|
+
...jidsRequiringFetch.filter(jid => !!isLidUser(jid) || !!isHostedLidUser(jid)),
|
|
313
|
+
...((await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => !!isPnUser(jid) || !!isHostedPnUser(jid)))) || []).map(a => a.lid)
|
|
314
|
+
];
|
|
315
|
+
logger.debug({ jidsRequiringFetch, wireJids }, 'fetching sessions');
|
|
193
316
|
const result = await query({
|
|
194
317
|
tag: 'iq',
|
|
195
318
|
attrs: {
|
|
196
319
|
xmlns: 'encrypt',
|
|
197
320
|
type: 'get',
|
|
198
|
-
to:
|
|
321
|
+
to: S_WHATSAPP_NET
|
|
199
322
|
},
|
|
200
323
|
content: [
|
|
201
324
|
{
|
|
202
325
|
tag: 'key',
|
|
203
326
|
attrs: {},
|
|
204
|
-
content:
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
327
|
+
content: wireJids.map(jid => {
|
|
328
|
+
const attrs = { jid };
|
|
329
|
+
if (force)
|
|
330
|
+
attrs.reason = 'identity';
|
|
331
|
+
return { tag: 'user', attrs };
|
|
332
|
+
})
|
|
208
333
|
}
|
|
209
334
|
]
|
|
210
335
|
});
|
|
211
|
-
await
|
|
336
|
+
await parseAndInjectE2ESessions(result, signalRepository);
|
|
212
337
|
didFetchNewSession = true;
|
|
338
|
+
// Cache fetched sessions using wire JIDs
|
|
339
|
+
for (const wireJid of wireJids) {
|
|
340
|
+
const signalId = signalRepository.jidToSignalProtocolAddress(wireJid);
|
|
341
|
+
peerSessionsCache.set(signalId, true);
|
|
342
|
+
}
|
|
213
343
|
}
|
|
214
344
|
return didFetchNewSession;
|
|
215
345
|
};
|
|
216
346
|
const sendPeerDataOperationMessage = async (pdoMessage) => {
|
|
347
|
+
//TODO: for later, abstract the logic to send a Peer Message instead of just PDO - useful for App State Key Resync with phone
|
|
217
348
|
if (!authState.creds.me?.id) {
|
|
218
|
-
throw new
|
|
349
|
+
throw new Boom('Not authenticated');
|
|
219
350
|
}
|
|
220
351
|
const protocolMessage = {
|
|
221
352
|
protocolMessage: {
|
|
222
353
|
peerDataOperationRequestMessage: pdoMessage,
|
|
223
|
-
type:
|
|
354
|
+
type: proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
|
|
224
355
|
}
|
|
225
356
|
};
|
|
226
|
-
const meJid =
|
|
357
|
+
const meJid = jidNormalizedUser(authState.creds.me.id);
|
|
227
358
|
const msgId = await relayMessage(meJid, protocolMessage, {
|
|
228
359
|
additionalAttributes: {
|
|
229
360
|
category: 'peer',
|
|
230
|
-
push_priority: 'high_force'
|
|
361
|
+
push_priority: 'high_force'
|
|
231
362
|
},
|
|
363
|
+
additionalNodes: [
|
|
364
|
+
{
|
|
365
|
+
tag: 'meta',
|
|
366
|
+
attrs: { appdata: 'default' }
|
|
367
|
+
}
|
|
368
|
+
]
|
|
232
369
|
});
|
|
233
370
|
return msgId;
|
|
234
371
|
};
|
|
235
|
-
const createParticipantNodes = async (
|
|
236
|
-
|
|
237
|
-
|
|
372
|
+
const createParticipantNodes = async (recipientJids, message, extraAttrs, dsmMessage) => {
|
|
373
|
+
if (!recipientJids.length) {
|
|
374
|
+
return { nodes: [], shouldIncludeDeviceIdentity: false };
|
|
375
|
+
}
|
|
376
|
+
const patched = await patchMessageBeforeSending(message, recipientJids);
|
|
377
|
+
const patchedMessages = Array.isArray(patched)
|
|
378
|
+
? patched
|
|
379
|
+
: recipientJids.map(jid => ({ recipientJid: jid, message: patched }));
|
|
238
380
|
let shouldIncludeDeviceIdentity = false;
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
381
|
+
const meId = authState.creds.me.id;
|
|
382
|
+
const meLid = authState.creds.me?.lid;
|
|
383
|
+
const meLidUser = meLid ? jidDecode(meLid)?.user : null;
|
|
384
|
+
const encryptionPromises = patchedMessages.map(async ({ recipientJid: jid, message: patchedMessage }) => {
|
|
385
|
+
try {
|
|
386
|
+
if (!jid)
|
|
387
|
+
return null;
|
|
388
|
+
let msgToEncrypt = patchedMessage;
|
|
389
|
+
if (dsmMessage) {
|
|
390
|
+
const { user: targetUser } = jidDecode(jid);
|
|
391
|
+
const { user: ownPnUser } = jidDecode(meId);
|
|
392
|
+
const ownLidUser = meLidUser;
|
|
393
|
+
const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser);
|
|
394
|
+
const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
|
|
395
|
+
if (isOwnUser && !isExactSenderDevice) {
|
|
396
|
+
msgToEncrypt = dsmMessage;
|
|
397
|
+
logger.debug({ jid, targetUser }, 'Using DSM for own device');
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const bytes = encodeWAMessage(msgToEncrypt);
|
|
401
|
+
const mutexKey = jid;
|
|
402
|
+
const node = await encryptionMutex.mutex(mutexKey, async () => {
|
|
403
|
+
const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes });
|
|
404
|
+
if (type === 'pkmsg') {
|
|
405
|
+
shouldIncludeDeviceIdentity = true;
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
tag: 'to',
|
|
409
|
+
attrs: { jid },
|
|
410
|
+
content: [
|
|
411
|
+
{
|
|
412
|
+
tag: 'enc',
|
|
413
|
+
attrs: { v: '2', type, ...(extraAttrs || {}) },
|
|
414
|
+
content: ciphertext
|
|
415
|
+
}
|
|
416
|
+
]
|
|
417
|
+
};
|
|
418
|
+
});
|
|
419
|
+
return node;
|
|
420
|
+
}
|
|
421
|
+
catch (err) {
|
|
422
|
+
logger.error({ jid, err }, 'Failed to encrypt for recipient');
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
const nodes = (await Promise.all(encryptionPromises)).filter(node => node !== null);
|
|
427
|
+
if (recipientJids.length > 0 && nodes.length === 0) {
|
|
428
|
+
throw new Boom('All encryptions failed', { statusCode: 500 });
|
|
429
|
+
}
|
|
260
430
|
return { nodes, shouldIncludeDeviceIdentity };
|
|
261
431
|
};
|
|
262
|
-
const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache,
|
|
432
|
+
const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, statusJidList }) => {
|
|
263
433
|
const meId = authState.creds.me.id;
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
434
|
+
const meLid = authState.creds.me?.lid;
|
|
435
|
+
const isRetryResend = Boolean(participant?.jid);
|
|
436
|
+
let shouldIncludeDeviceIdentity = isRetryResend;
|
|
267
437
|
const statusJid = 'status@broadcast';
|
|
438
|
+
const { user, server } = jidDecode(jid);
|
|
268
439
|
const isGroup = server === 'g.us';
|
|
269
440
|
const isStatus = jid === statusJid;
|
|
270
441
|
const isLid = server === 'lid';
|
|
271
|
-
const isPrivate = server === 's.whatsapp.net'
|
|
272
442
|
const isNewsletter = server === 'newsletter';
|
|
273
|
-
|
|
443
|
+
const isGroupOrStatus = isGroup || isStatus;
|
|
444
|
+
const finalJid = jid;
|
|
445
|
+
msgId = msgId || generateMessageIDV2(meId);
|
|
274
446
|
useUserDevicesCache = useUserDevicesCache !== false;
|
|
275
|
-
useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus
|
|
447
|
+
useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus;
|
|
276
448
|
const participants = [];
|
|
277
|
-
const destinationJid =
|
|
449
|
+
const destinationJid = !isStatus ? finalJid : statusJid;
|
|
278
450
|
const binaryNodeContent = [];
|
|
279
451
|
const devices = [];
|
|
452
|
+
let reportingMessage;
|
|
280
453
|
const meMsg = {
|
|
281
454
|
deviceSentMessage: {
|
|
282
455
|
destinationJid,
|
|
283
456
|
message
|
|
284
|
-
}
|
|
457
|
+
},
|
|
458
|
+
messageContextInfo: message.messageContextInfo
|
|
285
459
|
};
|
|
286
|
-
const extraAttrs = {}
|
|
287
|
-
const messages = Utils_1.normalizeMessageContent(message);
|
|
288
|
-
const buttonType = getButtonType(messages);
|
|
460
|
+
const extraAttrs = {};
|
|
289
461
|
if (participant) {
|
|
290
462
|
if (!isGroup && !isStatus) {
|
|
291
|
-
additionalAttributes = { ...additionalAttributes,
|
|
463
|
+
additionalAttributes = { ...additionalAttributes, device_fanout: 'false' };
|
|
292
464
|
}
|
|
293
|
-
const { user, device } =
|
|
294
|
-
devices.push({
|
|
465
|
+
const { user, device } = jidDecode(participant.jid);
|
|
466
|
+
devices.push({
|
|
467
|
+
user,
|
|
468
|
+
device,
|
|
469
|
+
jid: participant.jid
|
|
470
|
+
});
|
|
295
471
|
}
|
|
296
472
|
await authState.keys.transaction(async () => {
|
|
297
|
-
|
|
473
|
+
// vltcs@changes 02-02-26 --- Normalize message first to extract the original message and valid media type
|
|
474
|
+
const innerMessage = normalizeMessageContent(message);
|
|
475
|
+
const mediaType = getMediaType(innerMessage);
|
|
298
476
|
if (mediaType) {
|
|
299
|
-
extraAttrs['mediatype'] = mediaType
|
|
477
|
+
extraAttrs['mediatype'] = mediaType;
|
|
478
|
+
}
|
|
479
|
+
if (isNewsletter) {
|
|
480
|
+
if (innerMessage.productMessage) {
|
|
481
|
+
extraAttrs['mediatype'] = 'image'; // Lia@Note 02-02-26 --- Treat product message as image message to avoid the "479" error (✷‿✷)
|
|
482
|
+
}
|
|
483
|
+
const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message;
|
|
484
|
+
const bytes = encodeNewsletterMessage(patched);
|
|
485
|
+
// vltcs@changes 08-02-26 --- Add "additionalNodes" for newsletter too (っ˘̩╭╮˘̩)っ
|
|
486
|
+
if (additionalNodes && additionalNodes.length > 0) {
|
|
487
|
+
;
|
|
488
|
+
binaryNodeContent.push(...additionalNodes);
|
|
489
|
+
}
|
|
490
|
+
binaryNodeContent.push({
|
|
491
|
+
tag: 'plaintext',
|
|
492
|
+
attrs: extraAttrs, // vltcs@changes 02-02-26 --- Add extraAttrs to fix media being rejected when sending to newsletter (◠‿◕)
|
|
493
|
+
content: bytes
|
|
494
|
+
});
|
|
495
|
+
const stanza = {
|
|
496
|
+
tag: 'message',
|
|
497
|
+
attrs: {
|
|
498
|
+
to: jid,
|
|
499
|
+
id: msgId,
|
|
500
|
+
type: getMessageType(message),
|
|
501
|
+
...(additionalAttributes || {})
|
|
502
|
+
},
|
|
503
|
+
content: binaryNodeContent
|
|
504
|
+
};
|
|
505
|
+
logger.debug({ msgId }, `sending newsletter message to ${jid}`);
|
|
506
|
+
await sendNode(stanza);
|
|
507
|
+
return;
|
|
300
508
|
}
|
|
301
|
-
|
|
302
|
-
|
|
509
|
+
// vltcs@changes 02-02-26 --- Add keepInChat, editedMessage, mediaNotifyMessage and pollUpdateMessage
|
|
510
|
+
if (innerMessage?.pinInChatMessage || innerMessage?.pollUpdateMessage || innerMessage?.keepInChatMessage || innerMessage?.protocolMessage?.editedMessage || innerMessage?.protocolMessage?.mediaNotifyMessage || innerMessage?.reactionMessage) {
|
|
511
|
+
extraAttrs['decrypt-fail'] = 'hide'; // todo: expand for reactions and other types
|
|
303
512
|
}
|
|
304
|
-
|
|
305
|
-
|
|
513
|
+
// vltcs@changes 02-02-26 --- Add native_flow_name to extraAttrs when sending interactiveResponseMessage
|
|
514
|
+
if (innerMessage?.interactiveResponseMessage?.nativeFlowResponseMessage) {
|
|
515
|
+
extraAttrs['native_flow_name'] = innerMessage.interactiveResponseMessage.nativeFlowResponseMessage.name;
|
|
306
516
|
}
|
|
307
|
-
if (
|
|
517
|
+
if (isGroupOrStatus && !isRetryResend) {
|
|
308
518
|
const [groupData, senderKeyMap] = await Promise.all([
|
|
309
519
|
(async () => {
|
|
310
|
-
let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined
|
|
311
|
-
if (groupData) {
|
|
520
|
+
let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined; // todo: should we rely on the cache specially if the cache is outdated and the metadata has new fields?
|
|
521
|
+
if (groupData && Array.isArray(groupData?.participants)) {
|
|
312
522
|
logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata');
|
|
313
523
|
}
|
|
314
524
|
else if (!isStatus) {
|
|
315
|
-
groupData = await groupMetadata(jid)
|
|
525
|
+
groupData = await groupMetadata(jid); // TODO: start storing group participant list + addr mode in Signal & stop relying on this
|
|
316
526
|
}
|
|
317
527
|
return groupData;
|
|
318
528
|
})(),
|
|
319
529
|
(async () => {
|
|
320
530
|
if (!participant && !isStatus) {
|
|
321
|
-
|
|
322
|
-
|
|
531
|
+
// what if sender memory is less accurate than the cached metadata
|
|
532
|
+
// on participant change in group, we should do sender memory manipulation
|
|
533
|
+
const result = await authState.keys.get('sender-key-memory', [jid]); // TODO: check out what if the sender key memory doesn't include the LID stuff now?
|
|
534
|
+
return result[jid] || {};
|
|
323
535
|
}
|
|
324
|
-
return {}
|
|
536
|
+
return {};
|
|
325
537
|
})()
|
|
326
538
|
]);
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
539
|
+
const participantsList = groupData ? groupData.participants.map(p => p.id) : [];
|
|
540
|
+
if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) {
|
|
541
|
+
additionalAttributes = {
|
|
542
|
+
...additionalAttributes,
|
|
543
|
+
expiration: groupData.ephemeralDuration.toString()
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
if (isStatus && statusJidList) {
|
|
547
|
+
participantsList.push(...statusJidList);
|
|
548
|
+
}
|
|
549
|
+
const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false);
|
|
550
|
+
devices.push(...additionalDevices);
|
|
551
|
+
if (isGroup) {
|
|
552
|
+
additionalAttributes = {
|
|
553
|
+
...additionalAttributes,
|
|
554
|
+
addressing_mode: groupData?.addressingMode || 'lid'
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
const patched = await patchMessageBeforeSending(message);
|
|
558
|
+
if (Array.isArray(patched)) {
|
|
559
|
+
throw new Boom('Per-jid patching is not supported in groups');
|
|
334
560
|
}
|
|
335
|
-
const
|
|
336
|
-
|
|
561
|
+
const bytes = encodeWAMessage(patched);
|
|
562
|
+
reportingMessage = patched;
|
|
563
|
+
const groupAddressingMode = additionalAttributes?.['addressing_mode'] || groupData?.addressingMode || 'lid';
|
|
564
|
+
const groupSenderIdentity = groupAddressingMode === 'lid' && meLid ? meLid : meId;
|
|
337
565
|
const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
|
|
338
566
|
group: destinationJid,
|
|
339
567
|
data: bytes,
|
|
340
|
-
meId
|
|
568
|
+
meId: groupSenderIdentity
|
|
341
569
|
});
|
|
342
|
-
const
|
|
343
|
-
for (const
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
570
|
+
const senderKeyRecipients = [];
|
|
571
|
+
for (const device of devices) {
|
|
572
|
+
const deviceJid = device.jid;
|
|
573
|
+
const hasKey = !!senderKeyMap[deviceJid];
|
|
574
|
+
if ((!hasKey || !!participant) &&
|
|
575
|
+
!isHostedLidUser(deviceJid) &&
|
|
576
|
+
!isHostedPnUser(deviceJid) &&
|
|
577
|
+
device.device !== 99) {
|
|
578
|
+
//todo: revamp all this logic
|
|
579
|
+
// the goal is to follow with what I said above for each group, and instead of a true false map of ids, we can set an array full of those the app has already sent pkmsgs
|
|
580
|
+
senderKeyRecipients.push(deviceJid);
|
|
581
|
+
senderKeyMap[deviceJid] = true;
|
|
348
582
|
}
|
|
349
583
|
}
|
|
350
|
-
if (
|
|
351
|
-
logger.debug({ senderKeyJids }, 'sending new sender key');
|
|
584
|
+
if (senderKeyRecipients.length) {
|
|
585
|
+
logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending new sender key');
|
|
352
586
|
const senderKeyMsg = {
|
|
353
587
|
senderKeyDistributionMessage: {
|
|
354
588
|
axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage,
|
|
355
589
|
groupId: destinationJid
|
|
356
590
|
}
|
|
357
591
|
};
|
|
358
|
-
|
|
359
|
-
|
|
592
|
+
const senderKeySessionTargets = senderKeyRecipients;
|
|
593
|
+
await assertSessions(senderKeySessionTargets);
|
|
594
|
+
const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs);
|
|
360
595
|
shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity;
|
|
361
596
|
participants.push(...result.nodes);
|
|
362
597
|
}
|
|
@@ -367,63 +602,120 @@ const makeMessagesSocket = (config) => {
|
|
|
367
602
|
});
|
|
368
603
|
await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } });
|
|
369
604
|
}
|
|
370
|
-
else
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
605
|
+
else {
|
|
606
|
+
// ADDRESSING CONSISTENCY: Match own identity to conversation context
|
|
607
|
+
// TODO: investigate if this is true
|
|
608
|
+
let ownId = meId;
|
|
609
|
+
if (isLid && meLid) {
|
|
610
|
+
ownId = meLid;
|
|
611
|
+
logger.debug({ to: jid, ownId }, 'Using LID identity for @lid conversation');
|
|
374
612
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
message = {}
|
|
613
|
+
else {
|
|
614
|
+
logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation');
|
|
378
615
|
}
|
|
379
|
-
const
|
|
380
|
-
const bytes = Utils_1.encodeNewsletterMessage(patched)
|
|
381
|
-
binaryNodeContent.push({
|
|
382
|
-
tag: 'plaintext',
|
|
383
|
-
attrs: extraAttrs ? extraAttrs : {},
|
|
384
|
-
content: bytes
|
|
385
|
-
})
|
|
386
|
-
}
|
|
387
|
-
else {
|
|
388
|
-
const { user: meUser } = WABinary_1.jidDecode(meId);
|
|
616
|
+
const { user: ownUser } = jidDecode(ownId);
|
|
389
617
|
if (!participant) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
618
|
+
const patchedForReporting = await patchMessageBeforeSending(message, [jid]);
|
|
619
|
+
reportingMessage = Array.isArray(patchedForReporting)
|
|
620
|
+
? patchedForReporting.find(item => item.recipientJid === jid) || patchedForReporting[0]
|
|
621
|
+
: patchedForReporting;
|
|
622
|
+
}
|
|
623
|
+
if (!isRetryResend) {
|
|
624
|
+
const targetUserServer = isLid ? 'lid' : 's.whatsapp.net';
|
|
625
|
+
devices.push({
|
|
626
|
+
user,
|
|
627
|
+
device: 0,
|
|
628
|
+
jid: jidEncode(user, targetUserServer, 0) // rajeh, todo: this entire logic is convoluted and weird.
|
|
629
|
+
});
|
|
630
|
+
if (user !== ownUser) {
|
|
631
|
+
const ownUserServer = isLid ? 'lid' : 's.whatsapp.net';
|
|
632
|
+
const ownUserForAddressing = isLid && meLid ? jidDecode(meLid).user : jidDecode(meId).user;
|
|
633
|
+
devices.push({
|
|
634
|
+
user: ownUserForAddressing,
|
|
635
|
+
device: 0,
|
|
636
|
+
jid: jidEncode(ownUserForAddressing, ownUserServer, 0)
|
|
637
|
+
});
|
|
393
638
|
}
|
|
394
639
|
if (additionalAttributes?.['category'] !== 'peer') {
|
|
395
|
-
|
|
396
|
-
devices.
|
|
640
|
+
// Clear placeholders and enumerate actual devices
|
|
641
|
+
devices.length = 0;
|
|
642
|
+
// Use conversation-appropriate sender identity
|
|
643
|
+
const senderIdentity = isLid && meLid
|
|
644
|
+
? jidEncode(jidDecode(meLid)?.user, 'lid', undefined)
|
|
645
|
+
: jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined);
|
|
646
|
+
// Enumerate devices for sender and target with consistent addressing
|
|
647
|
+
const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false);
|
|
648
|
+
devices.push(...sessionDevices);
|
|
649
|
+
logger.debug({
|
|
650
|
+
deviceCount: devices.length,
|
|
651
|
+
devices: devices.map(d => `${d.user}:${d.device}@${jidDecode(d.jid)?.server}`)
|
|
652
|
+
}, 'Device enumeration complete with unified addressing');
|
|
397
653
|
}
|
|
398
654
|
}
|
|
399
|
-
const
|
|
400
|
-
const
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
655
|
+
const allRecipients = [];
|
|
656
|
+
const meRecipients = [];
|
|
657
|
+
const otherRecipients = [];
|
|
658
|
+
const { user: mePnUser } = jidDecode(meId);
|
|
659
|
+
const { user: meLidUser } = meLid ? jidDecode(meLid) : { user: null };
|
|
660
|
+
for (const { user, jid } of devices) {
|
|
661
|
+
const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
|
|
662
|
+
if (isExactSenderDevice) {
|
|
663
|
+
logger.debug({ jid, meId, meLid }, 'Skipping exact sender device (whatsmeow pattern)');
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
// Check if this is our device (could match either PN or LID user)
|
|
667
|
+
const isMe = user === mePnUser || user === meLidUser;
|
|
405
668
|
if (isMe) {
|
|
406
|
-
|
|
669
|
+
meRecipients.push(jid);
|
|
407
670
|
}
|
|
408
671
|
else {
|
|
409
|
-
|
|
672
|
+
otherRecipients.push(jid);
|
|
410
673
|
}
|
|
411
|
-
|
|
674
|
+
allRecipients.push(jid);
|
|
412
675
|
}
|
|
413
|
-
await assertSessions(
|
|
676
|
+
await assertSessions(allRecipients);
|
|
414
677
|
const [{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }] = await Promise.all([
|
|
415
|
-
|
|
416
|
-
createParticipantNodes(
|
|
417
|
-
|
|
678
|
+
// For own devices: use DSM if available (1:1 chats only)
|
|
679
|
+
createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
|
|
680
|
+
createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)
|
|
681
|
+
]);
|
|
418
682
|
participants.push(...meNodes);
|
|
419
683
|
participants.push(...otherNodes);
|
|
684
|
+
if (meRecipients.length > 0 || otherRecipients.length > 0) {
|
|
685
|
+
extraAttrs['phash'] = generateParticipantHashV2([...meRecipients, ...otherRecipients]);
|
|
686
|
+
}
|
|
420
687
|
shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2;
|
|
421
688
|
}
|
|
689
|
+
if (isRetryResend) {
|
|
690
|
+
const isParticipantLid = isLidUser(participant.jid);
|
|
691
|
+
const isMe = areJidsSameUser(participant.jid, isParticipantLid ? meLid : meId);
|
|
692
|
+
const encodedMessageToSend = isMe
|
|
693
|
+
? encodeWAMessage({
|
|
694
|
+
deviceSentMessage: {
|
|
695
|
+
destinationJid,
|
|
696
|
+
message
|
|
697
|
+
}
|
|
698
|
+
})
|
|
699
|
+
: encodeWAMessage(message);
|
|
700
|
+
const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({
|
|
701
|
+
data: encodedMessageToSend,
|
|
702
|
+
jid: participant.jid
|
|
703
|
+
});
|
|
704
|
+
binaryNodeContent.push({
|
|
705
|
+
tag: 'enc',
|
|
706
|
+
attrs: {
|
|
707
|
+
v: '2',
|
|
708
|
+
type,
|
|
709
|
+
count: participant.count?.toString() || '0'
|
|
710
|
+
},
|
|
711
|
+
content: encryptedContent
|
|
712
|
+
});
|
|
713
|
+
}
|
|
422
714
|
if (participants.length) {
|
|
423
715
|
if (additionalAttributes?.['category'] === 'peer') {
|
|
424
|
-
const peerNode = participants[0]?.content?.[0]
|
|
716
|
+
const peerNode = participants[0]?.content?.[0];
|
|
425
717
|
if (peerNode) {
|
|
426
|
-
binaryNodeContent.push(peerNode)
|
|
718
|
+
binaryNodeContent.push(peerNode); // push only enc
|
|
427
719
|
}
|
|
428
720
|
}
|
|
429
721
|
else {
|
|
@@ -431,24 +723,28 @@ const makeMessagesSocket = (config) => {
|
|
|
431
723
|
tag: 'participants',
|
|
432
724
|
attrs: {},
|
|
433
725
|
content: participants
|
|
434
|
-
})
|
|
726
|
+
});
|
|
435
727
|
}
|
|
436
728
|
}
|
|
437
729
|
const stanza = {
|
|
438
730
|
tag: 'message',
|
|
439
731
|
attrs: {
|
|
440
732
|
id: msgId,
|
|
441
|
-
|
|
733
|
+
to: destinationJid,
|
|
734
|
+
type: getMessageType(message),
|
|
442
735
|
...(additionalAttributes || {})
|
|
443
736
|
},
|
|
444
737
|
content: binaryNodeContent
|
|
445
|
-
}
|
|
738
|
+
};
|
|
739
|
+
// if the participant to send to is explicitly specified (generally retry recp)
|
|
740
|
+
// ensure the message is only sent to that person
|
|
741
|
+
// if a retry receipt is sent to everyone -- it'll fail decryption for everyone else who received the msg
|
|
446
742
|
if (participant) {
|
|
447
|
-
if (
|
|
743
|
+
if (isJidGroup(destinationJid)) {
|
|
448
744
|
stanza.attrs.to = destinationJid;
|
|
449
745
|
stanza.attrs.participant = participant.jid;
|
|
450
746
|
}
|
|
451
|
-
else if (
|
|
747
|
+
else if (areJidsSameUser(participant.jid, meId)) {
|
|
452
748
|
stanza.attrs.to = participant.jid;
|
|
453
749
|
stanza.attrs.recipient = destinationJid;
|
|
454
750
|
}
|
|
@@ -460,143 +756,162 @@ const makeMessagesSocket = (config) => {
|
|
|
460
756
|
stanza.attrs.to = destinationJid;
|
|
461
757
|
}
|
|
462
758
|
if (shouldIncludeDeviceIdentity) {
|
|
759
|
+
;
|
|
463
760
|
stanza.content.push({
|
|
464
761
|
tag: 'device-identity',
|
|
465
762
|
attrs: {},
|
|
466
|
-
content:
|
|
763
|
+
content: encodeSignedDeviceIdentity(authState.creds.account, true)
|
|
467
764
|
});
|
|
468
765
|
logger.debug({ jid }, 'adding device identity');
|
|
469
766
|
}
|
|
470
|
-
if (!isNewsletter &&
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
767
|
+
if (!isNewsletter &&
|
|
768
|
+
!isRetryResend &&
|
|
769
|
+
reportingMessage?.messageContextInfo?.messageSecret &&
|
|
770
|
+
shouldIncludeReportingToken(reportingMessage)) {
|
|
771
|
+
try {
|
|
772
|
+
const encoded = encodeWAMessage(reportingMessage);
|
|
773
|
+
const reportingKey = {
|
|
774
|
+
id: msgId,
|
|
775
|
+
fromMe: true,
|
|
776
|
+
remoteJid: destinationJid,
|
|
777
|
+
participant: participant?.jid
|
|
778
|
+
};
|
|
779
|
+
const reportingNode = await getMessageReportingToken(encoded, reportingMessage, reportingKey);
|
|
780
|
+
if (reportingNode) {
|
|
781
|
+
;
|
|
782
|
+
stanza.content.push(reportingNode);
|
|
783
|
+
logger.trace({ jid }, 'added reporting token to message');
|
|
784
|
+
}
|
|
476
785
|
}
|
|
477
|
-
|
|
478
|
-
|
|
786
|
+
catch (error) {
|
|
787
|
+
logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token');
|
|
479
788
|
}
|
|
480
|
-
logger.debug({ jid }, 'adding business node')
|
|
481
789
|
}
|
|
482
|
-
|
|
790
|
+
const contactTcTokenData = !isGroup && !isRetryResend && !isStatus ? await authState.keys.get('tctoken', [destinationJid]) : {};
|
|
791
|
+
const tcTokenBuffer = contactTcTokenData[destinationJid]?.token;
|
|
792
|
+
if (tcTokenBuffer) {
|
|
793
|
+
;
|
|
794
|
+
stanza.content.push({
|
|
795
|
+
tag: 'tctoken',
|
|
796
|
+
attrs: {},
|
|
797
|
+
content: tcTokenBuffer
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
if (additionalNodes && additionalNodes.length > 0) {
|
|
801
|
+
;
|
|
483
802
|
stanza.content.push(...additionalNodes);
|
|
484
803
|
}
|
|
804
|
+
// vltcs@changes 30-01-26 --- Add Biz Binary Node to support button messages
|
|
805
|
+
else if (shouldIncludeBizBinaryNode(innerMessage)) {
|
|
806
|
+
const bizNode = getBizBinaryNode(innerMessage);
|
|
807
|
+
stanza.content.push(bizNode);
|
|
808
|
+
}
|
|
809
|
+
if (isGroup && OLD_GROUP_ID_REGEX.test(jid) && !innerMessage.reactionMessage) {
|
|
810
|
+
stanza.content.push({
|
|
811
|
+
tag: 'multicast',
|
|
812
|
+
attrs: {},
|
|
813
|
+
content: undefined
|
|
814
|
+
})
|
|
815
|
+
}
|
|
485
816
|
logger.debug({ msgId }, `sending message to ${participants.length} devices`);
|
|
486
817
|
await sendNode(stanza);
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
id: msgId
|
|
494
|
-
},
|
|
495
|
-
message: message,
|
|
496
|
-
messageTimestamp: Utils_1.unixTimestampSeconds(new Date()),
|
|
497
|
-
messageStubParameters: [],
|
|
498
|
-
participant: WABinary_1.isJidGroup(jid) || WABinary_1.isJidStatusBroadcast(jid) ? meId : undefined,
|
|
499
|
-
status: Types_1.WAMessageStatus.PENDING
|
|
500
|
-
}
|
|
501
|
-
return Types_1.WAProto.WebMessageInfo.fromObject(messageJSON);
|
|
818
|
+
// Add message to retry cache if enabled
|
|
819
|
+
if (messageRetryManager && !participant) {
|
|
820
|
+
messageRetryManager.addRecentMessage(destinationJid, msgId, message);
|
|
821
|
+
}
|
|
822
|
+
}, meId);
|
|
823
|
+
return msgId;
|
|
502
824
|
};
|
|
503
|
-
const
|
|
504
|
-
const
|
|
505
|
-
if (
|
|
506
|
-
return '
|
|
825
|
+
const getMessageType = (message) => {
|
|
826
|
+
const normalizedMessage = normalizeMessageContent(message);
|
|
827
|
+
if (!normalizedMessage)
|
|
828
|
+
return 'text';
|
|
829
|
+
if (normalizedMessage.reactionMessage || normalizedMessage.encReactionMessage) {
|
|
830
|
+
return 'reaction';
|
|
507
831
|
}
|
|
508
|
-
|
|
509
|
-
|
|
832
|
+
if (normalizedMessage.pollCreationMessage ||
|
|
833
|
+
normalizedMessage.pollCreationMessageV2 ||
|
|
834
|
+
normalizedMessage.pollCreationMessageV3 ||
|
|
835
|
+
normalizedMessage.pollCreationMessageV5 ||
|
|
836
|
+
normalizedMessage.pollCreationMessageV6 ||
|
|
837
|
+
normalizedMessage.pollUpdateMessage) {
|
|
838
|
+
return 'poll';
|
|
510
839
|
}
|
|
511
|
-
|
|
512
|
-
return '
|
|
840
|
+
if (normalizedMessage.eventMessage) {
|
|
841
|
+
return 'event';
|
|
842
|
+
}
|
|
843
|
+
if (getMediaType(normalizedMessage) !== '') {
|
|
844
|
+
return 'media';
|
|
513
845
|
}
|
|
514
|
-
|
|
846
|
+
return 'text';
|
|
847
|
+
};
|
|
515
848
|
const getMediaType = (message) => {
|
|
516
849
|
if (message.imageMessage) {
|
|
517
|
-
return 'image'
|
|
850
|
+
return 'image';
|
|
518
851
|
}
|
|
519
852
|
else if (message.videoMessage) {
|
|
520
|
-
return message.videoMessage.gifPlayback ? 'gif' : 'video'
|
|
853
|
+
return message.videoMessage.gifPlayback ? 'gif' : 'video';
|
|
854
|
+
}
|
|
855
|
+
else if (message.stickerMessage) {
|
|
856
|
+
return message.stickerMessage.isLottie ? '1p_sticker' : message.stickerMessage.isAvatar ? 'avatar_sticker' : 'sticker';
|
|
521
857
|
}
|
|
522
858
|
else if (message.audioMessage) {
|
|
523
|
-
return message.audioMessage.ptt ? 'ptt' : 'audio'
|
|
859
|
+
return message.audioMessage.ptt ? 'ptt' : 'audio';
|
|
860
|
+
}
|
|
861
|
+
else if (message.albumMessage) {
|
|
862
|
+
return 'collection';
|
|
524
863
|
}
|
|
525
864
|
else if (message.contactMessage) {
|
|
526
|
-
return 'vcard'
|
|
865
|
+
return 'vcard';
|
|
527
866
|
}
|
|
528
867
|
else if (message.documentMessage) {
|
|
529
|
-
return 'document'
|
|
868
|
+
return 'document';
|
|
530
869
|
}
|
|
531
870
|
else if (message.contactsArrayMessage) {
|
|
532
|
-
return 'contact_array'
|
|
871
|
+
return 'contact_array';
|
|
533
872
|
}
|
|
534
873
|
else if (message.liveLocationMessage) {
|
|
535
|
-
return 'livelocation'
|
|
874
|
+
return 'livelocation';
|
|
536
875
|
}
|
|
537
|
-
else if (message.
|
|
538
|
-
return '
|
|
876
|
+
else if (message.stickerPackMessage) {
|
|
877
|
+
return 'sticker_pack';
|
|
539
878
|
}
|
|
540
879
|
else if (message.listMessage) {
|
|
541
|
-
return 'list'
|
|
880
|
+
return 'list';
|
|
542
881
|
}
|
|
543
882
|
else if (message.listResponseMessage) {
|
|
544
|
-
return 'list_response'
|
|
883
|
+
return 'list_response';
|
|
545
884
|
}
|
|
546
885
|
else if (message.buttonsResponseMessage) {
|
|
547
|
-
return 'buttons_response'
|
|
886
|
+
return 'buttons_response';
|
|
548
887
|
}
|
|
549
888
|
else if (message.orderMessage) {
|
|
550
|
-
return 'order'
|
|
889
|
+
return 'order';
|
|
551
890
|
}
|
|
552
891
|
else if (message.productMessage) {
|
|
553
|
-
return 'product'
|
|
892
|
+
return 'product';
|
|
554
893
|
}
|
|
555
894
|
else if (message.interactiveResponseMessage) {
|
|
556
|
-
return 'native_flow_response'
|
|
557
|
-
}
|
|
558
|
-
else if (message.groupInviteMessage) {
|
|
559
|
-
return 'url'
|
|
560
|
-
}
|
|
561
|
-
else if (/https:\/\/wa\.me\/p\/\d+\/\d+/.test(message.extendedTextMessage?.text)) {
|
|
562
|
-
return 'productlink'
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
const getButtonType = (message) => {
|
|
566
|
-
if (message.listMessage) {
|
|
567
|
-
return 'list'
|
|
568
|
-
}
|
|
569
|
-
else if (message.buttonsMessage) {
|
|
570
|
-
return 'buttons'
|
|
571
|
-
}
|
|
572
|
-
else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'review_and_pay') {
|
|
573
|
-
return 'review_and_pay'
|
|
574
|
-
}
|
|
575
|
-
else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'review_order') {
|
|
576
|
-
return 'review_order'
|
|
895
|
+
return 'native_flow_response';
|
|
577
896
|
}
|
|
578
|
-
else if (message.
|
|
579
|
-
return '
|
|
897
|
+
else if (message.extendedTextMessage?.matchedText || message.groupInviteMessage) {
|
|
898
|
+
return 'url';
|
|
580
899
|
}
|
|
581
|
-
|
|
582
|
-
|
|
900
|
+
// Lia@Note 02-02-26 --- Add more message type here
|
|
901
|
+
else if ((message.extendedTextMessage?.text || message.conversation || '').includes('://wa.me/c/')) {
|
|
902
|
+
return 'cataloglink';
|
|
583
903
|
}
|
|
584
|
-
else if (message.
|
|
585
|
-
return '
|
|
904
|
+
else if ((message.extendedTextMessage?.text || message.conversation || '').includes('://wa.me/p/')) {
|
|
905
|
+
return 'productlink';
|
|
586
906
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
}
|
|
590
|
-
else if (message.interactiveMessage?.nativeFlowMessage) {
|
|
591
|
-
return 'native_flow'
|
|
592
|
-
}
|
|
593
|
-
}
|
|
907
|
+
return ''
|
|
908
|
+
};
|
|
594
909
|
const getPrivacyTokens = async (jids) => {
|
|
595
|
-
const t =
|
|
910
|
+
const t = unixTimestampSeconds().toString();
|
|
596
911
|
const result = await query({
|
|
597
912
|
tag: 'iq',
|
|
598
913
|
attrs: {
|
|
599
|
-
to:
|
|
914
|
+
to: S_WHATSAPP_NET,
|
|
600
915
|
type: 'set',
|
|
601
916
|
xmlns: 'privacy'
|
|
602
917
|
},
|
|
@@ -607,7 +922,7 @@ const makeMessagesSocket = (config) => {
|
|
|
607
922
|
content: jids.map(jid => ({
|
|
608
923
|
tag: 'token',
|
|
609
924
|
attrs: {
|
|
610
|
-
jid:
|
|
925
|
+
jid: jidNormalizedUser(jid),
|
|
611
926
|
t,
|
|
612
927
|
type: 'trusted_contact'
|
|
613
928
|
}
|
|
@@ -616,10 +931,9 @@ const makeMessagesSocket = (config) => {
|
|
|
616
931
|
]
|
|
617
932
|
});
|
|
618
933
|
return result;
|
|
619
|
-
}
|
|
620
|
-
const waUploadToServer =
|
|
621
|
-
const
|
|
622
|
-
const waitForMsgMediaUpdate = (0, Utils_1.bindWaitForEvent)(ev, 'messages.media-update');
|
|
934
|
+
};
|
|
935
|
+
const waUploadToServer = getWAUploadToServer(config, refreshMediaConn);
|
|
936
|
+
const waitForMsgMediaUpdate = bindWaitForEvent(ev, 'messages.media-update');
|
|
623
937
|
return {
|
|
624
938
|
...sock,
|
|
625
939
|
getPrivacyTokens,
|
|
@@ -627,23 +941,24 @@ const makeMessagesSocket = (config) => {
|
|
|
627
941
|
relayMessage,
|
|
628
942
|
sendReceipt,
|
|
629
943
|
sendReceipts,
|
|
630
|
-
violeticsHandler,
|
|
631
944
|
readMessages,
|
|
632
945
|
refreshMediaConn,
|
|
633
|
-
getUSyncDevices,
|
|
634
|
-
createParticipantNodes,
|
|
635
946
|
waUploadToServer,
|
|
636
|
-
sendPeerDataOperationMessage,
|
|
637
947
|
fetchPrivacySettings,
|
|
948
|
+
sendPeerDataOperationMessage,
|
|
949
|
+
createParticipantNodes,
|
|
950
|
+
getUSyncDevices,
|
|
951
|
+
messageRetryManager,
|
|
952
|
+
updateMemberLabel,
|
|
638
953
|
updateMediaMessage: async (message) => {
|
|
639
|
-
const content =
|
|
954
|
+
const content = assertMediaContent(message.message);
|
|
640
955
|
const mediaKey = content.mediaKey;
|
|
641
956
|
const meId = authState.creds.me.id;
|
|
642
|
-
const node =
|
|
957
|
+
const node = encryptMediaRetryRequest(message.key, mediaKey, meId);
|
|
643
958
|
let error = undefined;
|
|
644
959
|
await Promise.all([
|
|
645
960
|
sendNode(node),
|
|
646
|
-
waitForMsgMediaUpdate(update => {
|
|
961
|
+
waitForMsgMediaUpdate(async (update) => {
|
|
647
962
|
const result = update.find(c => c.key.id === message.key.id);
|
|
648
963
|
if (result) {
|
|
649
964
|
if (result.error) {
|
|
@@ -651,13 +966,16 @@ const makeMessagesSocket = (config) => {
|
|
|
651
966
|
}
|
|
652
967
|
else {
|
|
653
968
|
try {
|
|
654
|
-
const media =
|
|
655
|
-
if (media.result !==
|
|
656
|
-
const resultStr =
|
|
657
|
-
throw new
|
|
969
|
+
const media = decryptMediaRetryData(result.media, mediaKey, result.key.id);
|
|
970
|
+
if (media.result !== proto.MediaRetryNotification.ResultType.SUCCESS) {
|
|
971
|
+
const resultStr = proto.MediaRetryNotification.ResultType[media.result];
|
|
972
|
+
throw new Boom(`Media re-upload failed by device (${resultStr})`, {
|
|
973
|
+
data: media,
|
|
974
|
+
statusCode: getStatusCodeForMediaRetry(media.result) || 404
|
|
975
|
+
});
|
|
658
976
|
}
|
|
659
977
|
content.directPath = media.directPath;
|
|
660
|
-
content.url =
|
|
978
|
+
content.url = getUrlFromDirectPath(content.directPath);
|
|
661
979
|
logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful');
|
|
662
980
|
}
|
|
663
981
|
catch (err) {
|
|
@@ -671,145 +989,258 @@ const makeMessagesSocket = (config) => {
|
|
|
671
989
|
if (error) {
|
|
672
990
|
throw error;
|
|
673
991
|
}
|
|
674
|
-
ev.emit('messages.update', [
|
|
675
|
-
{
|
|
676
|
-
key: message.key,
|
|
677
|
-
update: {
|
|
678
|
-
message: message.message
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
]);
|
|
992
|
+
ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
|
|
682
993
|
return message;
|
|
683
994
|
},
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
995
|
+
// vltcs@changes 30-01-26 --- Add support for modifying additionalNodes and additionalAttributes using "options" in sendMessage()
|
|
996
|
+
sendMessage: async (jid, content, options = {}) => {
|
|
997
|
+
const userJid = authState.creds.me.id;
|
|
998
|
+
// vltcs@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);
|
|
690
1027
|
}
|
|
691
1028
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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)
|
|
699
1061
|
},
|
|
700
|
-
|
|
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
|
+
});
|
|
701
1088
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
sendMessage: async (jid, content, options = {}) => {
|
|
717
|
-
const userJid = authState.creds.me.id;
|
|
718
|
-
delete options.ephemeralExpiration
|
|
719
|
-
const { filter = false, quoted } = options;
|
|
720
|
-
const getParticipantAttr = () => filter ? { participant: { jid } } : {};
|
|
721
|
-
const messageType = violeticsHandler.detectType(content);
|
|
722
|
-
if (typeof content === 'object' && 'disappearingMessagesInChat' in content &&
|
|
723
|
-
typeof content['disappearingMessagesInChat'] !== 'undefined' && WABinary_1.isJidGroup(jid)) {
|
|
724
|
-
const { disappearingMessagesInChat } = content
|
|
725
|
-
const value = typeof disappearingMessagesInChat === 'boolean' ?
|
|
726
|
-
(disappearingMessagesInChat ? Defaults_1.WA_DEFAULT_EPHEMERAL : 0) :
|
|
727
|
-
disappearingMessagesInChat
|
|
728
|
-
await groupToggleEphemeral(jid, value)
|
|
1089
|
+
await delay(delayMs);
|
|
1090
|
+
}
|
|
1091
|
+
return fullMsg;
|
|
1092
|
+
}
|
|
1093
|
+
else if ('disappearingMessagesInChat' in content && isJidGroup(jid)) {
|
|
1094
|
+
const { disappearingMessagesInChat } = content;
|
|
1095
|
+
const value = typeof disappearingMessagesInChat === 'boolean'
|
|
1096
|
+
? disappearingMessagesInChat
|
|
1097
|
+
? WA_DEFAULT_EPHEMERAL
|
|
1098
|
+
: 0
|
|
1099
|
+
: disappearingMessagesInChat;
|
|
1100
|
+
await groupToggleEphemeral(jid, value);
|
|
729
1101
|
}
|
|
730
1102
|
else {
|
|
731
|
-
|
|
732
|
-
if (messageType) {
|
|
733
|
-
switch (messageType) {
|
|
734
|
-
case 'PAYMENT':
|
|
735
|
-
const paymentContent = await violeticsHandler.handlePayment(content, quoted);
|
|
736
|
-
return await relayMessage(jid, paymentContent, {
|
|
737
|
-
messageId: Utils_1.generateMessageID(),
|
|
738
|
-
...getParticipantAttr()
|
|
739
|
-
});
|
|
740
|
-
case 'PRODUCT':
|
|
741
|
-
const productContent = await violeticsHandler.handleProduct(content, jid, quoted);
|
|
742
|
-
const productMsg = await Utils_1.generateWAMessageFromContent(jid, productContent, { quoted });
|
|
743
|
-
return await relayMessage(jid, productMsg.message, {
|
|
744
|
-
messageId: productMsg.key.id,
|
|
745
|
-
...getParticipantAttr()
|
|
746
|
-
});
|
|
747
|
-
case 'INTERACTIVE':
|
|
748
|
-
const interactiveContent = await violeticsHandler.handleInteractive(content, jid, quoted);
|
|
749
|
-
const interactiveMsg = await Utils_1.generateWAMessageFromContent(jid, interactiveContent, { quoted });
|
|
750
|
-
return await relayMessage(jid, interactiveMsg.message, {
|
|
751
|
-
messageId: interactiveMsg.key.id,
|
|
752
|
-
...getParticipantAttr()
|
|
753
|
-
});
|
|
754
|
-
case 'ALBUM':
|
|
755
|
-
return await violeticsHandler.handleAlbum(content, jid, quoted)
|
|
756
|
-
case 'EVENT':
|
|
757
|
-
return await violeticsHandler.handleEvent(content, jid, quoted)
|
|
758
|
-
case 'POLL_RESULT':
|
|
759
|
-
return await violeticsHandler.handlePollResult(content, jid, quoted)
|
|
760
|
-
case 'GROUP_STORY':
|
|
761
|
-
return await violeticsHandler.handleGroupStory(content, jid, quoted)
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
const fullMsg = await Utils_1.generateWAMessage(jid, content, {
|
|
1103
|
+
const fullMsg = await generateWAMessage(jid, content, {
|
|
765
1104
|
logger,
|
|
766
1105
|
userJid,
|
|
767
|
-
|
|
768
|
-
getUrlInfo: text => link_preview_1.getUrlInfo(text, {
|
|
1106
|
+
getUrlInfo: text => getUrlInfo(text, {
|
|
769
1107
|
thumbnailWidth: linkPreviewImageThumbnailWidth,
|
|
770
1108
|
fetchOpts: {
|
|
771
1109
|
timeout: 3000,
|
|
772
|
-
...
|
|
1110
|
+
...(httpRequestOptions || {})
|
|
773
1111
|
},
|
|
774
1112
|
logger,
|
|
775
1113
|
uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
|
|
776
1114
|
}),
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
});
|
|
782
|
-
return up;
|
|
783
|
-
},
|
|
1115
|
+
//TODO: CACHE
|
|
1116
|
+
getProfilePicUrl: sock.profilePictureUrl,
|
|
1117
|
+
getCallLink: sock.createCallLink,
|
|
1118
|
+
upload: waUploadToServer,
|
|
784
1119
|
mediaCache: config.mediaCache,
|
|
785
1120
|
options: config.options,
|
|
786
|
-
...options
|
|
1121
|
+
...options,
|
|
1122
|
+
messageId: generateMessageIDV2(userJid)
|
|
787
1123
|
});
|
|
1124
|
+
const isNewsletter = isJidNewsletter(jid);
|
|
1125
|
+
const isEventMsg = 'event' in content && !!content.event;
|
|
788
1126
|
const isDeleteMsg = 'delete' in content && !!content.delete;
|
|
789
1127
|
const isEditMsg = 'edit' in content && !!content.edit;
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
1128
|
+
const isPinMsg = 'pin' in content && !!content.pin;
|
|
1129
|
+
const isKeepMsg = 'keep' in content && !!content.keep;
|
|
1130
|
+
const isPollMsg = 'poll' in content && !!content.poll;
|
|
1131
|
+
const isQuizMsg = 'poll' in content && !!content.poll.pollType;
|
|
1132
|
+
const isPollResultMsg = 'pollResult' in content && !!content.pollResult;
|
|
1133
|
+
const isPollUpdateMsg = 'pollUpdate' in content && !!content.pollUpdate;
|
|
1134
|
+
const isAiMsg = 'ai' in content && !!content.ai;
|
|
1135
|
+
const additionalAttributes = options.additionalAttributes || {};
|
|
1136
|
+
const additionalNodes = options.additionalNodes || [];
|
|
1137
|
+
// required for delete
|
|
1138
|
+
if (isDeleteMsg || isKeepMsg) {
|
|
1139
|
+
// if the chat is a group, and I am not the author, then delete the message as an admin
|
|
1140
|
+
if (isJidGroup(content.delete?.remoteJid) && !content.delete?.fromMe) {
|
|
1141
|
+
additionalAttributes.edit = '8';
|
|
1142
|
+
}
|
|
1143
|
+
else {
|
|
1144
|
+
additionalAttributes.edit = '7';
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
else if (isEditMsg) {
|
|
1148
|
+
additionalAttributes.edit = isNewsletter ? '3' : '1';
|
|
1149
|
+
}
|
|
1150
|
+
else if (isPinMsg) {
|
|
1151
|
+
additionalAttributes.edit = '2';
|
|
1152
|
+
}
|
|
1153
|
+
else if (isPollMsg) {
|
|
1154
|
+
if (!isJidNewsletter(jid) && isQuizMsg) {
|
|
1155
|
+
throw new Boom('Quiz are only allowed for newsletter', { statusCode: 400 });
|
|
1156
|
+
}
|
|
1157
|
+
additionalNodes.push({
|
|
1158
|
+
tag: 'meta',
|
|
1159
|
+
attrs: {
|
|
1160
|
+
// Lia@Note 08-02-26 --- Still a hypothesis regarding PollResult ༎ຶ‿༎ຶ
|
|
1161
|
+
polltype: isQuizMsg ? 'quiz_creation' : isPollResultMsg || isPollUpdateMsg ? 'vote' : 'creation',
|
|
1162
|
+
contenttype: isPollMsg && isNewsletter ? 'text' : undefined
|
|
1163
|
+
},
|
|
1164
|
+
content: undefined
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
else if (isEventMsg) {
|
|
1168
|
+
additionalNodes.push({
|
|
1169
|
+
tag: 'meta',
|
|
1170
|
+
attrs: {
|
|
1171
|
+
event_type: 'creation'
|
|
1172
|
+
},
|
|
1173
|
+
content: undefined
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
// vltcs@changes 30-01-26 --- Add support for AI label in message when "ai" is true, but works only in private chat
|
|
1177
|
+
else if (isAiMsg) {
|
|
1178
|
+
if (!(isPnUser(jid) || isLidUser(jid))) {
|
|
1179
|
+
throw new Boom('AI labeled message are only allowed in private chat', { statusCode: 400 });
|
|
1180
|
+
}
|
|
1181
|
+
if ('messageContextInfo' in fullMsg.message && !!fullMsg.message.messageContextInfo) {
|
|
1182
|
+
fullMsg.message.messageContextInfo.supportPayload = BIZ_BOT_SUPPORT_PAYLOAD;
|
|
1183
|
+
};
|
|
1184
|
+
additionalNodes.push({
|
|
1185
|
+
tag: 'bot',
|
|
1186
|
+
attrs: {
|
|
1187
|
+
biz_bot: '1'
|
|
1188
|
+
},
|
|
1189
|
+
content: undefined
|
|
1190
|
+
});
|
|
1191
|
+
delete content.ai;
|
|
797
1192
|
}
|
|
798
1193
|
await relayMessage(jid, fullMsg.message, {
|
|
799
1194
|
messageId: fullMsg.key.id,
|
|
800
|
-
|
|
801
|
-
|
|
1195
|
+
useCachedGroupMetadata: options.useCachedGroupMetadata,
|
|
1196
|
+
statusJidList: options.statusJidList,
|
|
802
1197
|
additionalAttributes,
|
|
803
|
-
|
|
1198
|
+
additionalNodes
|
|
804
1199
|
});
|
|
805
1200
|
if (config.emitOwnEvents) {
|
|
806
|
-
process.nextTick(() => {
|
|
807
|
-
|
|
1201
|
+
process.nextTick(async () => {
|
|
1202
|
+
await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
|
|
808
1203
|
});
|
|
809
1204
|
}
|
|
1205
|
+
// vltcs@changes 31-01-26 --- Add support for album messages
|
|
1206
|
+
// Lia@Note 06-02-26 --- Refactored to reduce high RSS usage (╥﹏╥)
|
|
1207
|
+
if ('album' in content) {
|
|
1208
|
+
const { delayMs = 1500 } = options;
|
|
1209
|
+
for (const albumMedia of content.album) {
|
|
1210
|
+
const albumMsg = await generateWAMessage(jid, albumMedia, {
|
|
1211
|
+
logger,
|
|
1212
|
+
userJid,
|
|
1213
|
+
upload: waUploadToServer,
|
|
1214
|
+
mediaCache: config.mediaCache,
|
|
1215
|
+
options: config.options,
|
|
1216
|
+
...options,
|
|
1217
|
+
messageId: generateMessageIDV2(userJid)
|
|
1218
|
+
});
|
|
1219
|
+
if (!hasValidAlbumMedia(normalizeMessageContent(albumMsg.message))) {
|
|
1220
|
+
throw new Boom('Invalid message type for album', { statusCode: 400 });
|
|
1221
|
+
}
|
|
1222
|
+
albumMsg.message.messageContextInfo ||= {};
|
|
1223
|
+
albumMsg.message.messageContextInfo.messageAssociation = {
|
|
1224
|
+
parentMessageKey: fullMsg.key,
|
|
1225
|
+
associationType: AssociationType.MEDIA_ALBUM
|
|
1226
|
+
};
|
|
1227
|
+
await relayMessage(jid, albumMsg.message, {
|
|
1228
|
+
messageId: albumMsg.key.id,
|
|
1229
|
+
useCachedGroupMetadata: options.useCachedGroupMetadata,
|
|
1230
|
+
statusJidList: options.statusJidList,
|
|
1231
|
+
additionalAttributes,
|
|
1232
|
+
additionalNodes
|
|
1233
|
+
});
|
|
1234
|
+
if (config.emitOwnEvents) {
|
|
1235
|
+
process.nextTick(async () => {
|
|
1236
|
+
await messageMutex.mutex(() => upsertMessage(albumMsg, 'append'));
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
await delay(delayMs);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
810
1242
|
return fullMsg;
|
|
811
1243
|
}
|
|
812
1244
|
}
|
|
813
|
-
}
|
|
814
|
-
};
|
|
815
|
-
exports.makeMessagesSocket = makeMessagesSocket;
|
|
1245
|
+
};
|
|
1246
|
+
};
|