vinnleys 1.0.0
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.
Potentially problematic release.
This version of vinnleys might be problematic. Click here for more details.
- package/README.md +108 -0
- package/WAProto/GenerateStatics.sh +3 -0
- package/WAProto/WAProto.proto +6922 -0
- package/WAProto/fix-imports.js +85 -0
- package/WAProto/index.d.ts +79257 -0
- package/WAProto/index.js +242946 -0
- package/engine-requirements.js +10 -0
- package/lib/Defaults/index.js +131 -0
- package/lib/Signal/Group/ciphertext-message.js +12 -0
- package/lib/Signal/Group/group-session-builder.js +30 -0
- package/lib/Signal/Group/group_cipher.js +82 -0
- package/lib/Signal/Group/index.js +12 -0
- package/lib/Signal/Group/keyhelper.js +18 -0
- package/lib/Signal/Group/sender-chain-key.js +26 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +63 -0
- package/lib/Signal/Group/sender-key-message.js +66 -0
- package/lib/Signal/Group/sender-key-name.js +48 -0
- package/lib/Signal/Group/sender-key-record.js +41 -0
- package/lib/Signal/Group/sender-key-state.js +84 -0
- package/lib/Signal/Group/sender-message-key.js +26 -0
- package/lib/Signal/libsignal.js +431 -0
- package/lib/Signal/lid-mapping.js +277 -0
- package/lib/Socket/Client/index.js +3 -0
- package/lib/Socket/Client/types.js +11 -0
- package/lib/Socket/Client/websocket.js +54 -0
- package/lib/Socket/MessageBuilder.js +3728 -0
- package/lib/Socket/business.js +379 -0
- package/lib/Socket/chats.js +1198 -0
- package/lib/Socket/communities.js +431 -0
- package/lib/Socket/graphql.js +716 -0
- package/lib/Socket/groups.js +374 -0
- package/lib/Socket/index.js +24 -0
- package/lib/Socket/interop.js +463 -0
- package/lib/Socket/luxu.js +387 -0
- package/lib/Socket/message-builder.js +521 -0
- package/lib/Socket/messages-recv.js +1916 -0
- package/lib/Socket/messages-send.js +1511 -0
- package/lib/Socket/mex.js +54 -0
- package/lib/Socket/newsletter.js +636 -0
- package/lib/Socket/privacy.js +318 -0
- package/lib/Socket/socket.js +1114 -0
- package/lib/Socket/username.js +236 -0
- package/lib/Store/index.js +10 -0
- package/lib/Store/keyed-db.js +108 -0
- package/lib/Store/make-cache-manager-store.js +85 -0
- package/lib/Store/make-in-memory-store.js +244 -0
- package/lib/Store/make-ordered-dictionary.js +75 -0
- package/lib/Store/object-repository.js +32 -0
- package/lib/Types/Auth.js +2 -0
- package/lib/Types/Bussines.js +2 -0
- package/lib/Types/Call.js +2 -0
- package/lib/Types/Chat.js +8 -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 +25 -0
- package/lib/Types/LabelAssociation.js +7 -0
- package/lib/Types/Message.js +11 -0
- package/lib/Types/Mex.js +111 -0
- package/lib/Types/Product.js +2 -0
- package/lib/Types/Signal.js +2 -0
- package/lib/Types/Socket.js +3 -0
- package/lib/Types/State.js +56 -0
- package/lib/Types/USync.js +2 -0
- package/lib/Types/index.js +26 -0
- package/lib/Utils/auth-utils.js +302 -0
- package/lib/Utils/browser-utils.js +48 -0
- package/lib/Utils/business.js +231 -0
- package/lib/Utils/chat-utils.js +872 -0
- package/lib/Utils/companion-reg-client-utils.js +35 -0
- package/lib/Utils/crypto.js +118 -0
- package/lib/Utils/decode-wa-message.js +350 -0
- package/lib/Utils/event-buffer.js +622 -0
- package/lib/Utils/generics.js +403 -0
- package/lib/Utils/history.js +134 -0
- package/lib/Utils/identity-change-handler.js +50 -0
- package/lib/Utils/index.js +24 -0
- package/lib/Utils/link-preview.js +85 -0
- package/lib/Utils/logger.js +3 -0
- package/lib/Utils/lt-hash.js +8 -0
- package/lib/Utils/make-mutex.js +33 -0
- package/lib/Utils/message-composer.js +273 -0
- package/lib/Utils/message-retry-manager.js +265 -0
- package/lib/Utils/messages-media.js +870 -0
- package/lib/Utils/messages.js +1394 -0
- package/lib/Utils/noise-handler.js +201 -0
- package/lib/Utils/offline-node-processor.js +40 -0
- package/lib/Utils/pre-key-manager.js +106 -0
- package/lib/Utils/process-message.js +630 -0
- package/lib/Utils/reporting-utils.js +258 -0
- package/lib/Utils/signal.js +201 -0
- package/lib/Utils/stanza-ack.js +38 -0
- package/lib/Utils/sticker.js +133 -0
- package/lib/Utils/sync-action-utils.js +49 -0
- package/lib/Utils/tc-token-utils.js +163 -0
- package/lib/Utils/use-multi-file-auth-state.js +121 -0
- package/lib/Utils/use-sqlite-auth-state.js +162 -0
- package/lib/Utils/validate-connection.js +203 -0
- package/lib/WABinary/constants.js +1301 -0
- package/lib/WABinary/decode.js +262 -0
- package/lib/WABinary/encode.js +220 -0
- package/lib/WABinary/generic-utils.js +204 -0
- package/lib/WABinary/index.js +6 -0
- package/lib/WABinary/jid-utils.js +98 -0
- package/lib/WABinary/types.js +2 -0
- package/lib/WAM/BinaryInfo.js +10 -0
- package/lib/WAM/constants.js +22853 -0
- package/lib/WAM/encode.js +150 -0
- package/lib/WAM/index.js +4 -0
- package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +53 -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 +58 -0
- package/lib/WAUSync/Protocols/USyncLIDProtocol.js +30 -0
- package/lib/WAUSync/Protocols/USyncPictureProtocol.js +32 -0
- package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +28 -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 +12 -0
- package/lib/WAUSync/USyncQuery.js +151 -0
- package/lib/WAUSync/USyncUser.js +56 -0
- package/lib/WAUSync/index.js +3 -0
- package/lib/index.js +33 -0
- package/package.json +135 -0
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import { proto } from '../../WAProto/index.js';
|
|
3
|
+
import { generateWAMessageFromContent, prepareWAMessageMedia } from '../Utils/index.js';
|
|
4
|
+
|
|
5
|
+
export const makeMessageBuilderSocket = (sock) => {
|
|
6
|
+
const { relayMessage, sendMessage, waUploadToServer } = sock;
|
|
7
|
+
|
|
8
|
+
// per-socket-instance poll state: id -> [{ vote, action }]
|
|
9
|
+
const pollActionStore = new Map();
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Relay an arbitrary raw message content object, bypassing the normal
|
|
13
|
+
* content-type detection in sendMessage(). Useful when you've already
|
|
14
|
+
* built a proto-shaped message (e.g. from generateWAMessageFromContent)
|
|
15
|
+
* and just need it sent as-is.
|
|
16
|
+
*/
|
|
17
|
+
const sendJsonMessage = async (jid, content = {}, options = {}) => {
|
|
18
|
+
const msg = generateWAMessageFromContent(jid, content, {});
|
|
19
|
+
return relayMessage(jid, msg.message, { messageId: msg.key.id, ...options });
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Sends an HTML GenAI primitive rich response message.
|
|
24
|
+
* html: string template/raw HTML markup to be rendered.
|
|
25
|
+
*/
|
|
26
|
+
const sendHtml = async (jid, html = '', options = {}) => {
|
|
27
|
+
const payloadData = Buffer.from(
|
|
28
|
+
JSON.stringify({
|
|
29
|
+
__typename: "GenAIUnifiedResponse",
|
|
30
|
+
response_id: crypto.randomUUID(),
|
|
31
|
+
sections: [
|
|
32
|
+
{
|
|
33
|
+
__typename: "GenAIUnifiedResponseSection",
|
|
34
|
+
view_model: {
|
|
35
|
+
__typename: "GenAISingleLayoutViewModel",
|
|
36
|
+
primitive: {
|
|
37
|
+
__typename: "FOAHtmlPrimitiveDemoDONOTUSE",
|
|
38
|
+
trusted_sources: [],
|
|
39
|
+
payload: String(html).trim()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
})
|
|
45
|
+
).toString("base64");
|
|
46
|
+
|
|
47
|
+
const messageContent = {
|
|
48
|
+
botForwardedMessage: {
|
|
49
|
+
message: {
|
|
50
|
+
richResponseMessage: {
|
|
51
|
+
messageType: 1,
|
|
52
|
+
unifiedResponse: {
|
|
53
|
+
data: payloadData
|
|
54
|
+
},
|
|
55
|
+
contextInfo: {
|
|
56
|
+
isForwarded: true,
|
|
57
|
+
forwardOrigin: 4
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return sendJsonMessage(jid, messageContent, options);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Sends a WA poll and remembers which "action" each option maps to, so
|
|
69
|
+
* a caller can look up what the voter selected once the vote comes back
|
|
70
|
+
* through a `messages.update` event (see resolvePollAction below).
|
|
71
|
+
*
|
|
72
|
+
* pollOptions: [{ vote: 'Option label', action: 'anything you want back' }]
|
|
73
|
+
*/
|
|
74
|
+
const sendActionPoll = async (jid, name = '', pollOptions = [], options = {}) => {
|
|
75
|
+
const values = pollOptions.map(o => o.vote);
|
|
76
|
+
const pollMsg = await sendMessage(jid, { poll: { name, values, selectableCount: options.selectableCount || 1 } }, options);
|
|
77
|
+
pollActionStore.set(pollMsg.key.id, pollOptions);
|
|
78
|
+
return pollMsg;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Given a poll creation message id and the selected option label (as
|
|
83
|
+
* resolved via getAggregateVotesInPollMessage), returns the `action`
|
|
84
|
+
* that was registered for it in sendActionPoll, or undefined if this
|
|
85
|
+
* poll wasn't created through sendActionPoll (or already forgotten).
|
|
86
|
+
*/
|
|
87
|
+
const resolvePollAction = (pollMessageId, selectedVoteLabel) => {
|
|
88
|
+
const options = pollActionStore.get(pollMessageId);
|
|
89
|
+
return options?.find(o => o.vote === selectedVoteLabel)?.action;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Sends a set of media files as a single WA album (grouped gallery).
|
|
94
|
+
* media: array of local file paths or URLs; extension picks image/video.
|
|
95
|
+
*/
|
|
96
|
+
const sendAlbumMessage = async (jid, media = [], contextInfo = {}) => {
|
|
97
|
+
const albumMsg = generateWAMessageFromContent(jid, proto.Message.fromObject({
|
|
98
|
+
albumMessage: {
|
|
99
|
+
expectedImageCount: media.filter(m => !/\.mp4$/i.test(m)).length,
|
|
100
|
+
expectedVideoCount: media.filter(m => /\.mp4$/i.test(m)).length,
|
|
101
|
+
contextInfo
|
|
102
|
+
}
|
|
103
|
+
}), {});
|
|
104
|
+
|
|
105
|
+
const albumKey = {
|
|
106
|
+
id: await relayMessage(jid, albumMsg.message, { messageId: albumMsg.key.id }),
|
|
107
|
+
remoteJid: jid,
|
|
108
|
+
fromMe: true
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const keys = { album: albumKey };
|
|
112
|
+
let i = 1;
|
|
113
|
+
|
|
114
|
+
const mimetypes = {
|
|
115
|
+
jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png',
|
|
116
|
+
gif: 'image/gif', webp: 'image/webp', mp4: 'video/mp4'
|
|
117
|
+
};
|
|
118
|
+
const messagetypes = {
|
|
119
|
+
jpg: 'imageMessage', jpeg: 'imageMessage', png: 'imageMessage',
|
|
120
|
+
gif: 'imageMessage', webp: 'imageMessage', mp4: 'videoMessage'
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
for (const source of media) {
|
|
124
|
+
const ext = source.split('.').pop().toLowerCase();
|
|
125
|
+
const mimetype = mimetypes[ext];
|
|
126
|
+
const type = messagetypes[ext];
|
|
127
|
+
|
|
128
|
+
if (!mimetype || !type) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const msg = await prepareWAMessageMedia(
|
|
133
|
+
{ [type.startsWith('image') ? 'image' : 'video']: { url: source }, mimetype },
|
|
134
|
+
{ upload: waUploadToServer }
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const mediaMessage = generateWAMessageFromContent(jid, proto.Message.fromObject({
|
|
138
|
+
associatedChildMessage: {
|
|
139
|
+
message: {
|
|
140
|
+
messageContextInfo: {
|
|
141
|
+
messageSecret: crypto.randomBytes(32),
|
|
142
|
+
messageAssociation: {
|
|
143
|
+
associationType: 'MEDIA_ALBUM',
|
|
144
|
+
parentMessageKey: albumKey
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
[type]: { ...msg[type] }
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}), {});
|
|
151
|
+
|
|
152
|
+
keys[`media_${i++}`] = {
|
|
153
|
+
id: await relayMessage(jid, mediaMessage.message, { messageId: mediaMessage.key.id }),
|
|
154
|
+
fromMe: true,
|
|
155
|
+
remoteJid: jid
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return keys;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Posts `content` to status@broadcast and mentions `jid` in it (i.e. a
|
|
164
|
+
* status update that notifies a specific contact).
|
|
165
|
+
*/
|
|
166
|
+
const sendStatusMention = async (jid, content) => {
|
|
167
|
+
const media = generateWAMessageFromContent('status@broadcast', content, {});
|
|
168
|
+
|
|
169
|
+
const additionalNodes = [
|
|
170
|
+
{
|
|
171
|
+
tag: 'meta',
|
|
172
|
+
attrs: {},
|
|
173
|
+
content: [
|
|
174
|
+
{
|
|
175
|
+
tag: 'mentioned_users',
|
|
176
|
+
attrs: {},
|
|
177
|
+
content: [{ tag: 'to', attrs: { jid }, content: undefined }]
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
await relayMessage('status@broadcast', media.message, {
|
|
184
|
+
messageId: media.key.id,
|
|
185
|
+
statusJidList: [jid, sock.user?.id],
|
|
186
|
+
additionalNodes
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return media;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Builds and sends a "rich response" card (WA's GenAI-style unified
|
|
194
|
+
* response widget: optional header image/title, body buttons or a
|
|
195
|
+
* carousel/row of cards, and an optional footer link).
|
|
196
|
+
*
|
|
197
|
+
* content.header: { title, image: { url, mime_type, inline, width, height }, disclaimer, disclaimerText }
|
|
198
|
+
* content.body: { title, buttons: [labels] } OR { carousel|row: true, cards: [{ title, buttons, toast }] }
|
|
199
|
+
* content.footer: { text, url, image }
|
|
200
|
+
*/
|
|
201
|
+
const sendRichResponse = async (jid, content = {}) => {
|
|
202
|
+
const header = content?.header;
|
|
203
|
+
const body = content?.body;
|
|
204
|
+
const footer = content?.footer;
|
|
205
|
+
let messageContextInfo = {};
|
|
206
|
+
const sections = [];
|
|
207
|
+
|
|
208
|
+
if (header) {
|
|
209
|
+
const { disclaimer = false, disclaimerText = ' ', image = { inline: false }, title = '' } = header ?? {};
|
|
210
|
+
|
|
211
|
+
if (disclaimer) {
|
|
212
|
+
messageContextInfo = { messageContextInfo: { botMetadata: { messageDisclaimerText: disclaimerText } } };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (title) {
|
|
216
|
+
sections.push({
|
|
217
|
+
__typename: 'GenAIUnifiedResponseSection',
|
|
218
|
+
view_model: {
|
|
219
|
+
__typename: 'GenAISingleLayoutViewModel',
|
|
220
|
+
primitive: { __typename: 'FOATextPrimitive', text: '# ' + title }
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (image?.url) {
|
|
226
|
+
sections.push(image.inline ? {
|
|
227
|
+
__typename: 'GenAIUnifiedResponseSection',
|
|
228
|
+
view_model: {
|
|
229
|
+
__typename: 'GenAISingleLayoutViewModel',
|
|
230
|
+
primitive: {
|
|
231
|
+
__typename: 'GenAIMarkdownTextUXPrimitive',
|
|
232
|
+
text: '{{header}}.{{/header}}',
|
|
233
|
+
inline_entities: [{
|
|
234
|
+
__typename: 'GenAITextInlineEntity',
|
|
235
|
+
key: 'header',
|
|
236
|
+
metadata: {
|
|
237
|
+
__typename: 'GenAILatexItem',
|
|
238
|
+
latex_expression: '.',
|
|
239
|
+
font_height: 24,
|
|
240
|
+
padding: 4,
|
|
241
|
+
latex_image: {
|
|
242
|
+
__typename: 'GenAIMediaItem',
|
|
243
|
+
mime_type: image.mime_type || 'image/png',
|
|
244
|
+
url: image.url,
|
|
245
|
+
url_fallback: image.url,
|
|
246
|
+
width: image.width || 500,
|
|
247
|
+
height: image.height || 500,
|
|
248
|
+
expiration_timestamp_ms: Date.now() + 86400000
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}]
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} : {
|
|
255
|
+
__typename: 'GenAIUnifiedResponseSection',
|
|
256
|
+
view_model: {
|
|
257
|
+
__typename: 'GenAISingleLayoutViewModel',
|
|
258
|
+
primitive: {
|
|
259
|
+
__typename: 'GenAIImagePrimitive',
|
|
260
|
+
preview_image: { __typename: 'GenAIMediaItem', mime_type: image.mime_type || 'image/png', url: image.url },
|
|
261
|
+
full_image: { __typename: 'GenAIMediaItem', mime_type: image.mime_type || 'image/png', url: image.url }
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (body) {
|
|
269
|
+
const { cards = null, buttons = null, title = '', toast = '', carousel = false, row = false } = body ?? {};
|
|
270
|
+
|
|
271
|
+
if ((carousel || row) && cards?.length >= 1) {
|
|
272
|
+
sections.push({
|
|
273
|
+
__typename: 'GenAIUnifiedResponseSection',
|
|
274
|
+
view_model: {
|
|
275
|
+
primitives: cards.map((card, cardIndex) => ({
|
|
276
|
+
__typename: 'GenAI3PExtWidgetPrimitive',
|
|
277
|
+
header: { __typename: 'GenAI3PExtWidgetStandardHeader', title: card?.title || '' },
|
|
278
|
+
body: {
|
|
279
|
+
__typename: 'GenAI3PExtCalendarEventList',
|
|
280
|
+
ctas: (card?.buttons || []).map((text, buttonIndex) => ({
|
|
281
|
+
label: text,
|
|
282
|
+
state: 'PENDING',
|
|
283
|
+
kind: 'OTHER',
|
|
284
|
+
tool_call_id: `${cardIndex}${buttonIndex}`,
|
|
285
|
+
toast: { label: card?.toast || '', __typename: 'GenAI3PExtWidgetToast' },
|
|
286
|
+
__typename: 'GenAI3PExtWidgetCTA'
|
|
287
|
+
})),
|
|
288
|
+
sections: []
|
|
289
|
+
}
|
|
290
|
+
})),
|
|
291
|
+
__typename: carousel ? 'GenAIHScrollLayoutViewModel' : 'GenAIActionRowLayoutViewModel'
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
} else if (buttons?.length) {
|
|
295
|
+
sections.push({
|
|
296
|
+
__typename: 'GenAIUnifiedResponseSection',
|
|
297
|
+
view_model: {
|
|
298
|
+
primitive: {
|
|
299
|
+
__typename: 'GenAI3PExtWidgetPrimitive',
|
|
300
|
+
header: { __typename: 'GenAI3PExtWidgetStandardHeader', title: title || '' },
|
|
301
|
+
body: {
|
|
302
|
+
__typename: 'GenAI3PExtCalendarEventList',
|
|
303
|
+
ctas: buttons.map((text, buttonIndex) => ({
|
|
304
|
+
label: text,
|
|
305
|
+
state: 'PENDING',
|
|
306
|
+
kind: 'OTHER',
|
|
307
|
+
tool_call_id: `${buttonIndex}`,
|
|
308
|
+
toast: { label: toast, __typename: 'GenAI3PExtWidgetToast' },
|
|
309
|
+
__typename: 'GenAI3PExtWidgetCTA'
|
|
310
|
+
})),
|
|
311
|
+
sections: []
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (footer) {
|
|
321
|
+
const { text = '', url = '', image = {} } = footer ?? {};
|
|
322
|
+
const img = [];
|
|
323
|
+
if (image?.url) {
|
|
324
|
+
img.push({
|
|
325
|
+
__typename: 'GenAIMarkdownTextUXPrimitive',
|
|
326
|
+
text: '{{header}}.{{/header}}',
|
|
327
|
+
inline_entities: [{
|
|
328
|
+
__typename: 'GenAITextInlineEntity',
|
|
329
|
+
key: 'header',
|
|
330
|
+
metadata: {
|
|
331
|
+
__typename: 'GenAILatexItem',
|
|
332
|
+
latex_expression: '.',
|
|
333
|
+
font_height: 24,
|
|
334
|
+
padding: -5,
|
|
335
|
+
latex_image: {
|
|
336
|
+
__typename: 'GenAIMediaItem',
|
|
337
|
+
mime_type: image.mime_type || 'image/png',
|
|
338
|
+
url: image.url,
|
|
339
|
+
url_fallback: image.url,
|
|
340
|
+
width: image.width || 100,
|
|
341
|
+
height: image.height || 100,
|
|
342
|
+
expiration_timestamp_ms: Date.now() + 86400000
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}]
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
sections.push({
|
|
349
|
+
view_model: {
|
|
350
|
+
primitives: [
|
|
351
|
+
{ cta_text: text || '', cta_type: 'OPEN_URL', cta_url: url || '', __typename: 'GenAIFooterActionPrimitive' },
|
|
352
|
+
...img
|
|
353
|
+
],
|
|
354
|
+
__typename: 'GenAIActionRowLayoutViewModel'
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return sendJsonMessage(jid, {
|
|
360
|
+
...messageContextInfo,
|
|
361
|
+
richResponseMessage: {
|
|
362
|
+
unifiedResponse: { data: Buffer.from(JSON.stringify({ sections })).toString('base64') },
|
|
363
|
+
contextInfo: content?.contextInfo ?? {}
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Sends a native quick-reply buttons message.
|
|
370
|
+
* buttons: [{ id: 'unique_id', text: 'Label' }]
|
|
371
|
+
*/
|
|
372
|
+
const sendButtonsMessage = async (jid, { text = '', footer = '', buttons = [] } = {}, options = {}) => {
|
|
373
|
+
const interactiveMessage = {
|
|
374
|
+
body: { text },
|
|
375
|
+
...(footer ? { footer: { text: footer } } : {}),
|
|
376
|
+
nativeFlowMessage: {
|
|
377
|
+
buttons: buttons.map(b => ({
|
|
378
|
+
name: b.name || 'quick_reply',
|
|
379
|
+
buttonParamsJson: JSON.stringify({ display_text: b.text, id: b.id })
|
|
380
|
+
}))
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
return sendJsonMessage(jid, { viewOnceMessage: { message: { interactiveMessage } } }, options);
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Sends a native single-select list message.
|
|
388
|
+
* sections: [{ title: 'Section', rows: [{ title, description, id }] }]
|
|
389
|
+
*/
|
|
390
|
+
const sendListMessage = async (jid, { text = '', footer = '', buttonText = 'Menu', sections = [] } = {}, options = {}) => {
|
|
391
|
+
const interactiveMessage = {
|
|
392
|
+
body: { text },
|
|
393
|
+
...(footer ? { footer: { text: footer } } : {}),
|
|
394
|
+
nativeFlowMessage: {
|
|
395
|
+
buttons: [{
|
|
396
|
+
name: 'single_select',
|
|
397
|
+
buttonParamsJson: JSON.stringify({ title: buttonText, sections })
|
|
398
|
+
}]
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
return sendJsonMessage(jid, { viewOnceMessage: { message: { interactiveMessage } } }, options);
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Sends a native horizontal-scroll carousel of cards (the correct native
|
|
406
|
+
* mechanism for side-by-side buttons — unlike the GenAI rich-response
|
|
407
|
+
* widget, which is designed as a vertical list).
|
|
408
|
+
* cards: [{ image: 'url or local path', mimetype, title, body, buttons: [{ id, text }] }]
|
|
409
|
+
*/
|
|
410
|
+
const sendCarouselMessage = async (jid, { text = '', footer = '', cards = [] } = {}, options = {}) => {
|
|
411
|
+
const cardMessages = [];
|
|
412
|
+
|
|
413
|
+
for (const card of cards) {
|
|
414
|
+
let headerMedia = {};
|
|
415
|
+
if (card.image) {
|
|
416
|
+
const uploaded = await prepareWAMessageMedia(
|
|
417
|
+
{ image: { url: card.image }, mimetype: card.mimetype || 'image/jpeg' },
|
|
418
|
+
{ upload: waUploadToServer }
|
|
419
|
+
);
|
|
420
|
+
headerMedia = { imageMessage: uploaded.imageMessage };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
cardMessages.push({
|
|
424
|
+
header: { title: card.title || '', hasMediaAttachment: !!card.image, ...headerMedia },
|
|
425
|
+
body: { text: card.body || '' },
|
|
426
|
+
nativeFlowMessage: {
|
|
427
|
+
buttons: (card.buttons || []).map(b => ({
|
|
428
|
+
name: b.name || 'quick_reply',
|
|
429
|
+
buttonParamsJson: JSON.stringify({ display_text: b.text, id: b.id })
|
|
430
|
+
}))
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const message = {
|
|
436
|
+
viewOnceMessage: {
|
|
437
|
+
message: {
|
|
438
|
+
interactiveMessage: {
|
|
439
|
+
body: { text },
|
|
440
|
+
...(footer ? { footer: { text: footer } } : {}),
|
|
441
|
+
carouselMessage: { cards: cardMessages, messageVersion: 1 }
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
return sendJsonMessage(jid, message, options);
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Forwards an existing message (e.g. one pulled from the store) to
|
|
451
|
+
* another chat. Delegates to the library's own forward content builder
|
|
452
|
+
* (sendMessage's `forward` content type) so conversation→extendedText
|
|
453
|
+
* conversion, forwardingScore, and isForwarded are handled correctly
|
|
454
|
+
* instead of being reimplemented here.
|
|
455
|
+
*/
|
|
456
|
+
const forwardMessage = async (jid, message, options = {}) => {
|
|
457
|
+
return sendMessage(jid, { forward: message, force: options.force }, options);
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Builds a proper vCard string and sends it as a contact message.
|
|
462
|
+
* contact: { name, phone (E.164, no '+'), organization, waid }
|
|
463
|
+
* If multiple contacts are given, sends a contactsArrayMessage.
|
|
464
|
+
*/
|
|
465
|
+
const sendVCard = async (jid, contacts, options = {}) => {
|
|
466
|
+
const list = Array.isArray(contacts) ? contacts : [contacts];
|
|
467
|
+
const built = list.map(c => {
|
|
468
|
+
const waid = c.waid || c.phone;
|
|
469
|
+
const vcard = 'BEGIN:VCARD\n'
|
|
470
|
+
+ 'VERSION:3.0\n'
|
|
471
|
+
+ `FN:${c.name || 'Unknown'}\n`
|
|
472
|
+
+ (c.organization ? `ORG:${c.organization};\n` : '')
|
|
473
|
+
+ `TEL;type=CELL;type=VOICE;waid=${waid}:+${c.phone}\n`
|
|
474
|
+
+ 'END:VCARD';
|
|
475
|
+
return { displayName: c.name || 'Unknown', vcard };
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
if (built.length === 1) {
|
|
479
|
+
return sendMessage(jid, { contacts: { contacts: built } }, options);
|
|
480
|
+
}
|
|
481
|
+
return sendMessage(jid, { contacts: { displayName: `${built.length} contacts`, contacts: built } }, options);
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Sends the same content to a list of jids sequentially, with an
|
|
486
|
+
* optional delay between sends (helps avoid rate limiting). Returns
|
|
487
|
+
* an array of { jid, ok, result|error } for each recipient.
|
|
488
|
+
* options.delayMs is stripped before being passed to sendMessage.
|
|
489
|
+
*/
|
|
490
|
+
const broadcastMessage = async (jids, content, options = {}) => {
|
|
491
|
+
const { delayMs = 0, ...sendOptions } = options;
|
|
492
|
+
const results = [];
|
|
493
|
+
for (const jid of jids) {
|
|
494
|
+
try {
|
|
495
|
+
const result = await sendMessage(jid, content, sendOptions);
|
|
496
|
+
results.push({ jid, ok: true, result });
|
|
497
|
+
} catch (error) {
|
|
498
|
+
results.push({ jid, ok: false, error });
|
|
499
|
+
}
|
|
500
|
+
if (delayMs) await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
501
|
+
}
|
|
502
|
+
return results;
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
...sock,
|
|
507
|
+
sendJsonMessage,
|
|
508
|
+
sendActionPoll,
|
|
509
|
+
resolvePollAction,
|
|
510
|
+
sendAlbumMessage,
|
|
511
|
+
sendStatusMention,
|
|
512
|
+
sendRichResponse,
|
|
513
|
+
sendButtonsMessage,
|
|
514
|
+
sendListMessage,
|
|
515
|
+
sendCarouselMessage,
|
|
516
|
+
sendHtml,
|
|
517
|
+
forwardMessage,
|
|
518
|
+
sendVCard,
|
|
519
|
+
broadcastMessage
|
|
520
|
+
};
|
|
521
|
+
};
|