sauruspro-bail 5.0.0 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -9
- package/docs/CHANGELOG.md +25 -4
- package/docs/SPR.md +2 -2
- package/lib/Defaults/index.js +5 -1
- package/lib/Socket/dinonic-handler.d.ts +27 -0
- package/lib/Socket/dinonic-handler.js +882 -0
- package/lib/Socket/index.js +5 -1
- package/lib/Socket/messages-recv.js +26 -0
- package/lib/Socket/messages-send.js +25 -0
- package/lib/Utils/MessageBuilder.d.ts +215 -0
- package/lib/Utils/MessageBuilder.js +3991 -0
- package/lib/Utils/a2ui.d.ts +202 -0
- package/lib/Utils/a2ui.js +732 -0
- package/lib/Utils/airich-registry.js +3 -0
- package/lib/Utils/baileys-event-stream.js +58 -0
- package/lib/Utils/bot-signature.d.ts +22 -0
- package/lib/Utils/bot-signature.js +124 -0
- package/lib/Utils/{dinolith-spr.js → dinonic-spr.js} +1 -1
- package/lib/Utils/extras.d.ts +449 -0
- package/lib/Utils/extras.js +1377 -0
- package/lib/Utils/html-app.d.ts +39 -0
- package/lib/Utils/html-app.js +291 -0
- package/lib/Utils/index.js +14 -2
- package/lib/Utils/metaai.d.ts +438 -0
- package/lib/Utils/metaai.js +911 -0
- package/lib/Utils/modern-messages.d.ts +150 -0
- package/lib/Utils/modern-messages.js +496 -0
- package/lib/Utils/native-flow.d.ts +14 -0
- package/lib/Utils/native-flow.js +118 -0
- package/lib/Utils/newsletter-status.js +9 -53
- package/lib/Utils/payment-messages.d.ts +59 -0
- package/lib/Utils/payment-messages.js +156 -0
- package/lib/Utils/scheduled-message.d.ts +82 -0
- package/lib/Utils/scheduled-message.js +113 -0
- package/lib/Utils/status-stickers.d.ts +78 -0
- package/lib/Utils/status-stickers.js +261 -0
- package/lib/index.js +2 -2
- package/package.json +4 -4
- /package/lib/Utils/{dinolith-spr.d.ts → dinonic-spr.d.ts} +0 -0
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
import { generateWAMessageFromContent } from './messages.js';
|
|
2
|
+
import { prepareWAMessageMedia } from './messages.js';
|
|
3
|
+
import * as crypto from 'crypto';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
const A2UI_VERSION = 'v0.9';
|
|
6
|
+
|
|
7
|
+
const A2UI_BASIC_CATALOG = 'https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json';
|
|
8
|
+
|
|
9
|
+
const A2UI_ROOT_ID = 'root';
|
|
10
|
+
|
|
11
|
+
const BLOKS_A2UI_TYPE = 'im_a2ui';
|
|
12
|
+
|
|
13
|
+
const BLOKS_A2UI_REPLY_ACTION = 'a2ui_reply_action';
|
|
14
|
+
|
|
15
|
+
const BLOKS_A2UI_SUPPORTED_ELEMENTS = Object.freeze(['info_card', 'list_card']);
|
|
16
|
+
|
|
17
|
+
const trimEmpty = (object) => {
|
|
18
|
+
for (const key of Object.keys(object)) {
|
|
19
|
+
if (object[key] === undefined) {
|
|
20
|
+
delete object[key];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return object;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const bloksPayloadData = (data) => {
|
|
27
|
+
if (data === undefined || data === null) {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
if (typeof data === 'string') {
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
if (typeof data !== 'object' || Array.isArray(data)) {
|
|
34
|
+
throw new TypeError('bloks data must be a JSON string or a plain object');
|
|
35
|
+
}
|
|
36
|
+
return JSON.stringify(data);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const bloksType = (type, caller) => {
|
|
40
|
+
if (typeof type !== 'string' || type.trim() === '') {
|
|
41
|
+
throw new TypeError(caller + ' requires a non-empty bloks type');
|
|
42
|
+
}
|
|
43
|
+
return type;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const bloksSection = (type, data, { uuid, initialResponse = '', versioningId = '' } = {}) => ({
|
|
47
|
+
view_model: {
|
|
48
|
+
layout_type: 'Single',
|
|
49
|
+
primitive: {
|
|
50
|
+
type: bloksType(type, 'bloksSection'),
|
|
51
|
+
data: bloksPayloadData(data),
|
|
52
|
+
uuid: uuid ?? crypto.randomUUID(),
|
|
53
|
+
initial_response: String(initialResponse),
|
|
54
|
+
versioning_id: String(versioningId),
|
|
55
|
+
__typename: 'FOABloksPrimitive'
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const bloksWidget = ({ type, data, uuid, fallback = '' } = {}) => ({
|
|
61
|
+
type: bloksType(type, 'bloksWidget'),
|
|
62
|
+
data: bloksPayloadData(data),
|
|
63
|
+
uuid: uuid ?? crypto.randomUUID(),
|
|
64
|
+
fallback: String(fallback)
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const a2uiNode = (component) => (id, extra = {}) => {
|
|
68
|
+
if (typeof id !== 'string' || id.trim() === '') {
|
|
69
|
+
throw new TypeError('every a2ui component needs a non-empty id');
|
|
70
|
+
}
|
|
71
|
+
return trimEmpty({ id, component, ...extra });
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const a2uiText = (id, text, { variant = 'body' } = {}) =>
|
|
75
|
+
a2uiNode('Text')(id, { text: String(text), variant });
|
|
76
|
+
|
|
77
|
+
const a2uiImage = (id, url, { variant = 'header', fit = 'cover' } = {}) => {
|
|
78
|
+
if (typeof url !== 'string' || url.trim() === '') {
|
|
79
|
+
throw new TypeError('a2uiImage requires a url');
|
|
80
|
+
}
|
|
81
|
+
return a2uiNode('Image')(id, { url, variant, fit });
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const a2uiContainer = (component) => (id, children = []) => {
|
|
85
|
+
if (!Array.isArray(children)) {
|
|
86
|
+
throw new TypeError('a2ui container children must be an array of component ids');
|
|
87
|
+
}
|
|
88
|
+
return a2uiNode(component)(id, { children: children.map(String) });
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const a2uiColumn = a2uiContainer('Column');
|
|
92
|
+
|
|
93
|
+
const a2uiRow = a2uiContainer('Row');
|
|
94
|
+
|
|
95
|
+
const a2uiCard = (id, child) => {
|
|
96
|
+
if (typeof child !== 'string' || child.trim() === '') {
|
|
97
|
+
throw new TypeError('a2uiCard takes the id of one child component, not an array');
|
|
98
|
+
}
|
|
99
|
+
return a2uiNode('Card')(id, { child });
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Standard action payload shared by every interactive component (Button,
|
|
104
|
+
* CheckBox, Chip, MultipleChoice, Tabs, ...). It is what gets echoed back
|
|
105
|
+
* through BLOKS_A2UI_REPLY_ACTION when the user interacts with the surface.
|
|
106
|
+
*/
|
|
107
|
+
const a2uiAction = (action, data = {}) => {
|
|
108
|
+
if (typeof action !== 'string' || action.trim() === '') {
|
|
109
|
+
throw new TypeError('a2uiAction requires a non-empty action name');
|
|
110
|
+
}
|
|
111
|
+
return trimEmpty({ action, data });
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const a2uiHeading = (id, text, { level = 1, variant } = {}) =>
|
|
115
|
+
a2uiNode('Heading')(id, { text: String(text), level: Number(level) || 1, variant });
|
|
116
|
+
|
|
117
|
+
const a2uiButton = (id, label, { style = 'filled', enabled = true, action, icon } = {}) => {
|
|
118
|
+
if (typeof label !== 'string' || label.trim() === '') {
|
|
119
|
+
throw new TypeError('a2uiButton requires a label');
|
|
120
|
+
}
|
|
121
|
+
return a2uiNode('Button')(id, {
|
|
122
|
+
label,
|
|
123
|
+
style,
|
|
124
|
+
enabled: !!enabled,
|
|
125
|
+
icon,
|
|
126
|
+
action: action ? a2uiAction(action.action ?? action, action.data) : a2uiAction(BLOKS_A2UI_REPLY_ACTION, { id })
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const a2uiDivider = (id, { axis = 'horizontal' } = {}) =>
|
|
131
|
+
a2uiNode('Divider')(id, { axis });
|
|
132
|
+
|
|
133
|
+
const a2uiSpacer = (id, { size = 8 } = {}) =>
|
|
134
|
+
a2uiNode('Spacer')(id, { size: Number(size) || 0 });
|
|
135
|
+
|
|
136
|
+
const a2uiIcon = (id, name, { size = 24, color } = {}) => {
|
|
137
|
+
if (typeof name !== 'string' || name.trim() === '') {
|
|
138
|
+
throw new TypeError('a2uiIcon requires an icon name');
|
|
139
|
+
}
|
|
140
|
+
return a2uiNode('Icon')(id, { name, size: Number(size) || 24, color });
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const a2uiCheckBox = (id, label, { checked = false, value, action } = {}) =>
|
|
144
|
+
a2uiNode('CheckBox')(id, {
|
|
145
|
+
label: label !== undefined ? String(label) : undefined,
|
|
146
|
+
checked: !!checked,
|
|
147
|
+
value: value ?? id,
|
|
148
|
+
action: action ? a2uiAction(action.action ?? action, action.data) : a2uiAction(BLOKS_A2UI_REPLY_ACTION, { id })
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const a2uiTextField = (id, { label, placeholder, value = '', multiline = false, type = 'text' } = {}) =>
|
|
152
|
+
a2uiNode('TextField')(id, { label, placeholder, value: String(value), multiline: !!multiline, type });
|
|
153
|
+
|
|
154
|
+
const a2uiChip = (id, label, { selected = false, value, action } = {}) => {
|
|
155
|
+
if (typeof label !== 'string' || label.trim() === '') {
|
|
156
|
+
throw new TypeError('a2uiChip requires a label');
|
|
157
|
+
}
|
|
158
|
+
return a2uiNode('Chip')(id, {
|
|
159
|
+
label,
|
|
160
|
+
selected: !!selected,
|
|
161
|
+
value: value ?? id,
|
|
162
|
+
action: action ? a2uiAction(action.action ?? action, action.data) : a2uiAction(BLOKS_A2UI_REPLY_ACTION, { id })
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const a2uiList = (id, children = [], { axis = 'vertical', scrollable = true } = {}) => {
|
|
167
|
+
if (!Array.isArray(children)) {
|
|
168
|
+
throw new TypeError('a2uiList children must be an array of component ids');
|
|
169
|
+
}
|
|
170
|
+
return a2uiNode('List')(id, { axis, scrollable: !!scrollable, children: children.map(String) });
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* options: [{ id, label, value }]
|
|
175
|
+
*/
|
|
176
|
+
const a2uiMultipleChoice = (id, options = [], { selected = [], multiple = false, action } = {}) => {
|
|
177
|
+
if (!Array.isArray(options) || options.length === 0) {
|
|
178
|
+
throw new TypeError('a2uiMultipleChoice requires at least one option');
|
|
179
|
+
}
|
|
180
|
+
return a2uiNode('MultipleChoice')(id, {
|
|
181
|
+
multiple: !!multiple,
|
|
182
|
+
selected: Array.isArray(selected) ? selected.map(String) : [String(selected)],
|
|
183
|
+
options: options.map(opt => trimEmpty({ id: String(opt.id ?? opt.value), label: String(opt.label ?? opt.id), value: opt.value ?? opt.id })),
|
|
184
|
+
action: action ? a2uiAction(action.action ?? action, action.data) : a2uiAction(BLOKS_A2UI_REPLY_ACTION, { id })
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* tabs: [{ id, label, child }] where child is the id of the component shown for that tab.
|
|
190
|
+
*/
|
|
191
|
+
const a2uiTabs = (id, tabs = [], { selected } = {}) => {
|
|
192
|
+
if (!Array.isArray(tabs) || tabs.length === 0) {
|
|
193
|
+
throw new TypeError('a2uiTabs requires at least one tab');
|
|
194
|
+
}
|
|
195
|
+
const normalized = tabs.map(tab => {
|
|
196
|
+
if (!tab || typeof tab.child !== 'string') {
|
|
197
|
+
throw new TypeError('each a2uiTabs entry needs a child component id');
|
|
198
|
+
}
|
|
199
|
+
return trimEmpty({ id: String(tab.id), label: String(tab.label ?? tab.id), child: tab.child });
|
|
200
|
+
});
|
|
201
|
+
return a2uiNode('Tabs')(id, { tabs: normalized, selected: selected ?? normalized[0].id });
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const a2uiModal = (id, child, { title, dismissible = true } = {}) => {
|
|
205
|
+
if (typeof child !== 'string' || child.trim() === '') {
|
|
206
|
+
throw new TypeError('a2uiModal takes the id of one child component');
|
|
207
|
+
}
|
|
208
|
+
return a2uiNode('Modal')(id, { child, title, dismissible: !!dismissible });
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const a2uiSurface = (components, { surfaceId, catalogId = A2UI_BASIC_CATALOG, sendDataModel = false, version = A2UI_VERSION } = {}) => {
|
|
212
|
+
if (!Array.isArray(components) || components.length === 0) {
|
|
213
|
+
throw new TypeError('a2uiSurface requires at least one component');
|
|
214
|
+
}
|
|
215
|
+
if (!components.some(component => component?.id === A2UI_ROOT_ID)) {
|
|
216
|
+
throw new TypeError('a2ui components must include one with id "' + A2UI_ROOT_ID + '"');
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
version,
|
|
220
|
+
createSurface: {
|
|
221
|
+
surfaceId: surfaceId ?? 'card-' + crypto.randomUUID(),
|
|
222
|
+
catalogId,
|
|
223
|
+
sendDataModel: !!sendDataModel,
|
|
224
|
+
components
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const a2uiWidget = (components, { uuid, surfaceId, catalogId, sendDataModel, version, fallback = '' } = {}) => {
|
|
230
|
+
const id = uuid ?? crypto.randomUUID();
|
|
231
|
+
return bloksWidget({
|
|
232
|
+
type: BLOKS_A2UI_TYPE,
|
|
233
|
+
uuid: id,
|
|
234
|
+
fallback,
|
|
235
|
+
data: a2uiSurface(components, { surfaceId: surfaceId ?? 'card-' + id, catalogId, sendDataModel, version })
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const buildA2UIDefaultBizNodes = () => [
|
|
240
|
+
{
|
|
241
|
+
tag: 'biz',
|
|
242
|
+
attrs: {},
|
|
243
|
+
content: [
|
|
244
|
+
{
|
|
245
|
+
tag: 'interactive',
|
|
246
|
+
attrs: { type: 'native_flow', v: '1' },
|
|
247
|
+
content: [{ tag: 'native_flow', attrs: { v: '9', name: 'mixed' } }]
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
}
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
class A2UI {
|
|
254
|
+
constructor({
|
|
255
|
+
catalogId = A2UI_BASIC_CATALOG,
|
|
256
|
+
version = A2UI_VERSION
|
|
257
|
+
} = {}) {
|
|
258
|
+
this._version = version;
|
|
259
|
+
this._catalogId = catalogId;
|
|
260
|
+
this._components = new Map();
|
|
261
|
+
this._counter = 0;
|
|
262
|
+
this._rootChildren = [];
|
|
263
|
+
this._headerImageUrl = null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
#nextId(prefix) {
|
|
267
|
+
return `${prefix}_${(this._counter++).toString(36)}`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
#reg(id, component, extra = {}) {
|
|
271
|
+
id ??= this.#nextId(component.toLowerCase());
|
|
272
|
+
if (this._components.has(id)) throw new Error(`Component id "${id}" already used`);
|
|
273
|
+
this._components.set(id, {
|
|
274
|
+
id,
|
|
275
|
+
component,
|
|
276
|
+
...extra
|
|
277
|
+
});
|
|
278
|
+
return id;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
setImage(urlOrBuffer) {
|
|
282
|
+
this._headerImageUrl = urlOrBuffer;
|
|
283
|
+
return this;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
text(text, { id, variant = "body" } = {}) {
|
|
287
|
+
return this.#reg(id, "Text", { text, variant });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
image(url, { id, variant = "header", fit = "cover" } = {}) {
|
|
291
|
+
this._headerImageUrl = url;
|
|
292
|
+
return this.#reg(id, "Image", {
|
|
293
|
+
url,
|
|
294
|
+
...(variant ? { variant } : {}),
|
|
295
|
+
fit
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
video(url, { id } = {}) {
|
|
300
|
+
return this.#reg(id, "Video", { url });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
checkbox(label, { id, value = false } = {}) {
|
|
304
|
+
return this.#reg(id, "CheckBox", { label, value });
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
textField(label, { id, variant = "text" } = {}) {
|
|
308
|
+
return this.#reg(id, "TextField", { label, variant });
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
button(childId, { id, variant = "primary", action } = {}) {
|
|
312
|
+
if (!childId) throw new TypeError("button(childId) requires the id of a child component (e.g. from .text())");
|
|
313
|
+
return this.#reg(id, "Button", {
|
|
314
|
+
child: childId,
|
|
315
|
+
variant,
|
|
316
|
+
...(action ? { action } : {})
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
card(childId, { id } = {}) {
|
|
321
|
+
return this.#reg(id, "Card", { child: childId });
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
column(children = [], { id, justify, align } = {}) {
|
|
325
|
+
if (!children.length) throw new TypeError("column(children) requires at least one child id");
|
|
326
|
+
return this.#reg(id, "Column", {
|
|
327
|
+
children,
|
|
328
|
+
...(justify ? { justify } : {}),
|
|
329
|
+
...(align ? { align } : {})
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
row(children = [], { id } = {}) {
|
|
334
|
+
if (!children.length) throw new TypeError("row(children) requires at least one child id");
|
|
335
|
+
return this.#reg(id, "Row", { children });
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
divider({ id } = {}) {
|
|
339
|
+
return this.#reg(id, "Divider", {});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
choicePicker(label, options, {
|
|
343
|
+
id,
|
|
344
|
+
variant = "mutuallyExclusive",
|
|
345
|
+
value,
|
|
346
|
+
displayStyle = "checkbox",
|
|
347
|
+
filterable = false
|
|
348
|
+
} = {}) {
|
|
349
|
+
if (!Array.isArray(options) || !options.length) {
|
|
350
|
+
throw new TypeError("choicePicker(label, options) requires a non-empty options array of {label, value}");
|
|
351
|
+
}
|
|
352
|
+
return this.#reg(id, "ChoicePicker", {
|
|
353
|
+
label,
|
|
354
|
+
variant,
|
|
355
|
+
...(value !== undefined ? { value } : {}),
|
|
356
|
+
options,
|
|
357
|
+
displayStyle,
|
|
358
|
+
filterable,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
root(children) {
|
|
363
|
+
if (!Array.isArray(children) || !children.length) {
|
|
364
|
+
throw new TypeError("root(children) requires a non-empty array of top-level component ids");
|
|
365
|
+
}
|
|
366
|
+
this._rootChildren = children;
|
|
367
|
+
return this;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
listCard({ title, items, fallbackText, uuid = crypto.randomUUID() } = {}) {
|
|
371
|
+
if (!title) throw new TypeError("listCard requires a title");
|
|
372
|
+
if (!Array.isArray(items) || !items.length) {
|
|
373
|
+
throw new TypeError("listCard requires a non-empty items array");
|
|
374
|
+
}
|
|
375
|
+
this._listCardPayload = {
|
|
376
|
+
uuid,
|
|
377
|
+
data: JSON.stringify({
|
|
378
|
+
type: "list_card",
|
|
379
|
+
title,
|
|
380
|
+
fallback_text: fallbackText ?? "",
|
|
381
|
+
items: items.map((it) => {
|
|
382
|
+
if (typeof it === "string") return { title: it, action: "" };
|
|
383
|
+
if (!it?.title) throw new TypeError("listCard item must have at least {title}");
|
|
384
|
+
return {
|
|
385
|
+
title: it.title,
|
|
386
|
+
subtitle: it.subtitle ?? "",
|
|
387
|
+
action: it.action ?? "",
|
|
388
|
+
};
|
|
389
|
+
}),
|
|
390
|
+
}),
|
|
391
|
+
type: BLOKS_A2UI_TYPE
|
|
392
|
+
};
|
|
393
|
+
return this;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
build({ type = BLOKS_A2UI_TYPE, wrapped = true } = {}) {
|
|
397
|
+
if (this._listCardPayload) return this._listCardPayload;
|
|
398
|
+
if (!this._rootChildren.length) {
|
|
399
|
+
throw new Error("No root children set. Call .root([id1, id2...]) before build().");
|
|
400
|
+
}
|
|
401
|
+
const rootComp = {
|
|
402
|
+
id: "root",
|
|
403
|
+
component: "Column",
|
|
404
|
+
children: this._rootChildren
|
|
405
|
+
};
|
|
406
|
+
const components = [rootComp, ...this._components.values()];
|
|
407
|
+
const uuid = crypto.randomUUID();
|
|
408
|
+
const data = {
|
|
409
|
+
version: this._version,
|
|
410
|
+
createSurface: {
|
|
411
|
+
surfaceId: `starcore-widget=${uuid}`,
|
|
412
|
+
catalogId: this._catalogId,
|
|
413
|
+
components
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
return {
|
|
417
|
+
uuid,
|
|
418
|
+
data: JSON.stringify(data),
|
|
419
|
+
type
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async send(client, jid, options = {}) {
|
|
424
|
+
return sendA2UIWidget(client, jid, { a2ui: this, ...options });
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Static helpers attached to A2UI class
|
|
429
|
+
A2UI.text = a2uiText;
|
|
430
|
+
A2UI.image = a2uiImage;
|
|
431
|
+
A2UI.column = a2uiColumn;
|
|
432
|
+
A2UI.row = a2uiRow;
|
|
433
|
+
A2UI.card = a2uiCard;
|
|
434
|
+
A2UI.surface = a2uiSurface;
|
|
435
|
+
A2UI.widget = a2uiWidget;
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
async function sendA2UIWidget(client, jid, {
|
|
439
|
+
a2ui,
|
|
440
|
+
bodyText = "",
|
|
441
|
+
footer = "",
|
|
442
|
+
buttons = [],
|
|
443
|
+
contextInfo = {},
|
|
444
|
+
expiration,
|
|
445
|
+
quoted,
|
|
446
|
+
type = BLOKS_A2UI_TYPE,
|
|
447
|
+
wrapped = true,
|
|
448
|
+
singleScreen = false,
|
|
449
|
+
bottom_sheet = {},
|
|
450
|
+
limited_time_offer = {},
|
|
451
|
+
headerImage,
|
|
452
|
+
image,
|
|
453
|
+
thumbnail,
|
|
454
|
+
photo,
|
|
455
|
+
headerTitle,
|
|
456
|
+
headerSubtitle,
|
|
457
|
+
additionalNodes = []
|
|
458
|
+
} = {}) {
|
|
459
|
+
if (!client) throw new Error("Socket is required");
|
|
460
|
+
if (!(a2ui instanceof A2UI)) throw new TypeError("a2ui must be an A2UI instance");
|
|
461
|
+
|
|
462
|
+
let imageSource = headerImage || image || thumbnail || photo;
|
|
463
|
+
if (!imageSource && a2ui) {
|
|
464
|
+
if (a2ui._headerImageUrl) {
|
|
465
|
+
imageSource = a2ui._headerImageUrl;
|
|
466
|
+
} else if (a2ui._components) {
|
|
467
|
+
for (const comp of a2ui._components.values()) {
|
|
468
|
+
if (comp.component === "Image" && comp.url) {
|
|
469
|
+
imageSource = comp.url;
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
let preparedImageMessage = null;
|
|
477
|
+
if (imageSource) {
|
|
478
|
+
if (typeof imageSource === "object" && imageSource.imageMessage) {
|
|
479
|
+
preparedImageMessage = imageSource.imageMessage;
|
|
480
|
+
} else if (typeof imageSource === "object" && imageSource.directPath && imageSource.mediaKey) {
|
|
481
|
+
preparedImageMessage = imageSource;
|
|
482
|
+
} else {
|
|
483
|
+
try {
|
|
484
|
+
let imgBuffer = null;
|
|
485
|
+
if (Buffer.isBuffer(imageSource)) {
|
|
486
|
+
imgBuffer = imageSource;
|
|
487
|
+
} else if (typeof imageSource === "string") {
|
|
488
|
+
if (imageSource.startsWith("http://") || imageSource.startsWith("https://")) {
|
|
489
|
+
try {
|
|
490
|
+
const res = await fetch(imageSource, { signal: AbortSignal.timeout(15000) });
|
|
491
|
+
imgBuffer = Buffer.from(await res.arrayBuffer());
|
|
492
|
+
} catch (_) {}
|
|
493
|
+
} else if (imageSource.startsWith("data:image")) {
|
|
494
|
+
const base64Data = imageSource.split(",")[1] || imageSource;
|
|
495
|
+
imgBuffer = Buffer.from(base64Data, "base64");
|
|
496
|
+
} else if (fs.existsSync(imageSource)) {
|
|
497
|
+
imgBuffer = fs.readFileSync(imageSource);
|
|
498
|
+
}
|
|
499
|
+
} else if (imageSource.image && Buffer.isBuffer(imageSource.image)) {
|
|
500
|
+
imgBuffer = imageSource.image;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (imgBuffer && imgBuffer.length) {
|
|
504
|
+
const media = await prepareWAMessageMedia(
|
|
505
|
+
{ image: imgBuffer },
|
|
506
|
+
{ upload: client.waUploadToServer }
|
|
507
|
+
);
|
|
508
|
+
preparedImageMessage = media?.imageMessage || null;
|
|
509
|
+
}
|
|
510
|
+
} catch (err) {
|
|
511
|
+
console.warn("[sendA2UIWidget prepareImage error]", err?.message || err);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const header = preparedImageMessage ?
|
|
517
|
+
{
|
|
518
|
+
hasMediaAttachment: true,
|
|
519
|
+
imageMessage: preparedImageMessage,
|
|
520
|
+
...(headerTitle ? { title: headerTitle } : {}),
|
|
521
|
+
...(headerSubtitle ? { subtitle: headerSubtitle } : {})
|
|
522
|
+
} :
|
|
523
|
+
(headerTitle ?
|
|
524
|
+
{
|
|
525
|
+
hasMediaAttachment: false,
|
|
526
|
+
title: headerTitle,
|
|
527
|
+
...(headerSubtitle ? { subtitle: headerSubtitle } : {})
|
|
528
|
+
} :
|
|
529
|
+
(footer !== undefined ? {
|
|
530
|
+
hasMediaAttachment: false
|
|
531
|
+
} : undefined)
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
const params = {};
|
|
535
|
+
const nativeFlowMessage = buttons && buttons.length ?
|
|
536
|
+
{
|
|
537
|
+
buttons: buttons.map((b) => {
|
|
538
|
+
if (!b || Object.keys(b).length === 0 || b.name === '') {
|
|
539
|
+
return {};
|
|
540
|
+
}
|
|
541
|
+
return {
|
|
542
|
+
name: b.name ?? "cta_url",
|
|
543
|
+
buttonParamsJson: typeof b.params === "string"
|
|
544
|
+
? b.params
|
|
545
|
+
: typeof b.buttonParamsJson === "string"
|
|
546
|
+
? b.buttonParamsJson
|
|
547
|
+
: JSON.stringify(b.params ?? b.buttonParamsJson ?? {}),
|
|
548
|
+
};
|
|
549
|
+
}),
|
|
550
|
+
messageParamsJson: "{}",
|
|
551
|
+
messageVersion: 1
|
|
552
|
+
} :
|
|
553
|
+
{
|
|
554
|
+
buttons: [{}],
|
|
555
|
+
messageParamsJson: "{}",
|
|
556
|
+
messageVersion: 1
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
if (typeof bottom_sheet === "object" && bottom_sheet?.name) {
|
|
560
|
+
params.bottom_sheet = {
|
|
561
|
+
in_thread_buttons_limit: 1,
|
|
562
|
+
divider_indices: [1, 2],
|
|
563
|
+
list_title: bottom_sheet.name,
|
|
564
|
+
button_title: bottom_sheet.name
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
if (typeof limited_time_offer === "object" && limited_time_offer?.time) {
|
|
568
|
+
params.limited_time_offer = {
|
|
569
|
+
text: limited_time_offer.text || "",
|
|
570
|
+
url: '',
|
|
571
|
+
copy_code: '',
|
|
572
|
+
expiration_time: Date.now() + limited_time_offer.time
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
nativeFlowMessage.messageParamsJson = JSON.stringify(params);
|
|
576
|
+
|
|
577
|
+
const mergedContextInfo = {
|
|
578
|
+
...(typeof global !== 'undefined' && global.owner ? {
|
|
579
|
+
businessMessageForwardInfo: { businessOwnerJid: global.owner + '@s.whatsapp.net' }
|
|
580
|
+
} : {}),
|
|
581
|
+
...contextInfo
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
const interactiveMessage = singleScreen ?
|
|
585
|
+
{
|
|
586
|
+
...(header ? { header } : {}),
|
|
587
|
+
nativeFlowMessage,
|
|
588
|
+
bloksWidget: a2ui.build({
|
|
589
|
+
type,
|
|
590
|
+
wrapped
|
|
591
|
+
}),
|
|
592
|
+
...(expiration || Object.keys(mergedContextInfo).length ? {
|
|
593
|
+
contextInfo: {
|
|
594
|
+
...(expiration ? { expiration } : {}),
|
|
595
|
+
...mergedContextInfo
|
|
596
|
+
}
|
|
597
|
+
} : {})
|
|
598
|
+
} :
|
|
599
|
+
{
|
|
600
|
+
...(header ? { header } : {}),
|
|
601
|
+
...(bodyText !== undefined && bodyText !== "" ? {
|
|
602
|
+
body: { text: bodyText }
|
|
603
|
+
} : {}),
|
|
604
|
+
...(footer !== undefined && footer !== "" ? {
|
|
605
|
+
footer: { text: footer }
|
|
606
|
+
} : {}),
|
|
607
|
+
nativeFlowMessage,
|
|
608
|
+
bloksWidget: a2ui.build({
|
|
609
|
+
type,
|
|
610
|
+
wrapped
|
|
611
|
+
}),
|
|
612
|
+
...(expiration || Object.keys(mergedContextInfo).length ? {
|
|
613
|
+
contextInfo: {
|
|
614
|
+
...(expiration ? { expiration } : {}),
|
|
615
|
+
...mergedContextInfo
|
|
616
|
+
}
|
|
617
|
+
} : {})
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const msg = generateWAMessageFromContent(jid, {
|
|
621
|
+
messageContextInfo: {
|
|
622
|
+
messageSecret: crypto.randomBytes(32)
|
|
623
|
+
},
|
|
624
|
+
interactiveMessage
|
|
625
|
+
}, {
|
|
626
|
+
quoted
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
const finalAdditionalNodes = additionalNodes && additionalNodes.length > 0
|
|
630
|
+
? additionalNodes
|
|
631
|
+
: buildA2UIDefaultBizNodes();
|
|
632
|
+
|
|
633
|
+
await client.relayMessage(msg.key.remoteJid, msg.message, {
|
|
634
|
+
messageId: msg.key.id,
|
|
635
|
+
additionalNodes: finalAdditionalNodes,
|
|
636
|
+
...options
|
|
637
|
+
});
|
|
638
|
+
return msg;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
async function sendA2UI(sock, jid, components, { buttons = [], contextInfo, messageId, additionalNodes = [], ...options } = {}) {
|
|
642
|
+
if (!sock) {
|
|
643
|
+
throw new TypeError('sendA2UI requires a socket as the first argument');
|
|
644
|
+
}
|
|
645
|
+
if (!jid) {
|
|
646
|
+
throw new TypeError('sendA2UI requires a target jid');
|
|
647
|
+
}
|
|
648
|
+
if (!Array.isArray(buttons)) {
|
|
649
|
+
throw new TypeError('sendA2UI buttons must be an array of native flow buttons');
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const widget = a2uiWidget(components, options);
|
|
653
|
+
|
|
654
|
+
const mergedContextInfo = {
|
|
655
|
+
...(typeof global !== 'undefined' && global.owner ? {
|
|
656
|
+
businessMessageForwardInfo: { businessOwnerJid: global.owner + '@s.whatsapp.net' }
|
|
657
|
+
} : {}),
|
|
658
|
+
...contextInfo
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
const msg = generateWAMessageFromContent(
|
|
662
|
+
jid,
|
|
663
|
+
{
|
|
664
|
+
interactiveMessage: trimEmpty({
|
|
665
|
+
nativeFlowMessage: { buttons: buttons.length ? buttons : [{}], messageParamsJson: JSON.stringify({}), messageVersion: 1 },
|
|
666
|
+
bloksWidget: widget,
|
|
667
|
+
contextInfo: mergedContextInfo
|
|
668
|
+
})
|
|
669
|
+
},
|
|
670
|
+
{ messageId, ...options }
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
const finalNodes = additionalNodes && additionalNodes.length > 0
|
|
674
|
+
? additionalNodes
|
|
675
|
+
: buildA2UIDefaultBizNodes();
|
|
676
|
+
|
|
677
|
+
await sock.relayMessage(msg.key.remoteJid, msg.message, {
|
|
678
|
+
messageId: msg.key.id,
|
|
679
|
+
additionalNodes: finalNodes,
|
|
680
|
+
...options
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
return msg;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
async function sendBloksWidget(sock, jid, { type, data, uuid, fallback = '', body, contextInfo, messageId, additionalNodes = [], ...options } = {}) {
|
|
687
|
+
if (!sock) {
|
|
688
|
+
throw new TypeError('sendBloksWidget requires a socket as the first argument');
|
|
689
|
+
}
|
|
690
|
+
if (!jid) {
|
|
691
|
+
throw new TypeError('sendBloksWidget requires a target jid');
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const widget = bloksWidget({ type, data, uuid, fallback });
|
|
695
|
+
|
|
696
|
+
const mergedContextInfo = {
|
|
697
|
+
...(typeof global !== 'undefined' && global.owner ? {
|
|
698
|
+
businessMessageForwardInfo: { businessOwnerJid: global.owner + '@s.whatsapp.net' }
|
|
699
|
+
} : {}),
|
|
700
|
+
...contextInfo
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
const msg = generateWAMessageFromContent(
|
|
704
|
+
jid,
|
|
705
|
+
{
|
|
706
|
+
interactiveMessage: trimEmpty({
|
|
707
|
+
body: body ? { text: String(body) } : undefined,
|
|
708
|
+
bloksWidget: widget,
|
|
709
|
+
contextInfo: mergedContextInfo
|
|
710
|
+
})
|
|
711
|
+
},
|
|
712
|
+
{ messageId, ...options }
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
const finalNodes = additionalNodes && additionalNodes.length > 0
|
|
716
|
+
? additionalNodes
|
|
717
|
+
: buildA2UIDefaultBizNodes();
|
|
718
|
+
|
|
719
|
+
await sock.relayMessage(msg.key.remoteJid, msg.message, {
|
|
720
|
+
messageId: msg.key.id,
|
|
721
|
+
additionalNodes: finalNodes,
|
|
722
|
+
...options
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
return msg;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
A2UI.send = sendA2UI;
|
|
729
|
+
A2UI.sendWidget = sendA2UIWidget;
|
|
730
|
+
A2UI.sendBloksWidget = sendBloksWidget;
|
|
731
|
+
|
|
732
|
+
export { A2UI_VERSION, A2UI_BASIC_CATALOG, A2UI_ROOT_ID, BLOKS_A2UI_TYPE, BLOKS_A2UI_REPLY_ACTION, BLOKS_A2UI_SUPPORTED_ELEMENTS, bloksSection, bloksWidget, a2uiText, a2uiImage, a2uiColumn, a2uiRow, a2uiCard, a2uiAction, a2uiHeading, a2uiButton, a2uiDivider, a2uiSpacer, a2uiIcon, a2uiCheckBox, a2uiTextField, a2uiChip, a2uiList, a2uiMultipleChoice, a2uiTabs, a2uiModal, a2uiSurface, a2uiWidget, A2UI, sendA2UIWidget, sendA2UI, sendBloksWidget };
|