@bellaxchuu/flowred 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/WAProto/GenerateStatics.sh +3 -0
- package/WAProto/WAProto.proto +6902 -0
- package/WAProto/fix-imports.js +85 -0
- package/WAProto/index.js +242946 -0
- package/engine-requirements.js +10 -0
- package/lib/Defaults/index.js +190 -0
- package/lib/Defaults/phonenumber-mcc.json +223 -0
- package/lib/Signal/Group/ciphertext-message.js +15 -0
- package/lib/Signal/Group/group-session-builder.js +92 -0
- package/lib/Signal/Group/group_cipher.js +89 -0
- package/lib/Signal/Group/index.js +136 -0
- package/lib/Signal/Group/keyhelper.js +73 -0
- package/lib/Signal/Group/sender-chain-key.js +32 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +66 -0
- package/lib/Signal/Group/sender-key-message.js +69 -0
- package/lib/Signal/Group/sender-key-name.js +50 -0
- package/lib/Signal/Group/sender-key-record.js +44 -0
- package/lib/Signal/Group/sender-key-state.js +97 -0
- package/lib/Signal/Group/sender-message-key.js +30 -0
- package/lib/Signal/libsignal.js +490 -0
- package/lib/Signal/lid-mapping.js +262 -0
- package/lib/Socket/Client/index.js +30 -0
- package/lib/Socket/Client/types.js +13 -0
- package/lib/Socket/Client/websocket.js +62 -0
- package/lib/Socket/aigroups.js +240 -0
- package/lib/Socket/business.js +422 -0
- package/lib/Socket/chats.js +1816 -0
- package/lib/Socket/communities.js +549 -0
- package/lib/Socket/graphql.js +656 -0
- package/lib/Socket/groups.js +764 -0
- package/lib/Socket/index.js +39 -0
- package/lib/Socket/interactive-handler.js +522 -0
- package/lib/Socket/interop.js +549 -0
- package/lib/Socket/luxu.js +387 -0
- package/lib/Socket/managed-account.js +183 -0
- package/lib/Socket/messages-recv.js +2440 -0
- package/lib/Socket/messages-send.js +1853 -0
- package/lib/Socket/mex.js +60 -0
- package/lib/Socket/newsletter.js +820 -0
- package/lib/Socket/privacy.js +449 -0
- package/lib/Socket/registration.js +427 -0
- package/lib/Socket/socket.js +1092 -0
- package/lib/Socket/text-router.js +83 -0
- package/lib/Socket/username.js +243 -0
- package/lib/Store/index.js +36 -0
- package/lib/Store/keyed-db.js +108 -0
- package/lib/Store/make-cache-manager-store.js +90 -0
- package/lib/Store/make-in-memory-store.js +604 -0
- package/lib/Store/make-ordered-dictionary.js +81 -0
- package/lib/Store/object-repository.js +29 -0
- package/lib/Types/Auth.js +38 -0
- package/lib/Types/Bussines.js +2 -0
- package/lib/Types/Call.js +2 -0
- package/lib/Types/Chat.js +4 -0
- package/lib/Types/Contact.js +2 -0
- package/lib/Types/Events.js +2 -0
- package/lib/Types/GroupMetadata.js +2 -0
- package/lib/Types/Label.js +27 -0
- package/lib/Types/LabelAssociation.js +9 -0
- package/lib/Types/Message.js +95 -0
- package/lib/Types/Mex.js +112 -0
- package/lib/Types/Newsletter.js +121 -0
- package/lib/Types/Product.js +2 -0
- package/lib/Types/Signal.js +2 -0
- package/lib/Types/Socket.js +2 -0
- package/lib/Types/State.js +70 -0
- package/lib/Types/USync.js +2 -0
- package/lib/Types/index.js +55 -0
- package/lib/Utils/auth-utils.js +306 -0
- package/lib/Utils/browser-utils.js +114 -0
- package/lib/Utils/business.js +247 -0
- package/lib/Utils/chat-utils.js +1208 -0
- package/lib/Utils/command-loader.js +100 -0
- package/lib/Utils/companion-reg-client-utils.js +42 -0
- package/lib/Utils/consumer-application.js +107 -0
- package/lib/Utils/crypto.js +155 -0
- package/lib/Utils/curve25519-js.js +275 -0
- package/lib/Utils/decode-wa-message.js +560 -0
- package/lib/Utils/event-buffer.js +607 -0
- package/lib/Utils/generics.js +563 -0
- package/lib/Utils/group-history.js +60 -0
- package/lib/Utils/history.js +244 -0
- package/lib/Utils/identity-change-handler.js +52 -0
- package/lib/Utils/index.js +57 -0
- package/lib/Utils/jid-display-normalization.js +218 -0
- package/lib/Utils/link-preview.js +143 -0
- package/lib/Utils/logger.js +9 -0
- package/lib/Utils/lt-hash.js +39 -0
- package/lib/Utils/make-mutex.js +36 -0
- package/lib/Utils/message-composer.js +479 -0
- package/lib/Utils/message-retry-manager.js +270 -0
- package/lib/Utils/messages-media.js +896 -0
- package/lib/Utils/messages.js +2904 -0
- package/lib/Utils/meta-ai-msmsg.js +262 -0
- package/lib/Utils/native-bridge.js +82 -0
- package/lib/Utils/noise-handler.js +196 -0
- package/lib/Utils/offline-node-processor.js +42 -0
- package/lib/Utils/pre-key-manager.js +107 -0
- package/lib/Utils/process-message.js +1048 -0
- package/lib/Utils/reporting-utils.js +262 -0
- package/lib/Utils/session-pool.js +94 -0
- package/lib/Utils/signal.js +224 -0
- package/lib/Utils/stanza-ack.js +40 -0
- package/lib/Utils/sticker.js +145 -0
- package/lib/Utils/sync-action-utils.js +54 -0
- package/lib/Utils/tc-token-utils.js +161 -0
- package/lib/Utils/use-multi-file-auth-state.js +121 -0
- package/lib/Utils/validate-connection.js +219 -0
- package/lib/Utils/view-once-cache.js +79 -0
- package/lib/Utils/voip-rekey.js +22 -0
- package/lib/WABinary/constants.js +1304 -0
- package/lib/WABinary/decode.js +343 -0
- package/lib/WABinary/encode.js +228 -0
- package/lib/WABinary/generic-utils.js +240 -0
- package/lib/WABinary/index.js +33 -0
- package/lib/WABinary/jid-utils.js +361 -0
- package/lib/WABinary/types.js +2 -0
- package/lib/WAM/BinaryInfo.js +13 -0
- package/lib/WAM/constants.js +39486 -0
- package/lib/WAM/encode.js +142 -0
- package/lib/WAM/index.js +31 -0
- package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +55 -0
- package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +43 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +54 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +57 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +30 -0
- package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +59 -0
- package/lib/WAUSync/Protocols/USyncLIDProtocol.js +31 -0
- package/lib/WAUSync/Protocols/USyncPictureProtocol.js +32 -0
- package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +29 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +39 -0
- package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +38 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +27 -0
- package/lib/WAUSync/Protocols/index.js +39 -0
- package/lib/WAUSync/USyncQuery.js +133 -0
- package/lib/WAUSync/USyncUser.js +50 -0
- package/lib/WAUSync/index.js +31 -0
- package/lib/antiban.js +4637 -0
- package/lib/index.js +45 -0
- package/package.json +107 -0
|
@@ -0,0 +1,1853 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
var __importDefault =
|
|
3
|
+
(this && this.__importDefault) ||
|
|
4
|
+
function (mod) {
|
|
5
|
+
return mod && mod.__esModule ? mod : { default: mod }
|
|
6
|
+
}
|
|
7
|
+
Object.defineProperty(exports, '__esModule', { value: true })
|
|
8
|
+
exports.makeMessagesSocket = void 0
|
|
9
|
+
const node_cache_1 = __importDefault(require('@cacheable/node-cache'))
|
|
10
|
+
const boom_1 = require('@hapi/boom')
|
|
11
|
+
const index_js_1 = require('../../WAProto/index.js')
|
|
12
|
+
const Defaults_1 = require('../Defaults')
|
|
13
|
+
const Utils_1 = require('../Utils')
|
|
14
|
+
const link_preview_1 = require('../Utils/link-preview')
|
|
15
|
+
const make_mutex_1 = require('../Utils/make-mutex')
|
|
16
|
+
const reporting_utils_1 = require('../Utils/reporting-utils')
|
|
17
|
+
const tc_token_utils_1 = require('../Utils/tc-token-utils')
|
|
18
|
+
const jid_display_normalization_1 = require('../Utils/jid-display-normalization')
|
|
19
|
+
const WABinary_1 = require('../WABinary')
|
|
20
|
+
const WAUSync_1 = require('../WAUSync')
|
|
21
|
+
const message_composer_1 = require('../Utils/message-composer.js')
|
|
22
|
+
const interactive_handler_1 = require('./interactive-handler.js')
|
|
23
|
+
const username_1 = require('./username')
|
|
24
|
+
const { setBotMessageSecret } = require('../Utils/decode-wa-message')
|
|
25
|
+
const makeMessagesSocket = config => {
|
|
26
|
+
const {
|
|
27
|
+
logger,
|
|
28
|
+
linkPreviewImageThumbnailWidth,
|
|
29
|
+
generateHighQualityLinkPreview,
|
|
30
|
+
options: httpRequestOptions,
|
|
31
|
+
patchMessageBeforeSending,
|
|
32
|
+
cachedGroupMetadata,
|
|
33
|
+
enableRecentMessageCache,
|
|
34
|
+
maxMsgRetryCount
|
|
35
|
+
} = config
|
|
36
|
+
const sock = (0, username_1.makeUsernameSocket)(config)
|
|
37
|
+
const {
|
|
38
|
+
ev,
|
|
39
|
+
authState,
|
|
40
|
+
messageMutex,
|
|
41
|
+
signalRepository,
|
|
42
|
+
upsertMessage,
|
|
43
|
+
query,
|
|
44
|
+
fetchPrivacySettings,
|
|
45
|
+
sendNode,
|
|
46
|
+
groupMetadata,
|
|
47
|
+
groupToggleEphemeral,
|
|
48
|
+
trustInteropContact
|
|
49
|
+
} = sock
|
|
50
|
+
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping)
|
|
51
|
+
/**
|
|
52
|
+
* Set of tctoken storage JIDs with a fire-and-forget issuePrivacyTokens IQ in flight.
|
|
53
|
+
* Prevents duplicate IQs from rapid back-to-back sends before senderTimestamp persists.
|
|
54
|
+
*/
|
|
55
|
+
const inFlightTcTokenIssuance = new Set()
|
|
56
|
+
const userDevicesCache =
|
|
57
|
+
config.userDevicesCache ||
|
|
58
|
+
new node_cache_1.default({
|
|
59
|
+
stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
|
|
60
|
+
useClones: false
|
|
61
|
+
})
|
|
62
|
+
const peerSessionsCache = new node_cache_1.default({
|
|
63
|
+
stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.USER_DEVICES,
|
|
64
|
+
useClones: false
|
|
65
|
+
})
|
|
66
|
+
// Initialize message retry manager if enabled
|
|
67
|
+
const messageRetryManager = enableRecentMessageCache
|
|
68
|
+
? new Utils_1.MessageRetryManager(logger, maxMsgRetryCount)
|
|
69
|
+
: null
|
|
70
|
+
// Prevent race conditions in Signal session encryption by user
|
|
71
|
+
const encryptionMutex = (0, make_mutex_1.makeKeyedMutex)()
|
|
72
|
+
let mediaConn
|
|
73
|
+
const refreshMediaConn = async (forceGet = false) => {
|
|
74
|
+
const media = await mediaConn
|
|
75
|
+
if (!media || forceGet || new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1000) {
|
|
76
|
+
mediaConn = (async () => {
|
|
77
|
+
const result = await query({
|
|
78
|
+
tag: 'iq',
|
|
79
|
+
attrs: {
|
|
80
|
+
type: 'set',
|
|
81
|
+
xmlns: 'w:m',
|
|
82
|
+
to: WABinary_1.S_WHATSAPP_NET
|
|
83
|
+
},
|
|
84
|
+
content: [{ tag: 'media_conn', attrs: {} }]
|
|
85
|
+
})
|
|
86
|
+
const mediaConnNode = (0, WABinary_1.getBinaryNodeChild)(result, 'media_conn')
|
|
87
|
+
// TODO: explore full length of data that whatsapp provides
|
|
88
|
+
const node = {
|
|
89
|
+
hosts: (0, WABinary_1.getBinaryNodeChildren)(mediaConnNode, 'host').map(({ attrs }) => ({
|
|
90
|
+
hostname: attrs.hostname,
|
|
91
|
+
maxContentLengthBytes: +attrs.maxContentLengthBytes
|
|
92
|
+
})),
|
|
93
|
+
auth: mediaConnNode.attrs.auth,
|
|
94
|
+
ttl: +mediaConnNode.attrs.ttl,
|
|
95
|
+
fetchDate: new Date()
|
|
96
|
+
}
|
|
97
|
+
logger.debug('fetched media conn')
|
|
98
|
+
return node
|
|
99
|
+
})()
|
|
100
|
+
}
|
|
101
|
+
return mediaConn
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* generic send receipt function
|
|
105
|
+
* used for receipts of phone call, read, delivery etc.
|
|
106
|
+
* */
|
|
107
|
+
const sendReceipt = async (jid, participant, messageIds, type, sts) => {
|
|
108
|
+
if (!messageIds || messageIds.length === 0) {
|
|
109
|
+
throw new boom_1.Boom('missing ids in receipt')
|
|
110
|
+
}
|
|
111
|
+
const isInterop = (0, WABinary_1.isInteropUser)(jid)
|
|
112
|
+
const node = {
|
|
113
|
+
tag: 'receipt',
|
|
114
|
+
attrs: {
|
|
115
|
+
id: messageIds[0]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const isReadReceipt = type === 'read' || type === 'read-self'
|
|
119
|
+
if (isReadReceipt) {
|
|
120
|
+
node.attrs.t = (0, Utils_1.unixTimestampSeconds)().toString()
|
|
121
|
+
}
|
|
122
|
+
if (type === 'sender' && ((0, WABinary_1.isPnUser)(jid) || (0, WABinary_1.isLidUser)(jid))) {
|
|
123
|
+
node.attrs.recipient = jid
|
|
124
|
+
node.attrs.to = participant
|
|
125
|
+
} else if (isInterop && !type) {
|
|
126
|
+
// Delivery receipt to interop contact: WA sends to device-qualified JID (user:0@interop)
|
|
127
|
+
const { user } = (0, WABinary_1.jidDecode)(jid)
|
|
128
|
+
node.attrs.to = `${user}:0@interop`
|
|
129
|
+
} else {
|
|
130
|
+
node.attrs.to = jid
|
|
131
|
+
if (participant) {
|
|
132
|
+
node.attrs.participant = participant
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (type) {
|
|
136
|
+
node.attrs.type = type
|
|
137
|
+
}
|
|
138
|
+
// Interop bridge requires sts (microsecond stanza timestamp) from the original message
|
|
139
|
+
if (isInterop && sts) {
|
|
140
|
+
node.attrs.sts = sts
|
|
141
|
+
}
|
|
142
|
+
const remainingMessageIds = messageIds.slice(1)
|
|
143
|
+
if (remainingMessageIds.length) {
|
|
144
|
+
node.content = [
|
|
145
|
+
{
|
|
146
|
+
tag: 'list',
|
|
147
|
+
attrs: {},
|
|
148
|
+
content: remainingMessageIds.map(id => ({
|
|
149
|
+
tag: 'item',
|
|
150
|
+
attrs: { id }
|
|
151
|
+
}))
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
logger.debug({ attrs: node.attrs, messageIds }, 'sending receipt for messages')
|
|
156
|
+
await sendNode(node)
|
|
157
|
+
}
|
|
158
|
+
/** Correctly bulk send receipts to multiple chats, participants */
|
|
159
|
+
const sendReceipts = async (keys, type) => {
|
|
160
|
+
const recps = (0, Utils_1.aggregateMessageKeysNotFromMe)(keys)
|
|
161
|
+
for (const { jid, participant, messageIds, sts } of recps) {
|
|
162
|
+
await sendReceipt(jid, participant, messageIds, type, sts)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/** Bulk read messages. Keys can be from different chats & participants */
|
|
166
|
+
const readMessages = async keys => {
|
|
167
|
+
const privacySettings = await fetchPrivacySettings()
|
|
168
|
+
// based on privacy settings, we have to change the read type
|
|
169
|
+
const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self'
|
|
170
|
+
await sendReceipts(keys, readType)
|
|
171
|
+
}
|
|
172
|
+
/** Fetch all the devices we've to send a message to */
|
|
173
|
+
const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
|
|
174
|
+
const deviceResults = []
|
|
175
|
+
if (!useCache) {
|
|
176
|
+
logger.debug('not using cache for devices')
|
|
177
|
+
}
|
|
178
|
+
const toFetch = []
|
|
179
|
+
const jidsWithUser = jids
|
|
180
|
+
.map(jid => {
|
|
181
|
+
const decoded = (0, WABinary_1.jidDecode)(jid)
|
|
182
|
+
const user = decoded?.user
|
|
183
|
+
const device = decoded?.device
|
|
184
|
+
const isExplicitDevice = typeof device === 'number' && device >= 0
|
|
185
|
+
if (isExplicitDevice && user) {
|
|
186
|
+
deviceResults.push({
|
|
187
|
+
user,
|
|
188
|
+
device,
|
|
189
|
+
jid
|
|
190
|
+
})
|
|
191
|
+
return null
|
|
192
|
+
}
|
|
193
|
+
jid = (0, WABinary_1.jidNormalizedUser)(jid)
|
|
194
|
+
return { jid, user }
|
|
195
|
+
})
|
|
196
|
+
.filter(jid => jid !== null)
|
|
197
|
+
let mgetDevices
|
|
198
|
+
if (useCache && userDevicesCache.mget) {
|
|
199
|
+
const usersToFetch = jidsWithUser.map(j => j?.user).filter(Boolean)
|
|
200
|
+
mgetDevices = await userDevicesCache.mget(usersToFetch)
|
|
201
|
+
}
|
|
202
|
+
for (const { jid, user } of jidsWithUser) {
|
|
203
|
+
if (useCache) {
|
|
204
|
+
const devices = mgetDevices?.[user] || (userDevicesCache.mget ? undefined : await userDevicesCache.get(user))
|
|
205
|
+
if (devices) {
|
|
206
|
+
const devicesWithJid = devices.map(d => ({
|
|
207
|
+
...d,
|
|
208
|
+
jid: (0, WABinary_1.jidEncode)(d.user, d.server, d.device)
|
|
209
|
+
}))
|
|
210
|
+
deviceResults.push(...devicesWithJid)
|
|
211
|
+
logger.trace({ user }, 'using cache for devices')
|
|
212
|
+
} else {
|
|
213
|
+
toFetch.push(jid)
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
toFetch.push(jid)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (!toFetch.length) {
|
|
220
|
+
return deviceResults
|
|
221
|
+
}
|
|
222
|
+
const requestedLidUsers = new Set()
|
|
223
|
+
for (const jid of toFetch) {
|
|
224
|
+
if ((0, WABinary_1.isLidUser)(jid) || (0, WABinary_1.isHostedLidUser)(jid)) {
|
|
225
|
+
const user = (0, WABinary_1.jidDecode)(jid)?.user
|
|
226
|
+
if (user) requestedLidUsers.add(user)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const query = new WAUSync_1.USyncQuery().withContext('message').withDeviceProtocol().withLIDProtocol()
|
|
230
|
+
for (const jid of toFetch) {
|
|
231
|
+
query.withUser(new WAUSync_1.USyncUser().withId(jid)) // todo: investigate - the idea here is that <user> should have an inline lid field with the lid being the pn equivalent
|
|
232
|
+
}
|
|
233
|
+
const result = await sock.executeUSyncQuery(query)
|
|
234
|
+
if (result) {
|
|
235
|
+
// TODO: LID MAP this stuff (lid protocol will now return lid with devices)
|
|
236
|
+
const lidResults = result.list.filter(a => !!a.lid)
|
|
237
|
+
if (lidResults.length > 0) {
|
|
238
|
+
logger.trace('Storing LID maps from device call')
|
|
239
|
+
await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid, pn: a.id })))
|
|
240
|
+
// Force-refresh sessions for newly mapped LIDs to align identity addressing
|
|
241
|
+
try {
|
|
242
|
+
const lids = lidResults.map(a => a.lid)
|
|
243
|
+
if (lids.length) {
|
|
244
|
+
await assertSessions(lids, true)
|
|
245
|
+
}
|
|
246
|
+
} catch (e) {
|
|
247
|
+
logger.warn({ e, count: lidResults.length }, 'failed to assert sessions for newly mapped LIDs')
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const extracted = (0, Utils_1.extractDeviceJids)(
|
|
251
|
+
result?.list,
|
|
252
|
+
authState.creds.me.id,
|
|
253
|
+
authState.creds.me.lid,
|
|
254
|
+
ignoreZeroDevices
|
|
255
|
+
)
|
|
256
|
+
const deviceMap = {}
|
|
257
|
+
for (const item of extracted) {
|
|
258
|
+
deviceMap[item.user] = deviceMap[item.user] || []
|
|
259
|
+
deviceMap[item.user]?.push(item)
|
|
260
|
+
}
|
|
261
|
+
// Process each user's devices as a group for bulk LID migration
|
|
262
|
+
for (const [user, userDevices] of Object.entries(deviceMap)) {
|
|
263
|
+
const isLidUser = requestedLidUsers.has(user)
|
|
264
|
+
// Process all devices for this user
|
|
265
|
+
for (const item of userDevices) {
|
|
266
|
+
const finalJid = isLidUser
|
|
267
|
+
? (0, WABinary_1.jidEncode)(user, item.server, item.device)
|
|
268
|
+
: (0, WABinary_1.jidEncode)(item.user, item.server, item.device)
|
|
269
|
+
deviceResults.push({
|
|
270
|
+
...item,
|
|
271
|
+
jid: finalJid
|
|
272
|
+
})
|
|
273
|
+
logger.debug(
|
|
274
|
+
{
|
|
275
|
+
user: item.user,
|
|
276
|
+
device: item.device,
|
|
277
|
+
finalJid,
|
|
278
|
+
usedLid: isLidUser
|
|
279
|
+
},
|
|
280
|
+
'Processed device with LID priority'
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (userDevicesCache.mset) {
|
|
285
|
+
// if the cache supports mset, we can set all devices in one go
|
|
286
|
+
await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })))
|
|
287
|
+
} else {
|
|
288
|
+
for (const key in deviceMap) {
|
|
289
|
+
if (deviceMap[key]) await userDevicesCache.set(key, deviceMap[key])
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const userDeviceUpdates = {}
|
|
293
|
+
for (const [userId, devices] of Object.entries(deviceMap)) {
|
|
294
|
+
if (devices && devices.length > 0) {
|
|
295
|
+
userDeviceUpdates[userId] = devices.map(d => d.device?.toString() || '0')
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (Object.keys(userDeviceUpdates).length > 0) {
|
|
299
|
+
try {
|
|
300
|
+
await authState.keys.set({ 'device-list': userDeviceUpdates })
|
|
301
|
+
logger.debug(
|
|
302
|
+
{ userCount: Object.keys(userDeviceUpdates).length },
|
|
303
|
+
'stored user device lists for bulk migration'
|
|
304
|
+
)
|
|
305
|
+
} catch (error) {
|
|
306
|
+
logger.warn({ error }, 'failed to store user device lists')
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return deviceResults
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Update Member Label
|
|
314
|
+
*/
|
|
315
|
+
const updateMemberLabel = (jid, memberLabel) => {
|
|
316
|
+
return relayMessage(
|
|
317
|
+
jid,
|
|
318
|
+
{
|
|
319
|
+
protocolMessage: {
|
|
320
|
+
type: index_js_1.proto.Message.ProtocolMessage.Type.GROUP_MEMBER_LABEL_CHANGE,
|
|
321
|
+
memberLabel: {
|
|
322
|
+
label: memberLabel?.slice(0, 30),
|
|
323
|
+
labelTimestamp: (0, Utils_1.unixTimestampSeconds)()
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
additionalNodes: [
|
|
329
|
+
{
|
|
330
|
+
tag: 'meta',
|
|
331
|
+
attrs: {
|
|
332
|
+
tag_reason: 'user_update',
|
|
333
|
+
appdata: 'member_tag'
|
|
334
|
+
},
|
|
335
|
+
content: undefined
|
|
336
|
+
}
|
|
337
|
+
]
|
|
338
|
+
}
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
const assertSessions = async (jids, force) => {
|
|
342
|
+
let didFetchNewSession = false
|
|
343
|
+
const uniqueJids = [...new Set(jids)] // Deduplicate JIDs
|
|
344
|
+
const jidsRequiringFetch = []
|
|
345
|
+
logger.debug({ jids }, 'assertSessions call with jids')
|
|
346
|
+
// Check peerSessionsCache and validate sessions using libsignal loadSession
|
|
347
|
+
for (const jid of uniqueJids) {
|
|
348
|
+
const signalId = signalRepository.jidToSignalProtocolAddress(jid)
|
|
349
|
+
const cachedSession = peerSessionsCache.get(signalId)
|
|
350
|
+
if (cachedSession !== undefined) {
|
|
351
|
+
if (cachedSession && !force) {
|
|
352
|
+
continue // Session exists in cache
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
const sessionValidation = await signalRepository.validateSession(jid)
|
|
356
|
+
const hasSession = sessionValidation.exists
|
|
357
|
+
peerSessionsCache.set(signalId, hasSession)
|
|
358
|
+
if (hasSession && !force) {
|
|
359
|
+
continue
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
jidsRequiringFetch.push(jid)
|
|
363
|
+
}
|
|
364
|
+
if (jidsRequiringFetch.length) {
|
|
365
|
+
// LID if mapped, otherwise original; interop JIDs pass through as-is
|
|
366
|
+
const wireJids = [
|
|
367
|
+
...jidsRequiringFetch.filter(jid => !!(0, WABinary_1.isLidUser)(jid) || !!(0, WABinary_1.isHostedLidUser)(jid)),
|
|
368
|
+
...(
|
|
369
|
+
(await signalRepository.lidMapping.getLIDsForPNs(
|
|
370
|
+
jidsRequiringFetch.filter(jid => !!(0, WABinary_1.isPnUser)(jid) || !!(0, WABinary_1.isHostedPnUser)(jid))
|
|
371
|
+
)) || []
|
|
372
|
+
).map(a => a.lid),
|
|
373
|
+
...jidsRequiringFetch.filter(jid => (0, WABinary_1.isInteropUser)(jid))
|
|
374
|
+
]
|
|
375
|
+
const interopFetches = wireJids.filter(j => (0, WABinary_1.isInteropUser)(j))
|
|
376
|
+
if (interopFetches.length) {
|
|
377
|
+
logger.info({ interopFetches }, '[interop] fetching pre-key bundle for interop device(s)')
|
|
378
|
+
}
|
|
379
|
+
logger.debug({ jidsRequiringFetch, wireJids }, 'fetching sessions')
|
|
380
|
+
if (!wireJids.length) {
|
|
381
|
+
logger.debug({ jidsRequiringFetch }, 'assertSessions: no wire JIDs to fetch (all unsupported domain)')
|
|
382
|
+
return didFetchNewSession
|
|
383
|
+
}
|
|
384
|
+
const result = await query({
|
|
385
|
+
tag: 'iq',
|
|
386
|
+
attrs: {
|
|
387
|
+
xmlns: 'encrypt',
|
|
388
|
+
type: 'get',
|
|
389
|
+
to: WABinary_1.S_WHATSAPP_NET
|
|
390
|
+
},
|
|
391
|
+
content: [
|
|
392
|
+
{
|
|
393
|
+
tag: 'key',
|
|
394
|
+
attrs: {},
|
|
395
|
+
content: wireJids.map(jid => {
|
|
396
|
+
const attrs = { jid }
|
|
397
|
+
if (force) attrs.reason = 'identity'
|
|
398
|
+
return { tag: 'user', attrs }
|
|
399
|
+
})
|
|
400
|
+
}
|
|
401
|
+
]
|
|
402
|
+
})
|
|
403
|
+
if (interopFetches.length) {
|
|
404
|
+
logger.debug({ interopFetches }, '[interop] pre-key IQ response received')
|
|
405
|
+
}
|
|
406
|
+
await (0, Utils_1.parseAndInjectE2ESessions)(result, signalRepository)
|
|
407
|
+
didFetchNewSession = true
|
|
408
|
+
// Cache fetched sessions using wire JIDs
|
|
409
|
+
for (const wireJid of wireJids) {
|
|
410
|
+
const signalId = signalRepository.jidToSignalProtocolAddress(wireJid)
|
|
411
|
+
peerSessionsCache.set(signalId, true)
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return didFetchNewSession
|
|
415
|
+
}
|
|
416
|
+
const sendPeerDataOperationMessage = async pdoMessage => {
|
|
417
|
+
//TODO: for later, abstract the logic to send a Peer Message instead of just PDO - useful for App State Key Resync with phone
|
|
418
|
+
if (!authState.creds.me?.id) {
|
|
419
|
+
throw new boom_1.Boom('Not authenticated')
|
|
420
|
+
}
|
|
421
|
+
const protocolMessage = {
|
|
422
|
+
protocolMessage: {
|
|
423
|
+
peerDataOperationRequestMessage: pdoMessage,
|
|
424
|
+
type: index_js_1.proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
const meJid = (0, WABinary_1.jidNormalizedUser)(authState.creds.me.id)
|
|
428
|
+
const msgId = await relayMessage(meJid, protocolMessage, {
|
|
429
|
+
additionalAttributes: {
|
|
430
|
+
category: 'peer',
|
|
431
|
+
push_priority: 'high_force'
|
|
432
|
+
},
|
|
433
|
+
additionalNodes: [
|
|
434
|
+
{
|
|
435
|
+
tag: 'meta',
|
|
436
|
+
attrs: { appdata: 'default' }
|
|
437
|
+
}
|
|
438
|
+
]
|
|
439
|
+
})
|
|
440
|
+
return msgId
|
|
441
|
+
}
|
|
442
|
+
const createParticipantNodes = async (recipientJids, message, extraAttrs, dsmMessage) => {
|
|
443
|
+
if (!recipientJids.length) {
|
|
444
|
+
return { nodes: [], shouldIncludeDeviceIdentity: false }
|
|
445
|
+
}
|
|
446
|
+
const patched = await patchMessageBeforeSending(message, recipientJids)
|
|
447
|
+
const patchedMessages = Array.isArray(patched)
|
|
448
|
+
? patched
|
|
449
|
+
: recipientJids.map(jid => ({ recipientJid: jid, message: patched }))
|
|
450
|
+
let shouldIncludeDeviceIdentity = false
|
|
451
|
+
const meId = authState.creds.me.id
|
|
452
|
+
const meLid = authState.creds.me?.lid
|
|
453
|
+
const meLidUser = meLid ? (0, WABinary_1.jidDecode)(meLid)?.user : null
|
|
454
|
+
const encryptionPromises = patchedMessages.map(async ({ recipientJid: jid, message: patchedMessage }) => {
|
|
455
|
+
try {
|
|
456
|
+
if (!jid) return null
|
|
457
|
+
let msgToEncrypt = patchedMessage
|
|
458
|
+
if (dsmMessage) {
|
|
459
|
+
const { user: targetUser } = (0, WABinary_1.jidDecode)(jid)
|
|
460
|
+
const { user: ownPnUser } = (0, WABinary_1.jidDecode)(meId)
|
|
461
|
+
const ownLidUser = meLidUser
|
|
462
|
+
const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser)
|
|
463
|
+
const isExactSenderDevice = jid === meId || (meLid && jid === meLid)
|
|
464
|
+
if (isOwnUser && !isExactSenderDevice) {
|
|
465
|
+
msgToEncrypt = dsmMessage
|
|
466
|
+
logger.debug({ jid, targetUser }, 'Using DSM for own device')
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
const bytes = (0, Utils_1.encodeWAMessage)(msgToEncrypt)
|
|
470
|
+
const mutexKey = jid
|
|
471
|
+
const node = await encryptionMutex.mutex(mutexKey, async () => {
|
|
472
|
+
const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes })
|
|
473
|
+
if (type === 'pkmsg') {
|
|
474
|
+
shouldIncludeDeviceIdentity = true
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
tag: 'to',
|
|
478
|
+
attrs: { jid },
|
|
479
|
+
content: [
|
|
480
|
+
{
|
|
481
|
+
tag: 'enc',
|
|
482
|
+
attrs: { v: '2', type, ...(extraAttrs || {}) },
|
|
483
|
+
content: ciphertext
|
|
484
|
+
}
|
|
485
|
+
]
|
|
486
|
+
}
|
|
487
|
+
})
|
|
488
|
+
return node
|
|
489
|
+
} catch (err) {
|
|
490
|
+
logger.error({ jid, err }, 'Failed to encrypt for recipient')
|
|
491
|
+
return null
|
|
492
|
+
}
|
|
493
|
+
})
|
|
494
|
+
const nodes = (await Promise.all(encryptionPromises)).filter(node => node !== null)
|
|
495
|
+
if (recipientJids.length > 0 && nodes.length === 0) {
|
|
496
|
+
throw new boom_1.Boom('All encryptions failed', { statusCode: 500 })
|
|
497
|
+
}
|
|
498
|
+
return { nodes, shouldIncludeDeviceIdentity }
|
|
499
|
+
}
|
|
500
|
+
const relayMessage = async (
|
|
501
|
+
jid,
|
|
502
|
+
message,
|
|
503
|
+
{
|
|
504
|
+
messageId: msgId,
|
|
505
|
+
participant,
|
|
506
|
+
additionalAttributes,
|
|
507
|
+
additionalNodes,
|
|
508
|
+
useUserDevicesCache,
|
|
509
|
+
useCachedGroupMetadata,
|
|
510
|
+
statusJidList
|
|
511
|
+
}
|
|
512
|
+
) => {
|
|
513
|
+
const meId = authState.creds.me.id
|
|
514
|
+
const meLid = authState.creds.me?.lid
|
|
515
|
+
const isRetryResend = Boolean(participant?.jid)
|
|
516
|
+
let shouldIncludeDeviceIdentity = isRetryResend
|
|
517
|
+
const statusJid = 'status@broadcast'
|
|
518
|
+
const { user, server } = (0, WABinary_1.jidDecode)(jid)
|
|
519
|
+
const isGroup = server === 'g.us'
|
|
520
|
+
const isStatus = jid === statusJid
|
|
521
|
+
const isLid = server === 'lid'
|
|
522
|
+
const isNewsletter = server === 'newsletter'
|
|
523
|
+
const isInterop = (0, WABinary_1.isInteropUser)(jid)
|
|
524
|
+
const isGroupOrStatus = isGroup || isStatus
|
|
525
|
+
const finalJid = jid
|
|
526
|
+
const iosBros = config.browser[0] === "iOS" || config.browser[1] === "Safari";
|
|
527
|
+
msgId = iosBros ? Utils_1.generateIOSMessageID() : msgId ?? (0, Utils_1.generateMessageIDV2)(meId)
|
|
528
|
+
useUserDevicesCache = useUserDevicesCache !== false
|
|
529
|
+
useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus
|
|
530
|
+
const participants = []
|
|
531
|
+
const destinationJid = !isStatus ? finalJid : statusJid
|
|
532
|
+
const binaryNodeContent = []
|
|
533
|
+
const devices = []
|
|
534
|
+
let reportingMessage
|
|
535
|
+
const meMsg = {
|
|
536
|
+
deviceSentMessage: {
|
|
537
|
+
destinationJid,
|
|
538
|
+
message
|
|
539
|
+
},
|
|
540
|
+
messageContextInfo: message.messageContextInfo
|
|
541
|
+
}
|
|
542
|
+
const extraAttrs = {}
|
|
543
|
+
|
|
544
|
+
const regexGroupOld = /^(\d{1,15})-(\d+)@g\.us$/
|
|
545
|
+
|
|
546
|
+
const messages = Utils_1.normalizeMessageContent(message)
|
|
547
|
+
|
|
548
|
+
const buttonType = getButtonType(messages)
|
|
549
|
+
const pollMessage = messages.pollCreationMessage || messages.pollCreationMessageV2 || messages.pollCreationMessageV3
|
|
550
|
+
await authState.keys.transaction(async () => {
|
|
551
|
+
const mediaType = getMediaType(message)
|
|
552
|
+
if (mediaType) {
|
|
553
|
+
extraAttrs['mediatype'] = mediaType
|
|
554
|
+
}
|
|
555
|
+
if (isNewsletter) {
|
|
556
|
+
const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message
|
|
557
|
+
const bytes = (0, Utils_1.encodeNewsletterMessage)(patched)
|
|
558
|
+
binaryNodeContent.push({
|
|
559
|
+
tag: 'plaintext',
|
|
560
|
+
attrs: {},
|
|
561
|
+
content: bytes
|
|
562
|
+
})
|
|
563
|
+
const stanza = {
|
|
564
|
+
tag: 'message',
|
|
565
|
+
attrs: {
|
|
566
|
+
to: jid,
|
|
567
|
+
id: msgId,
|
|
568
|
+
type: getTypeMessage(message),
|
|
569
|
+
...(additionalAttributes || {})
|
|
570
|
+
},
|
|
571
|
+
content: binaryNodeContent
|
|
572
|
+
}
|
|
573
|
+
logger.debug({ msgId }, `sending newsletter message to ${jid}`)
|
|
574
|
+
await sendNode(stanza)
|
|
575
|
+
return
|
|
576
|
+
}
|
|
577
|
+
if (
|
|
578
|
+
(0, Utils_1.normalizeMessageContent)(message)?.pinInChatMessage ||
|
|
579
|
+
(0, Utils_1.normalizeMessageContent)(message)?.reactionMessage
|
|
580
|
+
) {
|
|
581
|
+
extraAttrs['decrypt-fail'] = 'hide' // todo: expand for reactions and other types
|
|
582
|
+
}
|
|
583
|
+
if (isGroupOrStatus && !isRetryResend) {
|
|
584
|
+
const [groupData, senderKeyMap] = await Promise.all([
|
|
585
|
+
(async () => {
|
|
586
|
+
let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined
|
|
587
|
+
const looksStale =
|
|
588
|
+
groupData &&
|
|
589
|
+
typeof groupData.size === 'number' &&
|
|
590
|
+
Array.isArray(groupData.participants) &&
|
|
591
|
+
groupData.participants.length < groupData.size
|
|
592
|
+
if (groupData && Array.isArray(groupData?.participants) && !looksStale) {
|
|
593
|
+
logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata')
|
|
594
|
+
} else if (!isStatus) {
|
|
595
|
+
if (looksStale) {
|
|
596
|
+
logger.debug(
|
|
597
|
+
{ jid, cached: groupData.participants.length, expected: groupData.size },
|
|
598
|
+
'cached group metadata looks stale, re-fetching'
|
|
599
|
+
)
|
|
600
|
+
}
|
|
601
|
+
groupData = await groupMetadata(jid) // TODO: start storing group participant list + addr mode in Signal & stop relying on this
|
|
602
|
+
}
|
|
603
|
+
return groupData
|
|
604
|
+
})(),
|
|
605
|
+
(async () => {
|
|
606
|
+
if (!participant && !isStatus) {
|
|
607
|
+
// what if sender memory is less accurate than the cached metadata
|
|
608
|
+
// on participant change in group, we should do sender memory manipulation
|
|
609
|
+
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?
|
|
610
|
+
return result[jid] || {}
|
|
611
|
+
}
|
|
612
|
+
return {}
|
|
613
|
+
})()
|
|
614
|
+
])
|
|
615
|
+
const participantsList = groupData ? groupData.participants.map(p => p.id) : []
|
|
616
|
+
if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) {
|
|
617
|
+
additionalAttributes = {
|
|
618
|
+
...additionalAttributes,
|
|
619
|
+
expiration: groupData.ephemeralDuration.toString()
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
if (isStatus && statusJidList) {
|
|
623
|
+
participantsList.push(...statusJidList)
|
|
624
|
+
}
|
|
625
|
+
const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false)
|
|
626
|
+
devices.push(...additionalDevices)
|
|
627
|
+
if (isGroup) {
|
|
628
|
+
additionalAttributes = {
|
|
629
|
+
...additionalAttributes,
|
|
630
|
+
addressing_mode: groupData?.addressingMode || 'lid'
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (message?.groupStatusMessageV2 && !message?.messageContextInfo?.messageSecret) {
|
|
634
|
+
const { randomBytes } = require('crypto')
|
|
635
|
+
message = {
|
|
636
|
+
...message,
|
|
637
|
+
messageContextInfo: {
|
|
638
|
+
...(message.messageContextInfo || {}),
|
|
639
|
+
messageSecret: randomBytes(32)
|
|
640
|
+
},
|
|
641
|
+
groupStatusMessageV2: {
|
|
642
|
+
...message.groupStatusMessageV2,
|
|
643
|
+
message: {
|
|
644
|
+
...(message.groupStatusMessageV2.message || {}),
|
|
645
|
+
messageContextInfo: {
|
|
646
|
+
...(message.groupStatusMessageV2.message?.messageContextInfo || {}),
|
|
647
|
+
messageSecret: message.messageContextInfo?.messageSecret || randomBytes(32)
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (message.listMessage) {
|
|
654
|
+
const list = message.listMessage
|
|
655
|
+
const interactiveMessage = {
|
|
656
|
+
nativeFlowMessage: {
|
|
657
|
+
buttons: [
|
|
658
|
+
{
|
|
659
|
+
name: 'single_select',
|
|
660
|
+
buttonParamsJson: JSON.stringify({
|
|
661
|
+
title: list.buttonText || 'Select',
|
|
662
|
+
sections: (list.sections || []).map(section => ({
|
|
663
|
+
title: section.title || '',
|
|
664
|
+
highlight_label: '',
|
|
665
|
+
rows: (section.rows || []).map(row => ({
|
|
666
|
+
header: '',
|
|
667
|
+
title: row.title || '',
|
|
668
|
+
description: row.description || '',
|
|
669
|
+
id: row.rowId || row.id || ''
|
|
670
|
+
}))
|
|
671
|
+
}))
|
|
672
|
+
})
|
|
673
|
+
}
|
|
674
|
+
],
|
|
675
|
+
messageParamsJson: '',
|
|
676
|
+
messageVersion: 1
|
|
677
|
+
},
|
|
678
|
+
body: { text: list.description || '' },
|
|
679
|
+
footer: list.footerText ? { text: list.footerText } : undefined,
|
|
680
|
+
header: list.title ? { title: list.title, hasMediaAttachment: false, subtitle: '' } : undefined,
|
|
681
|
+
contextInfo: list.contextInfo
|
|
682
|
+
}
|
|
683
|
+
message = { interactiveMessage }
|
|
684
|
+
} else if (message.buttonsMessage) {
|
|
685
|
+
const bMsg = message.buttonsMessage
|
|
686
|
+
const buttons = (bMsg.buttons || []).map(btn => ({
|
|
687
|
+
name: 'quick_reply',
|
|
688
|
+
buttonParamsJson: JSON.stringify({
|
|
689
|
+
display_text: btn.buttonText?.displayText || btn.buttonText || '',
|
|
690
|
+
id: btn.buttonId || btn.buttonText?.displayText || ''
|
|
691
|
+
})
|
|
692
|
+
}))
|
|
693
|
+
const interactiveMessage = {
|
|
694
|
+
nativeFlowMessage: {
|
|
695
|
+
buttons,
|
|
696
|
+
messageParamsJson: '',
|
|
697
|
+
messageVersion: 1
|
|
698
|
+
},
|
|
699
|
+
body: { text: bMsg.contentText || bMsg.text || '' },
|
|
700
|
+
footer: bMsg.footerText ? { text: bMsg.footerText } : undefined,
|
|
701
|
+
header: bMsg.text
|
|
702
|
+
? { title: bMsg.text, hasMediaAttachment: false, subtitle: '' }
|
|
703
|
+
: bMsg.imageMessage || bMsg.videoMessage || bMsg.documentMessage
|
|
704
|
+
? {
|
|
705
|
+
hasMediaAttachment: true,
|
|
706
|
+
...(bMsg.imageMessage ? { imageMessage: bMsg.imageMessage } : {}),
|
|
707
|
+
...(bMsg.videoMessage ? { videoMessage: bMsg.videoMessage } : {})
|
|
708
|
+
}
|
|
709
|
+
: undefined,
|
|
710
|
+
contextInfo: bMsg.contextInfo
|
|
711
|
+
}
|
|
712
|
+
message = { interactiveMessage }
|
|
713
|
+
} else if (message.templateMessage) {
|
|
714
|
+
const tmpl = message.templateMessage.hydratedTemplate || message.templateMessage.fourRowTemplate
|
|
715
|
+
if (tmpl) {
|
|
716
|
+
const hydratedButtons = tmpl.hydratedButtons || []
|
|
717
|
+
const buttons = hydratedButtons
|
|
718
|
+
.map(hBtn => {
|
|
719
|
+
if (hBtn.quickReplyButton) {
|
|
720
|
+
return {
|
|
721
|
+
name: 'quick_reply',
|
|
722
|
+
buttonParamsJson: JSON.stringify({
|
|
723
|
+
display_text: hBtn.quickReplyButton.displayText || '',
|
|
724
|
+
id: hBtn.quickReplyButton.id || hBtn.quickReplyButton.displayText || ''
|
|
725
|
+
})
|
|
726
|
+
}
|
|
727
|
+
} else if (hBtn.urlButton) {
|
|
728
|
+
return {
|
|
729
|
+
name: 'cta_url',
|
|
730
|
+
buttonParamsJson: JSON.stringify({
|
|
731
|
+
display_text: hBtn.urlButton.displayText || '',
|
|
732
|
+
url: hBtn.urlButton.url || '',
|
|
733
|
+
merchant_url: hBtn.urlButton.url || ''
|
|
734
|
+
})
|
|
735
|
+
}
|
|
736
|
+
} else if (hBtn.callButton) {
|
|
737
|
+
return {
|
|
738
|
+
name: 'cta_call',
|
|
739
|
+
buttonParamsJson: JSON.stringify({
|
|
740
|
+
display_text: hBtn.callButton.displayText || '',
|
|
741
|
+
phone_number: hBtn.callButton.phoneNumber || ''
|
|
742
|
+
})
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return null
|
|
746
|
+
})
|
|
747
|
+
.filter(Boolean)
|
|
748
|
+
const interactiveMessage = {
|
|
749
|
+
nativeFlowMessage: {
|
|
750
|
+
buttons,
|
|
751
|
+
messageParamsJson: '',
|
|
752
|
+
messageVersion: 1
|
|
753
|
+
},
|
|
754
|
+
body: { text: tmpl.hydratedContentText || tmpl.contentText || '' },
|
|
755
|
+
footer: tmpl.hydratedFooterText ? { text: tmpl.hydratedFooterText } : undefined,
|
|
756
|
+
header: tmpl.hydratedTitleText
|
|
757
|
+
? { title: tmpl.hydratedTitleText, hasMediaAttachment: false, subtitle: '' }
|
|
758
|
+
: tmpl.imageMessage || tmpl.videoMessage || tmpl.documentMessage
|
|
759
|
+
? {
|
|
760
|
+
hasMediaAttachment: true,
|
|
761
|
+
...(tmpl.imageMessage ? { imageMessage: tmpl.imageMessage } : {}),
|
|
762
|
+
...(tmpl.videoMessage ? { videoMessage: tmpl.videoMessage } : {})
|
|
763
|
+
}
|
|
764
|
+
: undefined,
|
|
765
|
+
contextInfo: tmpl.contextInfo
|
|
766
|
+
}
|
|
767
|
+
message = { interactiveMessage }
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
const patched = await patchMessageBeforeSending(message)
|
|
771
|
+
if (Array.isArray(patched)) {
|
|
772
|
+
throw new boom_1.Boom('Per-jid patching is not supported in groups')
|
|
773
|
+
}
|
|
774
|
+
const bytes = (0, Utils_1.encodeWAMessage)(patched)
|
|
775
|
+
reportingMessage = patched
|
|
776
|
+
const groupAddressingMode = additionalAttributes?.['addressing_mode'] || groupData?.addressingMode || 'lid'
|
|
777
|
+
const groupSenderIdentity = groupAddressingMode === 'lid' && meLid ? meLid : meId
|
|
778
|
+
const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
|
|
779
|
+
group: destinationJid,
|
|
780
|
+
data: bytes,
|
|
781
|
+
meId: groupSenderIdentity
|
|
782
|
+
})
|
|
783
|
+
const senderKeyRecipients = []
|
|
784
|
+
for (const device of devices) {
|
|
785
|
+
const deviceJid = device.jid
|
|
786
|
+
const hasKey = !!senderKeyMap[deviceJid]
|
|
787
|
+
if (
|
|
788
|
+
(!hasKey || !!participant) &&
|
|
789
|
+
!(0, WABinary_1.isHostedLidUser)(deviceJid) &&
|
|
790
|
+
!(0, WABinary_1.isHostedPnUser)(deviceJid) &&
|
|
791
|
+
device.device !== 99
|
|
792
|
+
) {
|
|
793
|
+
//todo: revamp all this logic
|
|
794
|
+
// 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
|
|
795
|
+
senderKeyRecipients.push(deviceJid)
|
|
796
|
+
senderKeyMap[deviceJid] = true
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
if (senderKeyRecipients.length) {
|
|
800
|
+
logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending new sender key')
|
|
801
|
+
const senderKeyMsg = {
|
|
802
|
+
senderKeyDistributionMessage: {
|
|
803
|
+
axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage,
|
|
804
|
+
groupId: destinationJid
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
const senderKeySessionTargets = senderKeyRecipients
|
|
808
|
+
await assertSessions(senderKeySessionTargets)
|
|
809
|
+
const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs)
|
|
810
|
+
shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity
|
|
811
|
+
participants.push(...result.nodes)
|
|
812
|
+
}
|
|
813
|
+
binaryNodeContent.push({
|
|
814
|
+
tag: 'enc',
|
|
815
|
+
attrs: { v: '2', type: 'skmsg', ...extraAttrs },
|
|
816
|
+
content: ciphertext
|
|
817
|
+
})
|
|
818
|
+
await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } })
|
|
819
|
+
} else {
|
|
820
|
+
// ADDRESSING CONSISTENCY: Match own identity to conversation context
|
|
821
|
+
// TODO: investigate if this is true
|
|
822
|
+
let ownId = meId
|
|
823
|
+
if (isLid && meLid) {
|
|
824
|
+
ownId = meLid
|
|
825
|
+
logger.debug({ to: jid, ownId }, 'Using LID identity for @lid conversation')
|
|
826
|
+
} else {
|
|
827
|
+
logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation')
|
|
828
|
+
}
|
|
829
|
+
const { user: ownUser } = (0, WABinary_1.jidDecode)(ownId)
|
|
830
|
+
if (!participant) {
|
|
831
|
+
const patchedForReporting = await patchMessageBeforeSending(message, [jid])
|
|
832
|
+
reportingMessage = Array.isArray(patchedForReporting)
|
|
833
|
+
? patchedForReporting.find(item => item.recipientJid === jid) || patchedForReporting[0]
|
|
834
|
+
: patchedForReporting
|
|
835
|
+
}
|
|
836
|
+
if (!isRetryResend) {
|
|
837
|
+
const targetUserServer = isLid ? 'lid' : isInterop ? 'interop' : 's.whatsapp.net'
|
|
838
|
+
devices.push({
|
|
839
|
+
user,
|
|
840
|
+
device: 0,
|
|
841
|
+
jid: (0, WABinary_1.jidEncode)(user, targetUserServer, 0) // rajeh, todo: this entire logic is convoluted and weird.
|
|
842
|
+
})
|
|
843
|
+
// Interop contacts don't receive a copy to own devices — native WA sends only one flat <enc>
|
|
844
|
+
if (user !== ownUser && !isInterop) {
|
|
845
|
+
const ownUserServer = isLid ? 'lid' : 's.whatsapp.net'
|
|
846
|
+
const ownUserForAddressing =
|
|
847
|
+
isLid && meLid ? (0, WABinary_1.jidDecode)(meLid).user : (0, WABinary_1.jidDecode)(meId).user
|
|
848
|
+
devices.push({
|
|
849
|
+
user: ownUserForAddressing,
|
|
850
|
+
device: 0,
|
|
851
|
+
jid: (0, WABinary_1.jidEncode)(ownUserForAddressing, ownUserServer, 0)
|
|
852
|
+
})
|
|
853
|
+
}
|
|
854
|
+
if (additionalAttributes?.['category'] !== 'peer' && !isInterop) {
|
|
855
|
+
// Clear placeholders and enumerate actual devices
|
|
856
|
+
devices.length = 0
|
|
857
|
+
// Use conversation-appropriate sender identity
|
|
858
|
+
const senderIdentity =
|
|
859
|
+
isLid && meLid
|
|
860
|
+
? (0, WABinary_1.jidEncode)((0, WABinary_1.jidDecode)(meLid)?.user, 'lid', undefined)
|
|
861
|
+
: (0, WABinary_1.jidEncode)((0, WABinary_1.jidDecode)(meId)?.user, 's.whatsapp.net', undefined)
|
|
862
|
+
// Enumerate devices for sender and target with consistent addressing
|
|
863
|
+
const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false)
|
|
864
|
+
devices.push(...sessionDevices)
|
|
865
|
+
logger.debug(
|
|
866
|
+
{
|
|
867
|
+
deviceCount: devices.length,
|
|
868
|
+
devices: devices.map(d => `${d.user}:${d.device}@${(0, WABinary_1.jidDecode)(d.jid)?.server}`)
|
|
869
|
+
},
|
|
870
|
+
'Device enumeration complete with unified addressing'
|
|
871
|
+
)
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
const allRecipients = []
|
|
875
|
+
const meRecipients = []
|
|
876
|
+
const otherRecipients = []
|
|
877
|
+
const { user: mePnUser } = (0, WABinary_1.jidDecode)(meId)
|
|
878
|
+
const { user: meLidUser } = meLid ? (0, WABinary_1.jidDecode)(meLid) : { user: null }
|
|
879
|
+
for (const { user, jid } of devices) {
|
|
880
|
+
const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
|
|
881
|
+
if (isExactSenderDevice) continue;
|
|
882
|
+
const isMe = user === mePnUser || user === meLidUser;
|
|
883
|
+
let ptcp = false;
|
|
884
|
+
if (participant) {
|
|
885
|
+
if (!(!isMe)) ptcp = true;
|
|
886
|
+
}
|
|
887
|
+
if (!ptcp) {
|
|
888
|
+
if (isMe) meRecipients.push(jid);
|
|
889
|
+
else otherRecipients.push(jid);
|
|
890
|
+
allRecipients.push(jid);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
await assertSessions(allRecipients)
|
|
894
|
+
const [
|
|
895
|
+
{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 },
|
|
896
|
+
{ nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }
|
|
897
|
+
] = await Promise.all([
|
|
898
|
+
// For own devices: use DSM if available (1:1 chats only)
|
|
899
|
+
createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
|
|
900
|
+
createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)
|
|
901
|
+
])
|
|
902
|
+
participants.push(...meNodes)
|
|
903
|
+
participants.push(...otherNodes)
|
|
904
|
+
if (meRecipients.length > 0 || otherRecipients.length > 0) {
|
|
905
|
+
extraAttrs['phash'] = (0, Utils_1.generateParticipantHashV2)([...meRecipients, ...otherRecipients])
|
|
906
|
+
}
|
|
907
|
+
shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2
|
|
908
|
+
}
|
|
909
|
+
if (participants.length) {
|
|
910
|
+
if (additionalAttributes?.['category'] === 'peer') {
|
|
911
|
+
const peerNode = participants[0]?.content?.[0]
|
|
912
|
+
if (peerNode) {
|
|
913
|
+
binaryNodeContent.push(peerNode) // push only enc
|
|
914
|
+
}
|
|
915
|
+
} else if (isInterop) {
|
|
916
|
+
// Flat <enc> directly on <message> — no <participants> wrapper (native WA behavior).
|
|
917
|
+
// Find the enc for the actual interop recipient, not own-device DSM copy.
|
|
918
|
+
const recipientNode = participants.find(p => (0, WABinary_1.isInteropUser)(p?.attrs?.jid))
|
|
919
|
+
const encNode = (recipientNode ?? participants[0])?.content?.[0]
|
|
920
|
+
if (encNode) {
|
|
921
|
+
binaryNodeContent.push(encNode)
|
|
922
|
+
}
|
|
923
|
+
} else {
|
|
924
|
+
binaryNodeContent.push({
|
|
925
|
+
tag: 'participants',
|
|
926
|
+
attrs: {},
|
|
927
|
+
content: participants
|
|
928
|
+
})
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
const stanza = {
|
|
932
|
+
tag: 'message',
|
|
933
|
+
attrs: {
|
|
934
|
+
id: msgId,
|
|
935
|
+
to: destinationJid,
|
|
936
|
+
type: getTypeMessage(message),
|
|
937
|
+
...(additionalAttributes || {})
|
|
938
|
+
},
|
|
939
|
+
content: binaryNodeContent
|
|
940
|
+
}
|
|
941
|
+
if (shouldIncludeDeviceIdentity) {
|
|
942
|
+
stanza.content.push({
|
|
943
|
+
tag: 'device-identity',
|
|
944
|
+
attrs: {},
|
|
945
|
+
content: (0, Utils_1.encodeSignedDeviceIdentity)(authState.creds.account, true)
|
|
946
|
+
})
|
|
947
|
+
logger.debug({ jid }, 'adding device identity')
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
if (isGroup && regexGroupOld.test(jid) && !message.reactionMessage) {
|
|
951
|
+
stanza.content.push({
|
|
952
|
+
tag: 'multicast',
|
|
953
|
+
attrs: {}
|
|
954
|
+
})
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
if (pollMessage || messages.eventMessage) {
|
|
958
|
+
stanza.content.push({
|
|
959
|
+
tag: 'meta',
|
|
960
|
+
attrs: messages.eventMessage
|
|
961
|
+
? {
|
|
962
|
+
event_type: 'creation'
|
|
963
|
+
}
|
|
964
|
+
: isNewsletter
|
|
965
|
+
? {
|
|
966
|
+
polltype: 'creation',
|
|
967
|
+
contenttype: pollMessage?.pollContentType === 2 ? 'image' : 'text'
|
|
968
|
+
}
|
|
969
|
+
: {
|
|
970
|
+
polltype: 'creation'
|
|
971
|
+
}
|
|
972
|
+
})
|
|
973
|
+
}
|
|
974
|
+
if (
|
|
975
|
+
!isNewsletter &&
|
|
976
|
+
!isRetryResend &&
|
|
977
|
+
reportingMessage?.messageContextInfo?.messageSecret &&
|
|
978
|
+
(0, reporting_utils_1.shouldIncludeReportingToken)(reportingMessage)
|
|
979
|
+
) {
|
|
980
|
+
try {
|
|
981
|
+
const encoded = (0, Utils_1.encodeWAMessage)(reportingMessage)
|
|
982
|
+
const reportingKey = {
|
|
983
|
+
id: msgId,
|
|
984
|
+
fromMe: true,
|
|
985
|
+
remoteJid: destinationJid,
|
|
986
|
+
participant: participant?.jid
|
|
987
|
+
}
|
|
988
|
+
const reportingNode = await (0, reporting_utils_1.getMessageReportingToken)(
|
|
989
|
+
encoded,
|
|
990
|
+
reportingMessage,
|
|
991
|
+
reportingKey
|
|
992
|
+
)
|
|
993
|
+
if (reportingNode) {
|
|
994
|
+
stanza.content.push(reportingNode)
|
|
995
|
+
logger.trace({ jid }, 'added reporting token to message')
|
|
996
|
+
}
|
|
997
|
+
} catch (error) {
|
|
998
|
+
logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token')
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
let didPushAdditional = false
|
|
1003
|
+
if (!isNewsletter && buttonType) {
|
|
1004
|
+
const buttonsNode = getButtonArgs(messages)
|
|
1005
|
+
const filteredButtons = WABinary_1.getBinaryFilteredButtons(additionalNodes ? additionalNodes : [])
|
|
1006
|
+
|
|
1007
|
+
if (filteredButtons) {
|
|
1008
|
+
stanza.content.push(...additionalNodes)
|
|
1009
|
+
didPushAdditional = true
|
|
1010
|
+
} else {
|
|
1011
|
+
stanza.content.push(buttonsNode)
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (!config.aiLabel && (0, WABinary_1.isPnUser)(destinationJid)) {
|
|
1015
|
+
const alreadyHasBizBot =
|
|
1016
|
+
WABinary_1.getBinaryFilteredBizBot(additionalNodes || []) ||
|
|
1017
|
+
WABinary_1.getBinaryFilteredBizBot(stanza.content)
|
|
1018
|
+
if (!alreadyHasBizBot) {
|
|
1019
|
+
stanza.content.push({ tag: 'bot', attrs: { biz_bot: '1' } })
|
|
1020
|
+
}
|
|
1021
|
+
} else if (config.aiLabel && !isGroup && !isStatus && !isNewsletter) {
|
|
1022
|
+
const existingBizBot = WABinary_1.getBinaryFilteredBizBot(additionalNodes || [])
|
|
1023
|
+
if (!existingBizBot) {
|
|
1024
|
+
stanza.content.push({ tag: 'bot', attrs: { biz_bot: '1' } })
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
// WA Web never attaches tctoken to peer (AppStateSync) messages — server rejects with 479
|
|
1028
|
+
const isPeerMessage = additionalAttributes?.['category'] === 'peer'
|
|
1029
|
+
const is1on1Send = !isGroup && !isRetryResend && !isStatus && !isNewsletter && !isPeerMessage
|
|
1030
|
+
// Resolve destination to LID for tctoken storage — matches Signal session key pattern
|
|
1031
|
+
const tcTokenJid = is1on1Send
|
|
1032
|
+
? await (0, tc_token_utils_1.resolveTcTokenJid)(destinationJid, getLIDForPN)
|
|
1033
|
+
: destinationJid
|
|
1034
|
+
const contactTcTokenData = is1on1Send ? await authState.keys.get('tctoken', [tcTokenJid]) : {}
|
|
1035
|
+
const existingTokenEntry = contactTcTokenData[tcTokenJid]
|
|
1036
|
+
let tcTokenBuffer = existingTokenEntry?.token
|
|
1037
|
+
// Treat expired tokens the same as missing — clear from cache
|
|
1038
|
+
if (tcTokenBuffer?.length && (0, tc_token_utils_1.isTcTokenExpired)(existingTokenEntry?.timestamp)) {
|
|
1039
|
+
logger.debug({ jid: destinationJid, timestamp: existingTokenEntry?.timestamp }, 'tctoken expired, clearing')
|
|
1040
|
+
tcTokenBuffer = undefined
|
|
1041
|
+
const cleared =
|
|
1042
|
+
existingTokenEntry?.senderTimestamp !== undefined
|
|
1043
|
+
? { token: Buffer.alloc(0), senderTimestamp: existingTokenEntry.senderTimestamp }
|
|
1044
|
+
: null
|
|
1045
|
+
try {
|
|
1046
|
+
await authState.keys.set({ tctoken: { [tcTokenJid]: cleared } })
|
|
1047
|
+
} catch (err) {
|
|
1048
|
+
logger.debug({ jid: destinationJid, err: err?.message }, 'failed to persist tctoken expiry cleanup')
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
if (tcTokenBuffer?.length && sock.serverProps.privacyTokenOn1to1) {
|
|
1052
|
+
stanza.content.push({
|
|
1053
|
+
tag: 'tctoken',
|
|
1054
|
+
attrs: {},
|
|
1055
|
+
content: tcTokenBuffer
|
|
1056
|
+
})
|
|
1057
|
+
}
|
|
1058
|
+
if (additionalNodes && additionalNodes.length > 0 && !didPushAdditional) {
|
|
1059
|
+
stanza.content.push(...additionalNodes)
|
|
1060
|
+
}
|
|
1061
|
+
logger.debug({ msgId }, `sending message to ${participants.length} devices`)
|
|
1062
|
+
await sendNode(stanza)
|
|
1063
|
+
// Register messageSecret immediately at send time so msmsg replies can be
|
|
1064
|
+
// decrypted even if they arrive before our own message echo comes back.
|
|
1065
|
+
if (message.messageContextInfo?.messageSecret) {
|
|
1066
|
+
setBotMessageSecret(msgId, message.messageContextInfo.messageSecret, destinationJid)
|
|
1067
|
+
}
|
|
1068
|
+
// Fire-and-forget: issue our token to the contact AFTER message send.
|
|
1069
|
+
const isProtocolMsg = !!(0, Utils_1.normalizeMessageContent)(message)?.protocolMessage
|
|
1070
|
+
const isBotOrPSA =
|
|
1071
|
+
destinationJid === WABinary_1.PSA_WID ||
|
|
1072
|
+
(0, WABinary_1.isJidBot)(destinationJid) ||
|
|
1073
|
+
(0, WABinary_1.isJidMetaAI)(destinationJid)
|
|
1074
|
+
if (
|
|
1075
|
+
is1on1Send &&
|
|
1076
|
+
!isProtocolMsg &&
|
|
1077
|
+
!isBotOrPSA &&
|
|
1078
|
+
(0, tc_token_utils_1.shouldSendNewTcToken)(existingTokenEntry?.senderTimestamp) &&
|
|
1079
|
+
!inFlightTcTokenIssuance.has(tcTokenJid)
|
|
1080
|
+
) {
|
|
1081
|
+
inFlightTcTokenIssuance.add(tcTokenJid)
|
|
1082
|
+
const issueTimestamp = (0, Utils_1.unixTimestampSeconds)()
|
|
1083
|
+
const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping)
|
|
1084
|
+
;(0, tc_token_utils_1.resolveIssuanceJid)(
|
|
1085
|
+
destinationJid,
|
|
1086
|
+
sock.serverProps.lidTrustedTokenIssueToLid,
|
|
1087
|
+
getLIDForPN,
|
|
1088
|
+
getPNForLID
|
|
1089
|
+
)
|
|
1090
|
+
.then(issueJid => issuePrivacyTokens([issueJid], issueTimestamp))
|
|
1091
|
+
.then(async result => {
|
|
1092
|
+
await (0, tc_token_utils_1.storeTcTokensFromIqResult)({
|
|
1093
|
+
result,
|
|
1094
|
+
fallbackJid: tcTokenJid,
|
|
1095
|
+
keys: authState.keys,
|
|
1096
|
+
getLIDForPN
|
|
1097
|
+
})
|
|
1098
|
+
const currentData = await authState.keys.get('tctoken', [tcTokenJid])
|
|
1099
|
+
const currentEntry = currentData[tcTokenJid]
|
|
1100
|
+
const indexWrite = await (0, tc_token_utils_1.buildMergedTcTokenIndexWrite)(authState.keys, [tcTokenJid])
|
|
1101
|
+
await authState.keys.set({
|
|
1102
|
+
tctoken: {
|
|
1103
|
+
[tcTokenJid]: {
|
|
1104
|
+
token: Buffer.alloc(0),
|
|
1105
|
+
...currentEntry,
|
|
1106
|
+
senderTimestamp: issueTimestamp
|
|
1107
|
+
},
|
|
1108
|
+
...indexWrite
|
|
1109
|
+
}
|
|
1110
|
+
})
|
|
1111
|
+
})
|
|
1112
|
+
.catch(err => {
|
|
1113
|
+
logger.debug({ jid: destinationJid, err: err?.message }, 'fire-and-forget tctoken issuance failed')
|
|
1114
|
+
})
|
|
1115
|
+
.finally(() => {
|
|
1116
|
+
inFlightTcTokenIssuance.delete(tcTokenJid)
|
|
1117
|
+
})
|
|
1118
|
+
}
|
|
1119
|
+
// Add message to retry cache if enabled
|
|
1120
|
+
if (messageRetryManager && !participant) {
|
|
1121
|
+
messageRetryManager.addRecentMessage(destinationJid, msgId, message)
|
|
1122
|
+
}
|
|
1123
|
+
if (isInterop && !isRetryResend) {
|
|
1124
|
+
await trustInteropContact(destinationJid).catch(err => {
|
|
1125
|
+
logger.debug({ err, jid: destinationJid }, 'failed to trust interop contact')
|
|
1126
|
+
})
|
|
1127
|
+
}
|
|
1128
|
+
}, meId)
|
|
1129
|
+
return msgId
|
|
1130
|
+
}
|
|
1131
|
+
const getTypeMessage = msg => {
|
|
1132
|
+
const message = Utils_1.normalizeMessageContent(msg)
|
|
1133
|
+
if (message.pollCreationMessage || message.pollCreationMessageV2 || message.pollCreationMessageV3) {
|
|
1134
|
+
return 'poll'
|
|
1135
|
+
} else if (message.reactionMessage) {
|
|
1136
|
+
return 'reaction'
|
|
1137
|
+
} else if (message.eventMessage) {
|
|
1138
|
+
return 'event'
|
|
1139
|
+
} else if (getMediaType(message)) {
|
|
1140
|
+
return 'media'
|
|
1141
|
+
} else {
|
|
1142
|
+
return 'text'
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
const getMediaType = message => {
|
|
1147
|
+
if (message.imageMessage) {
|
|
1148
|
+
return 'image'
|
|
1149
|
+
} else if (message.stickerMessage) {
|
|
1150
|
+
return message.stickerMessage.isLottie
|
|
1151
|
+
? '1p_sticker'
|
|
1152
|
+
: message.stickerMessage.isAvatar
|
|
1153
|
+
? 'avatar_sticker'
|
|
1154
|
+
: 'sticker'
|
|
1155
|
+
} else if (message.videoMessage) {
|
|
1156
|
+
return message.videoMessage.gifPlayback ? 'gif' : 'video'
|
|
1157
|
+
} else if (message.audioMessage) {
|
|
1158
|
+
return message.audioMessage.ptt ? 'ptt' : 'audio'
|
|
1159
|
+
} else if (message.ptvMessage) {
|
|
1160
|
+
return 'ptv'
|
|
1161
|
+
} else if (message.albumMessage) {
|
|
1162
|
+
return 'collection'
|
|
1163
|
+
} else if (message.contactMessage) {
|
|
1164
|
+
return 'vcard'
|
|
1165
|
+
} else if (message.documentMessage) {
|
|
1166
|
+
return 'document'
|
|
1167
|
+
} else if (message.stickerPackMessage) {
|
|
1168
|
+
return 'sticker_pack'
|
|
1169
|
+
} else if (message.contactsArrayMessage) {
|
|
1170
|
+
return 'contact_array'
|
|
1171
|
+
} else if (message.locationMessage) {
|
|
1172
|
+
return 'location'
|
|
1173
|
+
} else if (message.liveLocationMessage) {
|
|
1174
|
+
return 'livelocation'
|
|
1175
|
+
} else if (message.listMessage) {
|
|
1176
|
+
return 'list'
|
|
1177
|
+
} else if (message.listResponseMessage) {
|
|
1178
|
+
return 'list_response'
|
|
1179
|
+
} else if (message.buttonsResponseMessage) {
|
|
1180
|
+
return 'buttons_response'
|
|
1181
|
+
} else if (message.orderMessage) {
|
|
1182
|
+
return 'order'
|
|
1183
|
+
} else if (message.productMessage) {
|
|
1184
|
+
return 'product'
|
|
1185
|
+
} else if (message.interactiveResponseMessage) {
|
|
1186
|
+
return 'native_flow_response'
|
|
1187
|
+
} else if (/https:\/\/wa\.me\/c\/\d+/.test(message.extendedTextMessage?.text)) {
|
|
1188
|
+
return 'cataloglink'
|
|
1189
|
+
} else if (/https:\/\/wa\.me\/p\/\d+\/\d+/.test(message.extendedTextMessage?.text)) {
|
|
1190
|
+
return 'productlink'
|
|
1191
|
+
} else if (message.extendedTextMessage?.matchedText || message.groupInviteMessage) {
|
|
1192
|
+
return 'url'
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
const getButtonType = message => {
|
|
1196
|
+
if (message.listMessage) {
|
|
1197
|
+
return 'list'
|
|
1198
|
+
} else if (message.buttonsMessage) {
|
|
1199
|
+
return 'buttons'
|
|
1200
|
+
} else if (message.interactiveMessage?.nativeFlowMessage) {
|
|
1201
|
+
const firstButtonName = message.interactiveMessage.nativeFlowMessage.buttons?.[0]?.name
|
|
1202
|
+
if (firstButtonName === 'review_and_pay') {
|
|
1203
|
+
return 'review_and_pay'
|
|
1204
|
+
} else if (firstButtonName === 'review_order') {
|
|
1205
|
+
return 'review_order'
|
|
1206
|
+
} else if (firstButtonName === 'payment_info') {
|
|
1207
|
+
return 'payment_info'
|
|
1208
|
+
} else if (firstButtonName === 'payment_status') {
|
|
1209
|
+
return 'payment_status'
|
|
1210
|
+
} else if (firstButtonName === 'payment_method') {
|
|
1211
|
+
return 'payment_method'
|
|
1212
|
+
} else if (firstButtonName === 'pix') {
|
|
1213
|
+
return 'pix'
|
|
1214
|
+
} else if (firstButtonName === 'pay') {
|
|
1215
|
+
return 'pay'
|
|
1216
|
+
}
|
|
1217
|
+
return 'native_flow'
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
const getButtonArgs = message => {
|
|
1222
|
+
const nativeFlow = message.interactiveMessage?.nativeFlowMessage
|
|
1223
|
+
const firstButtonName = nativeFlow?.buttons?.[0]?.name
|
|
1224
|
+
const nativeFlowSpecials = [
|
|
1225
|
+
'mpm',
|
|
1226
|
+
'cta_catalog',
|
|
1227
|
+
'send_location',
|
|
1228
|
+
'call_permission_request',
|
|
1229
|
+
'wa_payment_transaction_details',
|
|
1230
|
+
'automated_greeting_message_view_catalog'
|
|
1231
|
+
]
|
|
1232
|
+
|
|
1233
|
+
if (nativeFlow && (firstButtonName === 'review_and_pay' || firstButtonName === 'payment_info')) {
|
|
1234
|
+
return {
|
|
1235
|
+
tag: 'biz',
|
|
1236
|
+
attrs: {
|
|
1237
|
+
native_flow_name: firstButtonName === 'review_and_pay' ? 'order_details' : firstButtonName
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
} else if (nativeFlow && nativeFlowSpecials.includes(firstButtonName)) {
|
|
1241
|
+
// Only works for WhatsApp Original, not WhatsApp Business
|
|
1242
|
+
return {
|
|
1243
|
+
tag: 'biz',
|
|
1244
|
+
attrs: {
|
|
1245
|
+
actual_actors: '2',
|
|
1246
|
+
host_storage: '2',
|
|
1247
|
+
privacy_mode_ts: Utils_1.unixTimestampSeconds().toString()
|
|
1248
|
+
},
|
|
1249
|
+
content: [
|
|
1250
|
+
{
|
|
1251
|
+
tag: 'interactive',
|
|
1252
|
+
attrs: {
|
|
1253
|
+
type: 'native_flow',
|
|
1254
|
+
v: '1'
|
|
1255
|
+
},
|
|
1256
|
+
content: [
|
|
1257
|
+
{
|
|
1258
|
+
tag: 'native_flow',
|
|
1259
|
+
attrs: {
|
|
1260
|
+
v: '2',
|
|
1261
|
+
name: firstButtonName
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
]
|
|
1265
|
+
},
|
|
1266
|
+
{
|
|
1267
|
+
tag: 'quality_control',
|
|
1268
|
+
attrs: {
|
|
1269
|
+
source_type: 'third_party'
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
]
|
|
1273
|
+
}
|
|
1274
|
+
} else if (nativeFlow || message.buttonsMessage) {
|
|
1275
|
+
// It works for whatsapp original and whatsapp business
|
|
1276
|
+
return {
|
|
1277
|
+
tag: 'biz',
|
|
1278
|
+
attrs: {
|
|
1279
|
+
actual_actors: '2',
|
|
1280
|
+
host_storage: '2',
|
|
1281
|
+
privacy_mode_ts: Utils_1.unixTimestampSeconds().toString()
|
|
1282
|
+
},
|
|
1283
|
+
content: [
|
|
1284
|
+
{
|
|
1285
|
+
tag: 'interactive',
|
|
1286
|
+
attrs: {
|
|
1287
|
+
type: 'native_flow',
|
|
1288
|
+
v: '1'
|
|
1289
|
+
},
|
|
1290
|
+
content: [
|
|
1291
|
+
{
|
|
1292
|
+
tag: 'native_flow',
|
|
1293
|
+
attrs: {
|
|
1294
|
+
v: '9',
|
|
1295
|
+
name: 'mixed'
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
]
|
|
1299
|
+
},
|
|
1300
|
+
{
|
|
1301
|
+
tag: 'quality_control',
|
|
1302
|
+
attrs: {
|
|
1303
|
+
source_type: 'third_party'
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
]
|
|
1307
|
+
}
|
|
1308
|
+
} else if (message.listMessage) {
|
|
1309
|
+
return {
|
|
1310
|
+
tag: 'biz',
|
|
1311
|
+
attrs: {
|
|
1312
|
+
actual_actors: '2',
|
|
1313
|
+
host_storage: '2',
|
|
1314
|
+
privacy_mode_ts: Utils_1.unixTimestampSeconds().toString()
|
|
1315
|
+
},
|
|
1316
|
+
content: [
|
|
1317
|
+
{
|
|
1318
|
+
tag: 'list',
|
|
1319
|
+
attrs: {
|
|
1320
|
+
v: '2',
|
|
1321
|
+
type: 'product_list'
|
|
1322
|
+
}
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
tag: 'quality_control',
|
|
1326
|
+
attrs: {
|
|
1327
|
+
source_type: 'third_party'
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
]
|
|
1331
|
+
}
|
|
1332
|
+
} else {
|
|
1333
|
+
return {
|
|
1334
|
+
tag: 'biz',
|
|
1335
|
+
attrs: {
|
|
1336
|
+
actual_actors: '2',
|
|
1337
|
+
host_storage: '2',
|
|
1338
|
+
privacy_mode_ts: Utils_1.unixTimestampSeconds().toString()
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
const issuePrivacyTokens = async (jids, timestamp) => {
|
|
1344
|
+
const t = (timestamp ?? (0, Utils_1.unixTimestampSeconds)()).toString()
|
|
1345
|
+
const result = await query({
|
|
1346
|
+
tag: 'iq',
|
|
1347
|
+
attrs: {
|
|
1348
|
+
to: WABinary_1.S_WHATSAPP_NET,
|
|
1349
|
+
type: 'set',
|
|
1350
|
+
xmlns: 'privacy'
|
|
1351
|
+
},
|
|
1352
|
+
content: [
|
|
1353
|
+
{
|
|
1354
|
+
tag: 'tokens',
|
|
1355
|
+
attrs: {},
|
|
1356
|
+
content: jids.map(jid => ({
|
|
1357
|
+
tag: 'token',
|
|
1358
|
+
attrs: {
|
|
1359
|
+
jid: (0, WABinary_1.jidNormalizedUser)(jid),
|
|
1360
|
+
t,
|
|
1361
|
+
type: 'trusted_contact'
|
|
1362
|
+
}
|
|
1363
|
+
}))
|
|
1364
|
+
}
|
|
1365
|
+
]
|
|
1366
|
+
})
|
|
1367
|
+
return result
|
|
1368
|
+
}
|
|
1369
|
+
const getPrivacyTokens = issuePrivacyTokens
|
|
1370
|
+
const waUploadToServer = (0, Utils_1.getWAUploadToServer)(config, refreshMediaConn)
|
|
1371
|
+
const interactiveHandler = new interactive_handler_1.InteractiveMessageHandler(waUploadToServer, relayMessage, config, sock)
|
|
1372
|
+
const waitForMsgMediaUpdate = (0, Utils_1.bindWaitForEvent)(ev, 'messages.media-update')
|
|
1373
|
+
return {
|
|
1374
|
+
...sock,
|
|
1375
|
+
issuePrivacyTokens,
|
|
1376
|
+
getPrivacyTokens,
|
|
1377
|
+
assertSessions,
|
|
1378
|
+
relayMessage,
|
|
1379
|
+
sendReceipt,
|
|
1380
|
+
sendReceipts,
|
|
1381
|
+
readMessages,
|
|
1382
|
+
refreshMediaConn,
|
|
1383
|
+
waUploadToServer,
|
|
1384
|
+
fetchPrivacySettings,
|
|
1385
|
+
sendPeerDataOperationMessage,
|
|
1386
|
+
createParticipantNodes,
|
|
1387
|
+
getUSyncDevices,
|
|
1388
|
+
messageRetryManager,
|
|
1389
|
+
updateMemberLabel,
|
|
1390
|
+
updateMediaMessage: async message => {
|
|
1391
|
+
const content = (0, Utils_1.assertMediaContent)(message.message)
|
|
1392
|
+
const mediaKey = content.mediaKey
|
|
1393
|
+
const meId = authState.creds.me.id
|
|
1394
|
+
const node = (0, Utils_1.encryptMediaRetryRequest)(message.key, mediaKey, meId)
|
|
1395
|
+
let error = undefined
|
|
1396
|
+
await Promise.all([
|
|
1397
|
+
sendNode(node),
|
|
1398
|
+
waitForMsgMediaUpdate(async update => {
|
|
1399
|
+
const result = update.find(c => c.key.id === message.key.id)
|
|
1400
|
+
if (result) {
|
|
1401
|
+
if (result.error) {
|
|
1402
|
+
error = result.error
|
|
1403
|
+
} else {
|
|
1404
|
+
try {
|
|
1405
|
+
const media = (0, Utils_1.decryptMediaRetryData)(result.media, mediaKey, result.key.id)
|
|
1406
|
+
if (media.result !== index_js_1.proto.MediaRetryNotification.ResultType.SUCCESS) {
|
|
1407
|
+
const resultStr = index_js_1.proto.MediaRetryNotification.ResultType[media.result]
|
|
1408
|
+
throw new boom_1.Boom(`Media re-upload failed by device (${resultStr})`, {
|
|
1409
|
+
data: media,
|
|
1410
|
+
statusCode: (0, Utils_1.getStatusCodeForMediaRetry)(media.result) || 404
|
|
1411
|
+
})
|
|
1412
|
+
}
|
|
1413
|
+
content.directPath = media.directPath
|
|
1414
|
+
content.url = (0, Utils_1.getUrlFromDirectPath)(content.directPath)
|
|
1415
|
+
logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful')
|
|
1416
|
+
} catch (err) {
|
|
1417
|
+
error = err
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
return true
|
|
1421
|
+
}
|
|
1422
|
+
})
|
|
1423
|
+
])
|
|
1424
|
+
if (error) {
|
|
1425
|
+
throw error
|
|
1426
|
+
}
|
|
1427
|
+
ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }])
|
|
1428
|
+
return message
|
|
1429
|
+
},
|
|
1430
|
+
|
|
1431
|
+
sendGroupStatus: async (groupIdsOrContent = [], contentOrGroups = {}, opts = {}) => {
|
|
1432
|
+
const toGroupIdArray = input => {
|
|
1433
|
+
if (Array.isArray(input)) {
|
|
1434
|
+
return input
|
|
1435
|
+
}
|
|
1436
|
+
if (typeof input === 'string') {
|
|
1437
|
+
return [input]
|
|
1438
|
+
}
|
|
1439
|
+
return []
|
|
1440
|
+
}
|
|
1441
|
+
const normalizeGroupJid = value => {
|
|
1442
|
+
if (typeof value !== 'string') {
|
|
1443
|
+
return ''
|
|
1444
|
+
}
|
|
1445
|
+
const trimmed = value.trim()
|
|
1446
|
+
if (!trimmed) {
|
|
1447
|
+
return ''
|
|
1448
|
+
}
|
|
1449
|
+
if (/^\d{8,}$/.test(trimmed)) {
|
|
1450
|
+
return `${trimmed}@g.us`
|
|
1451
|
+
}
|
|
1452
|
+
return trimmed
|
|
1453
|
+
}
|
|
1454
|
+
const useNewSignature = Array.isArray(groupIdsOrContent) || typeof groupIdsOrContent === 'string'
|
|
1455
|
+
const groupIds = useNewSignature ? toGroupIdArray(groupIdsOrContent) : toGroupIdArray(contentOrGroups)
|
|
1456
|
+
const content = useNewSignature ? contentOrGroups || {} : groupIdsOrContent || {}
|
|
1457
|
+
const sendOpts = opts || {}
|
|
1458
|
+
const groupJids = groupIds.map(normalizeGroupJid).filter(jid => (0, WABinary_1.isJidGroup)(jid))
|
|
1459
|
+
const nonGroupJids = groupIds.filter(jid => !(0, WABinary_1.isJidGroup)(normalizeGroupJid(jid)))
|
|
1460
|
+
if (nonGroupJids.length) {
|
|
1461
|
+
logger.warn({ nonGroupJids }, 'ignoring non-group JIDs in sendGroupStatus')
|
|
1462
|
+
}
|
|
1463
|
+
if (!groupJids.length) {
|
|
1464
|
+
return []
|
|
1465
|
+
}
|
|
1466
|
+
const { allUsers } = await resolveStatusAudience(groupJids, false)
|
|
1467
|
+
if (!allUsers.length) {
|
|
1468
|
+
return []
|
|
1469
|
+
}
|
|
1470
|
+
const msg = await generateStatusMessage({
|
|
1471
|
+
...content,
|
|
1472
|
+
backgroundColor: content.backgroundColor || getRandomHexColor(),
|
|
1473
|
+
font: normalizeStatusFont(content.font, logger)
|
|
1474
|
+
})
|
|
1475
|
+
await relayMessage(WABinary_1.STORIES_JID, msg.message, {
|
|
1476
|
+
messageId: msg.key.id,
|
|
1477
|
+
statusJidList: allUsers,
|
|
1478
|
+
...(sendOpts.relay || {})
|
|
1479
|
+
})
|
|
1480
|
+
return groupJids
|
|
1481
|
+
},
|
|
1482
|
+
sendAlbumMessage: async (jid, medias, options = {}) => {
|
|
1483
|
+
const userJid = authState.creds.me.id
|
|
1484
|
+
for (const media of medias) {
|
|
1485
|
+
if (!media.image && !media.video) throw new TypeError(`medias[i] must have image or video property`)
|
|
1486
|
+
}
|
|
1487
|
+
if (medias.length < 2) throw new RangeError('Minimum 2 media')
|
|
1488
|
+
const time = options.delay || 500
|
|
1489
|
+
delete options.delay
|
|
1490
|
+
const album = await (0, Utils_1.generateWAMessageFromContent)(
|
|
1491
|
+
jid,
|
|
1492
|
+
{
|
|
1493
|
+
albumMessage: {
|
|
1494
|
+
expectedImageCount: medias.filter(media => media.image).length,
|
|
1495
|
+
expectedVideoCount: medias.filter(media => media.video).length,
|
|
1496
|
+
...options
|
|
1497
|
+
}
|
|
1498
|
+
},
|
|
1499
|
+
{ userJid, ...options }
|
|
1500
|
+
)
|
|
1501
|
+
await relayMessage(jid, album.message, { messageId: album.key.id })
|
|
1502
|
+
let mediaHandle
|
|
1503
|
+
let msg
|
|
1504
|
+
for (const i in medias) {
|
|
1505
|
+
const media = medias[i]
|
|
1506
|
+
if (media.image) {
|
|
1507
|
+
msg = await (0, Utils_1.generateWAMessage)(
|
|
1508
|
+
jid,
|
|
1509
|
+
{
|
|
1510
|
+
image: media.image,
|
|
1511
|
+
...media,
|
|
1512
|
+
...options
|
|
1513
|
+
},
|
|
1514
|
+
{
|
|
1515
|
+
userJid,
|
|
1516
|
+
upload: async (readStream, opts) => {
|
|
1517
|
+
const up = await waUploadToServer(readStream, {
|
|
1518
|
+
...opts,
|
|
1519
|
+
newsletter: (0, WABinary_1.isJidNewsletter)(jid)
|
|
1520
|
+
})
|
|
1521
|
+
mediaHandle = up.handle
|
|
1522
|
+
return up
|
|
1523
|
+
},
|
|
1524
|
+
...options
|
|
1525
|
+
}
|
|
1526
|
+
)
|
|
1527
|
+
} else if (media.video) {
|
|
1528
|
+
msg = await (0, Utils_1.generateWAMessage)(
|
|
1529
|
+
jid,
|
|
1530
|
+
{
|
|
1531
|
+
video: media.video,
|
|
1532
|
+
...media,
|
|
1533
|
+
...options
|
|
1534
|
+
},
|
|
1535
|
+
{
|
|
1536
|
+
userJid,
|
|
1537
|
+
upload: async (readStream, opts) => {
|
|
1538
|
+
const up = await waUploadToServer(readStream, {
|
|
1539
|
+
...opts,
|
|
1540
|
+
newsletter: (0, WABinary_1.isJidNewsletter)(jid)
|
|
1541
|
+
})
|
|
1542
|
+
mediaHandle = up.handle
|
|
1543
|
+
return up
|
|
1544
|
+
},
|
|
1545
|
+
...options
|
|
1546
|
+
}
|
|
1547
|
+
)
|
|
1548
|
+
}
|
|
1549
|
+
if (msg) {
|
|
1550
|
+
msg.message.messageContextInfo = {
|
|
1551
|
+
messageAssociation: {
|
|
1552
|
+
associationType: 1,
|
|
1553
|
+
parentMessageKey: album.key
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
await relayMessage(jid, msg.message, { messageId: msg.key.id })
|
|
1558
|
+
await (0, Utils_1.delay)(time)
|
|
1559
|
+
}
|
|
1560
|
+
return album
|
|
1561
|
+
},
|
|
1562
|
+
|
|
1563
|
+
sendStatusMention: async (content, jids = []) => {
|
|
1564
|
+
return await interactiveHandler.sendStatusWhatsApp(content, jids)
|
|
1565
|
+
},
|
|
1566
|
+
sendTable: async (jid, title, headers, rows, quoted, options = {}) => {
|
|
1567
|
+
const { message, messageId } = message_composer_1.generateTableContent(title, headers, rows, quoted, options)
|
|
1568
|
+
await relayMessage(jid, message, { messageId })
|
|
1569
|
+
return { message, messageId }
|
|
1570
|
+
},
|
|
1571
|
+
sendList: async (jid, title, items, quoted, options = {}) => {
|
|
1572
|
+
const { message, messageId } = message_composer_1.generateListContent(title, items, quoted, options)
|
|
1573
|
+
await relayMessage(jid, message, { messageId })
|
|
1574
|
+
return { message, messageId }
|
|
1575
|
+
},
|
|
1576
|
+
sendCodeBlock: async (jid, code, quoted, options = {}) => {
|
|
1577
|
+
const { message, messageId } = message_composer_1.generateCodeBlockContent(code, quoted, options)
|
|
1578
|
+
await relayMessage(jid, message, { messageId })
|
|
1579
|
+
return { message, messageId }
|
|
1580
|
+
},
|
|
1581
|
+
sendLatex: async (jid, quoted, options) => {
|
|
1582
|
+
const { message, messageId } = message_composer_1.generateLatexContent(quoted, options)
|
|
1583
|
+
await relayMessage(jid, message, { messageId })
|
|
1584
|
+
return { message, messageId }
|
|
1585
|
+
},
|
|
1586
|
+
sendLatexImage: async (jid, quoted, options, renderLatexToPng, uploadFn) => {
|
|
1587
|
+
const { message, messageId } = await message_composer_1.generateLatexImageContent(
|
|
1588
|
+
quoted,
|
|
1589
|
+
options,
|
|
1590
|
+
uploadFn,
|
|
1591
|
+
renderLatexToPng
|
|
1592
|
+
)
|
|
1593
|
+
await relayMessage(jid, message, { messageId })
|
|
1594
|
+
return { message, messageId }
|
|
1595
|
+
},
|
|
1596
|
+
sendLatexInlineImage: async (jid, quoted, options, renderLatexToPng, uploadFn) => {
|
|
1597
|
+
const { message, messageId } = await message_composer_1.generateLatexInlineImageContent(
|
|
1598
|
+
quoted,
|
|
1599
|
+
options,
|
|
1600
|
+
uploadFn,
|
|
1601
|
+
renderLatexToPng
|
|
1602
|
+
)
|
|
1603
|
+
await relayMessage(jid, message, { messageId })
|
|
1604
|
+
return { message, messageId }
|
|
1605
|
+
},
|
|
1606
|
+
captureUnifiedResponse: message_composer_1.captureUnifiedResponse,
|
|
1607
|
+
sendUnifiedResponse: async (jid, quoted, captured) => {
|
|
1608
|
+
const { message, messageId } = message_composer_1.generateUnifiedResponseContent(quoted, captured)
|
|
1609
|
+
await relayMessage(jid, message, { messageId })
|
|
1610
|
+
return { message, messageId }
|
|
1611
|
+
},
|
|
1612
|
+
sendRichMessage: async (jid, submessages, quoted, options = {}) => {
|
|
1613
|
+
const { message, messageId } = message_composer_1.generateRichMessageContent(submessages, quoted, options)
|
|
1614
|
+
await relayMessage(jid, message, { messageId })
|
|
1615
|
+
return { message, messageId }
|
|
1616
|
+
},
|
|
1617
|
+
sendMessage: async (jid, content, options = {}) => {
|
|
1618
|
+
const userJid = authState.creds.me.id
|
|
1619
|
+
const iosBros = config.browser[0] === "iOS" || config.browser[1] === "Safari";
|
|
1620
|
+
const mId = iosBros ? Utils_1.generateIOSMessageID() : msgId ?? (0, Utils_1.generateMessageIDV2)(userJid)
|
|
1621
|
+
// ── Normalize: buttons[].nativeFlowInfo -> interactiveButtons ──────
|
|
1622
|
+
if (
|
|
1623
|
+
typeof content === 'object' &&
|
|
1624
|
+
Array.isArray(content.buttons) &&
|
|
1625
|
+
content.buttons.length > 0 &&
|
|
1626
|
+
content.buttons.some(b => b.nativeFlowInfo)
|
|
1627
|
+
) {
|
|
1628
|
+
const interactiveButtons = content.buttons.map(b => {
|
|
1629
|
+
if (b.nativeFlowInfo) {
|
|
1630
|
+
return {
|
|
1631
|
+
name: b.nativeFlowInfo.name,
|
|
1632
|
+
buttonParamsJson: b.nativeFlowInfo.paramsJson || '{}'
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
return {
|
|
1636
|
+
name: 'quick_reply',
|
|
1637
|
+
buttonParamsJson: JSON.stringify({
|
|
1638
|
+
display_text: b.buttonText?.displayText || b.buttonId || 'Button',
|
|
1639
|
+
id: b.buttonId || b.buttonText?.displayText || 'btn'
|
|
1640
|
+
})
|
|
1641
|
+
}
|
|
1642
|
+
})
|
|
1643
|
+
const { buttons, headerType, viewOnce, ...rest } = content
|
|
1644
|
+
content = { ...rest, interactiveButtons }
|
|
1645
|
+
}
|
|
1646
|
+
if (
|
|
1647
|
+
typeof content === 'object' &&
|
|
1648
|
+
'disappearingMessagesInChat' in content &&
|
|
1649
|
+
typeof content['disappearingMessagesInChat'] !== 'undefined' &&
|
|
1650
|
+
(0, WABinary_1.isJidGroup)(jid)
|
|
1651
|
+
) {
|
|
1652
|
+
const { disappearingMessagesInChat } = content
|
|
1653
|
+
const value =
|
|
1654
|
+
typeof disappearingMessagesInChat === 'boolean'
|
|
1655
|
+
? disappearingMessagesInChat
|
|
1656
|
+
? WA_DEFAULT_EPHEMERAL
|
|
1657
|
+
: 0
|
|
1658
|
+
: disappearingMessagesInChat
|
|
1659
|
+
await groupToggleEphemeral(jid, value)
|
|
1660
|
+
} else if (interactiveHandler.detectType(content)) {
|
|
1661
|
+
const { quoted } = options
|
|
1662
|
+
const messageType = interactiveHandler.detectType(content)
|
|
1663
|
+
switch (messageType) {
|
|
1664
|
+
case 'PAYMENT': {
|
|
1665
|
+
const paymentContent = await interactiveHandler.handlePayment(content, quoted)
|
|
1666
|
+
return await relayMessage(jid, paymentContent, {
|
|
1667
|
+
messageId: mId
|
|
1668
|
+
})
|
|
1669
|
+
}
|
|
1670
|
+
case 'PRODUCT': {
|
|
1671
|
+
const productContent = await interactiveHandler.handleProduct(content, jid, quoted)
|
|
1672
|
+
const productMsg = await (0, Utils_1.generateWAMessageFromContent)(jid, productContent, { quoted, userJid })
|
|
1673
|
+
return await relayMessage(jid, productMsg.message, {
|
|
1674
|
+
messageId: productMsg.key.id
|
|
1675
|
+
})
|
|
1676
|
+
}
|
|
1677
|
+
case 'INTERACTIVE': {
|
|
1678
|
+
const interactiveContent = await interactiveHandler.handleInteractive(content, jid, quoted)
|
|
1679
|
+
const interactiveMsg = await (0, Utils_1.generateWAMessageFromContent)(jid, interactiveContent, {
|
|
1680
|
+
quoted,
|
|
1681
|
+
userJid
|
|
1682
|
+
})
|
|
1683
|
+
return await relayMessage(jid, interactiveMsg.message, {
|
|
1684
|
+
messageId: interactiveMsg.key.id
|
|
1685
|
+
})
|
|
1686
|
+
}
|
|
1687
|
+
case 'ALBUM':
|
|
1688
|
+
return await interactiveHandler.handleAlbum(content, jid, quoted)
|
|
1689
|
+
case 'EVENT':
|
|
1690
|
+
return await interactiveHandler.handleEvent(content, jid, quoted)
|
|
1691
|
+
case 'POLL_RESULT':
|
|
1692
|
+
return await interactiveHandler.handlePollResult(content, jid, quoted)
|
|
1693
|
+
case 'GROUP_STORY':
|
|
1694
|
+
return await interactiveHandler.handleGroupStory(content, jid, quoted)
|
|
1695
|
+
case 'ORDER':
|
|
1696
|
+
return await interactiveHandler.handleOrderMessage(content, jid, quoted)
|
|
1697
|
+
case 'GROUP_LABEL':
|
|
1698
|
+
return await interactiveHandler.handleGbLabel(content, jid)
|
|
1699
|
+
}
|
|
1700
|
+
} else {
|
|
1701
|
+
let mediaHandle
|
|
1702
|
+
const fullMsg = await (0, Utils_1.generateWAMessage)(jid, content, {
|
|
1703
|
+
logger,
|
|
1704
|
+
userJid,
|
|
1705
|
+
getUrlInfo: text =>
|
|
1706
|
+
(0, link_preview_1.getUrlInfo)(text, {
|
|
1707
|
+
thumbnailWidth: linkPreviewImageThumbnailWidth,
|
|
1708
|
+
fetchOpts: {
|
|
1709
|
+
timeout: 3000,
|
|
1710
|
+
...(httpRequestOptions || {})
|
|
1711
|
+
},
|
|
1712
|
+
logger,
|
|
1713
|
+
uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
|
|
1714
|
+
}),
|
|
1715
|
+
//TODO: CACHE
|
|
1716
|
+
getProfilePicUrl: sock.profilePictureUrl,
|
|
1717
|
+
getCallLink: sock.createCallLink,
|
|
1718
|
+
newsletter: (0, WABinary_1.isJidNewsletter)(jid),
|
|
1719
|
+
upload: async (encFilePath, opts) => {
|
|
1720
|
+
const up = await waUploadToServer(encFilePath, {
|
|
1721
|
+
...opts,
|
|
1722
|
+
newsletter: (0, WABinary_1.isJidNewsletter)(jid)
|
|
1723
|
+
})
|
|
1724
|
+
mediaHandle = up.handle
|
|
1725
|
+
return up
|
|
1726
|
+
},
|
|
1727
|
+
mediaCache: config.mediaCache,
|
|
1728
|
+
options: config.options,
|
|
1729
|
+
messageId: mId,
|
|
1730
|
+
...options
|
|
1731
|
+
})
|
|
1732
|
+
if (!mediaHandle) {
|
|
1733
|
+
const msgContent = fullMsg.message
|
|
1734
|
+
const msgTypes = ['audioMessage', 'imageMessage', 'videoMessage', 'documentMessage', 'stickerMessage']
|
|
1735
|
+
for (const t of msgTypes) {
|
|
1736
|
+
if (msgContent?.[t]?._uploadHandle) {
|
|
1737
|
+
mediaHandle = msgContent[t]._uploadHandle
|
|
1738
|
+
|
|
1739
|
+
delete msgContent[t]._uploadHandle
|
|
1740
|
+
break
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
const isEventMsg = 'event' in content && !!content.event
|
|
1745
|
+
const isDeleteMsg = 'delete' in content && !!content.delete
|
|
1746
|
+
const isEditMsg = 'edit' in content && !!content.edit
|
|
1747
|
+
const isPinMsg = 'pin' in content && !!content.pin
|
|
1748
|
+
const isPollMessage = 'poll' in content && !!content.poll
|
|
1749
|
+
const additionalAttributes = {}
|
|
1750
|
+
const additionalNodes = []
|
|
1751
|
+
if (isDeleteMsg) {
|
|
1752
|
+
if ((0, WABinary_1.isJidGroup)(content.delete?.remoteJid) && !content.delete?.fromMe) {
|
|
1753
|
+
additionalAttributes.edit = '8'
|
|
1754
|
+
} else {
|
|
1755
|
+
additionalAttributes.edit = '7'
|
|
1756
|
+
}
|
|
1757
|
+
} else if (isEditMsg) {
|
|
1758
|
+
additionalAttributes.edit = '1'
|
|
1759
|
+
} else if (isPinMsg) {
|
|
1760
|
+
additionalAttributes.edit = '2'
|
|
1761
|
+
} else if (isPollMessage) {
|
|
1762
|
+
additionalNodes.push({
|
|
1763
|
+
tag: 'meta',
|
|
1764
|
+
attrs: {
|
|
1765
|
+
polltype: 'creation'
|
|
1766
|
+
}
|
|
1767
|
+
})
|
|
1768
|
+
} else if (isEventMsg) {
|
|
1769
|
+
additionalNodes.push({
|
|
1770
|
+
tag: 'meta',
|
|
1771
|
+
attrs: {
|
|
1772
|
+
event_type: 'creation'
|
|
1773
|
+
}
|
|
1774
|
+
})
|
|
1775
|
+
}
|
|
1776
|
+
const buttonType = getButtonType(fullMsg.message)
|
|
1777
|
+
if (content?.audio && options?.contextInfo) {
|
|
1778
|
+
const msgContent = fullMsg.message
|
|
1779
|
+
if (msgContent?.audioMessage) {
|
|
1780
|
+
msgContent.audioMessage.contextInfo = options.contextInfo
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
if (buttonType) {
|
|
1784
|
+
const btnNode = getButtonArgs(fullMsg.message)
|
|
1785
|
+
if (btnNode) additionalNodes.push(btnNode)
|
|
1786
|
+
}
|
|
1787
|
+
if (mediaHandle) {
|
|
1788
|
+
additionalAttributes['media_id'] = mediaHandle
|
|
1789
|
+
}
|
|
1790
|
+
await relayMessage(jid, fullMsg.message, {
|
|
1791
|
+
messageId: fullMsg.key.id,
|
|
1792
|
+
useCachedGroupMetadata: options.useCachedGroupMetadata,
|
|
1793
|
+
additionalAttributes,
|
|
1794
|
+
statusJidList: options.statusJidList,
|
|
1795
|
+
additionalNodes
|
|
1796
|
+
})
|
|
1797
|
+
if (config.emitOwnEvents) {
|
|
1798
|
+
process.nextTick(async () => {
|
|
1799
|
+
await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'))
|
|
1800
|
+
})
|
|
1801
|
+
}
|
|
1802
|
+
return fullMsg
|
|
1803
|
+
}
|
|
1804
|
+
},
|
|
1805
|
+
sendMessageMembers: async (jid, message, options = {}) => {
|
|
1806
|
+
const {
|
|
1807
|
+
messageId: idm,
|
|
1808
|
+
quoted,
|
|
1809
|
+
delayMs = 1500,
|
|
1810
|
+
useUserDevicesCache = true,
|
|
1811
|
+
cachedGroupMetadata,
|
|
1812
|
+
onlyMember = true
|
|
1813
|
+
} = options;
|
|
1814
|
+
const { server } = jidDecode(jid);
|
|
1815
|
+
if (server !== "g.us") throw new Error("@g.us server required");
|
|
1816
|
+
const meId = authState.creds.me.id;
|
|
1817
|
+
const messages = Utils_1.normalizeMessageContent(message);
|
|
1818
|
+
const groupData = cachedGroupMetadata? await cachedGroupMetadata(jid) : await groupMetadata(jid);
|
|
1819
|
+
const isLid = groupData.addressingMode === "lid";
|
|
1820
|
+
const isAdmin = groupData.participants.filter((x) => x.admin !== null).map((y) => y.id)
|
|
1821
|
+
let participantJids = groupData.participants.map(z => z.id);
|
|
1822
|
+
if (onlyMember) {
|
|
1823
|
+
participantJids = isAdmin ? isAdmin : participantJids;
|
|
1824
|
+
}
|
|
1825
|
+
logger.info(`Sending message to ${participantJids.length} members from ${jid}`);
|
|
1826
|
+
for (let i = 0; i < participantJids.length; i++) {
|
|
1827
|
+
const jid = participantJids[i];
|
|
1828
|
+
if (areJidsSameUser(jid, meId)) continue;
|
|
1829
|
+
try {
|
|
1830
|
+
const msgId = `${idm || Utils_1.generateMessageID()}_${i}`;
|
|
1831
|
+
const fullMsg = await Utils_1.generateWAMessageFromContent(jid, message, {
|
|
1832
|
+
messageId: msgId,
|
|
1833
|
+
quoted
|
|
1834
|
+
})
|
|
1835
|
+
await relayMessage(jid, fullMsg.message, {
|
|
1836
|
+
messageId: fullMsg.key.id
|
|
1837
|
+
});
|
|
1838
|
+
logger.debug(`Message successfully sent to ${jid}`);
|
|
1839
|
+
if (delayMs && i < participantJids.length - 1) {
|
|
1840
|
+
await new Promise(z => setTimeout(z, delayMs));
|
|
1841
|
+
}
|
|
1842
|
+
} catch (e) {
|
|
1843
|
+
logger.error({ jid, e }, "Error sending message to");
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
return JSON.stringify({
|
|
1847
|
+
members_total: participantJids.length,
|
|
1848
|
+
message
|
|
1849
|
+
}, null, 4);
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
exports.makeMessagesSocket = makeMessagesSocket
|