@vanzxy/baileys 2.0.1 → 2.0.3
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/NOTICE.md +1 -2
- package/README.md +46 -3
- package/lib/Builders/AIRich.js +1143 -1103
- package/lib/Builders/Album.js +71 -0
- package/lib/Builders/Contact.js +83 -0
- package/lib/Builders/LinkPreview.js +74 -0
- package/lib/Builders/Location.js +151 -0
- package/lib/Builders/NativeFlow.js +210 -0
- package/lib/Builders/VanzxyBaileys.js +40 -1
- package/lib/Builders/index.js +6 -1
- package/package.json +1 -1
package/lib/Builders/AIRich.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { BaseBuilder, Toolkit, extractIE, waitAllPromises,
|
|
1
|
+
import { BaseBuilder, Toolkit, extractIE, waitAllPromises, crypto, generateWAMessageFromContent, generateMessageIDV2 } from './shared.js';
|
|
2
|
+
|
|
3
|
+
const VERSION = '5.0';
|
|
4
|
+
|
|
2
5
|
class AIRich extends BaseBuilder {
|
|
3
6
|
#client;
|
|
4
7
|
|
|
5
|
-
constructor(client) {
|
|
8
|
+
constructor(client, { dynamic = true, unsupportedTypeAlert = true } = {}) {
|
|
6
9
|
if (!client) {
|
|
7
10
|
throw new Error('Socket is required');
|
|
8
11
|
}
|
|
@@ -10,204 +13,125 @@ class AIRich extends BaseBuilder {
|
|
|
10
13
|
super();
|
|
11
14
|
this.#client = client;
|
|
12
15
|
this._contextInfo = {};
|
|
13
|
-
this.
|
|
14
|
-
this.
|
|
15
|
-
this.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// Track every addInlineImage() call here so send() can fall back to a normal imageMessage.
|
|
20
|
-
this._inlineImages = [];
|
|
21
|
-
|
|
22
|
-
// Vanz@Add 24-08-26 --- ported from temen's MessageBuilderV4.7 (setResponseId/setBotResponseId
|
|
23
|
-
// below), rewritten to fit this fork's conventions. build() used to always mint a fresh
|
|
24
|
-
// crypto.randomUUID() for both unifiedResponse.response_id and botMetadata.botResponseId on
|
|
25
|
-
// every call, with no way to reuse one — so a `sendEdit()`-style flow (rebuild the same
|
|
26
|
-
// message with updated content, same response_id, so WA patches it in place instead of
|
|
27
|
-
// showing a new message) was never actually possible despite being in the gist example.
|
|
28
|
-
// null here means "not pinned yet" — build() falls back to a fresh randomUUID() same as before
|
|
29
|
-
// when neither setResponseId() nor setBotResponseId() has been called.
|
|
30
|
-
this._responseId = null;
|
|
31
|
-
this._botResponseId = null;
|
|
32
|
-
|
|
33
|
-
// Vanz@Add --- set by send()/sendEdit() after every relay so a follow-up sendEdit(), called
|
|
34
|
-
// with no args, knows which jid/message id to patch in place (matches temen's v4.7 API).
|
|
16
|
+
this._nodes = [];
|
|
17
|
+
this._idIndex = new Map();
|
|
18
|
+
this._unsupportedTypeAlert = !!unsupportedTypeAlert;
|
|
19
|
+
this._dynamic = !!dynamic;
|
|
20
|
+
this._responseId = crypto.randomUUID();
|
|
21
|
+
this._botResponseId = crypto.randomUUID();
|
|
35
22
|
this._lastMessageKey = null;
|
|
23
|
+
}
|
|
36
24
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
const result = orig.apply(target, args);
|
|
77
|
-
return result === target ? receiver : result;
|
|
78
|
-
};
|
|
25
|
+
loadFrom(msg) {
|
|
26
|
+
if (!msg) throw new Error('AI Rich message needed');
|
|
27
|
+
|
|
28
|
+
const message = msg.message ?? msg;
|
|
29
|
+
|
|
30
|
+
let richResponseMessage = message?.botForwardedMessage?.message?.richResponseMessage;
|
|
31
|
+
|
|
32
|
+
if (!richResponseMessage) {
|
|
33
|
+
richResponseMessage = message?.botForwardedMessage?.richResponseMessage;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!richResponseMessage) {
|
|
37
|
+
richResponseMessage = message?.richResponseMessage;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!richResponseMessage) {
|
|
41
|
+
throw new Error('richResponseMessage not found');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const messageContextInfo = message?.messageContextInfo ?? {};
|
|
45
|
+
const botMetadata = messageContextInfo?.botMetadata ?? {};
|
|
46
|
+
|
|
47
|
+
this._title = botMetadata?.messageDisclaimerText ?? '';
|
|
48
|
+
|
|
49
|
+
this._contextInfo = structuredClone(richResponseMessage?.contextInfo ?? {});
|
|
50
|
+
|
|
51
|
+
const loadedSubmessages = Array.isArray(richResponseMessage?.submessages) ? structuredClone(richResponseMessage.submessages) : [];
|
|
52
|
+
|
|
53
|
+
let loadedSections = [];
|
|
54
|
+
|
|
55
|
+
const unifiedData = richResponseMessage?.unifiedResponse?.data;
|
|
56
|
+
|
|
57
|
+
if (unifiedData) {
|
|
58
|
+
try {
|
|
59
|
+
const decoded = Buffer.from(unifiedData, 'base64').toString('utf8');
|
|
60
|
+
const unifiedResponse = JSON.parse(decoded);
|
|
61
|
+
|
|
62
|
+
if (Array.isArray(unifiedResponse?.sections)) {
|
|
63
|
+
loadedSections = structuredClone(unifiedResponse.sections);
|
|
79
64
|
}
|
|
65
|
+
} catch {}
|
|
66
|
+
}
|
|
80
67
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const id = opts?.id;
|
|
84
|
-
const insertAt = opts?.insertAt;
|
|
85
|
-
const replace = opts?.replace;
|
|
86
|
-
|
|
87
|
-
// Vanz@Fix (bug 70) --- `id` reuse across two different add*/set* calls was silently
|
|
88
|
-
// accepted: target._blocks.set(id, ...) below just clobbers the previous registration,
|
|
89
|
-
// so the FIRST block with that id becomes an untracked ghost — still in _sections/
|
|
90
|
-
// _submessages (still renders), but no longer reachable via hasId/peek/delete/replace/
|
|
91
|
-
// insertAt (the id now only resolves to the second block). Confirmed by direct test:
|
|
92
|
-
// addText('first',{id:'dup'}); addText('second',{id:'dup'}) left both in the message
|
|
93
|
-
// but getIds() only ever had one 'dup', pointing at 'second'. Fail fast instead — same
|
|
94
|
-
// as re-registering the same id you're actively `replace`-ing (that's a legitimate
|
|
95
|
-
// "update this block, keep its id" call, not a collision).
|
|
96
|
-
if (id && target._blocks.has(id) && replace !== id) {
|
|
97
|
-
throw new Error(`add*/set*: id "${id}" is already registered — each id must be unique (pass { replace: "${id}" } to update that block instead, or use a different id)`);
|
|
98
|
-
}
|
|
68
|
+
this._nodes = [];
|
|
69
|
+
this._idIndex = new Map();
|
|
99
70
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
// too once JIT/engine specifics changed). Binding to `target` (the real instance) instead
|
|
110
|
-
// fixes it for good; `target._submessages`/`target._sections` below are unaffected since
|
|
111
|
-
// they're plain properties, and `result === target ? receiver : result` still converts a
|
|
112
|
-
// `this`-return back to the Proxy so chaining (`.addX().addY()`) keeps working.
|
|
113
|
-
const result = orig.apply(target, args);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const subItems = target._submessages.splice(subBefore);
|
|
117
|
-
const secItems = target._sections.splice(secBefore);
|
|
118
|
-
|
|
119
|
-
if (insertAt) {
|
|
120
|
-
const anchor = target._blocks.get(insertAt);
|
|
121
|
-
if (!anchor) throw new Error(`insertAt: no block registered with id "${insertAt}" (register it by passing { id: "${insertAt}" } on an earlier add*() call)`);
|
|
122
|
-
|
|
123
|
-
const lastSub = anchor.subItems[anchor.subItems.length - 1];
|
|
124
|
-
const subIdx = lastSub ? target._submessages.indexOf(lastSub) + 1 : target._submessages.length;
|
|
125
|
-
target._submessages.splice(subIdx, 0, ...subItems);
|
|
126
|
-
|
|
127
|
-
const lastSec = anchor.secItems[anchor.secItems.length - 1];
|
|
128
|
-
const secIdx = lastSec ? target._sections.indexOf(lastSec) + 1 : target._sections.length;
|
|
129
|
-
target._sections.splice(secIdx, 0, ...secItems);
|
|
130
|
-
} else if (replace) {
|
|
131
|
-
// replace: delete the old block's items at their current positions,
|
|
132
|
-
// then insert new items at the same positions
|
|
133
|
-
const old = target._blocks.get(replace);
|
|
134
|
-
if (!old) throw new Error(`replace: no block registered with id "${replace}" (register it first with { id: "${replace}" })`);
|
|
135
|
-
|
|
136
|
-
let subIdx = old.subItems.length > 0 ? target._submessages.indexOf(old.subItems[0]) : target._submessages.length;
|
|
137
|
-
if (subIdx === -1) subIdx = target._submessages.length;
|
|
138
|
-
for (const item of old.subItems) {
|
|
139
|
-
const i = target._submessages.indexOf(item);
|
|
140
|
-
if (i !== -1) target._submessages.splice(i, 1);
|
|
141
|
-
}
|
|
142
|
-
target._submessages.splice(subIdx, 0, ...subItems);
|
|
143
|
-
|
|
144
|
-
let secIdx = old.secItems.length > 0 ? target._sections.indexOf(old.secItems[0]) : target._sections.length;
|
|
145
|
-
if (secIdx === -1) secIdx = target._sections.length;
|
|
146
|
-
for (const item of old.secItems) {
|
|
147
|
-
const i = target._sections.indexOf(item);
|
|
148
|
-
if (i !== -1) target._sections.splice(i, 1);
|
|
149
|
-
}
|
|
150
|
-
target._sections.splice(secIdx, 0, ...secItems);
|
|
151
|
-
|
|
152
|
-
target._blocks.delete(replace);
|
|
153
|
-
if (id) target._blocks.set(id, { subItems, secItems });
|
|
154
|
-
else target._blocks.set(replace, { subItems, secItems });
|
|
155
|
-
} else {
|
|
156
|
-
target._submessages.push(...subItems);
|
|
157
|
-
target._sections.push(...secItems);
|
|
158
|
-
}
|
|
71
|
+
const maxLength = Math.max(loadedSections.length, loadedSubmessages.length);
|
|
72
|
+
|
|
73
|
+
for (let i = 0; i < maxLength; i++) {
|
|
74
|
+
this._nodes.push({
|
|
75
|
+
id: null,
|
|
76
|
+
section: loadedSections[i] ?? null,
|
|
77
|
+
submessage: loadedSubmessages[i] ?? null,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
159
80
|
|
|
160
|
-
|
|
81
|
+
this._extraPayload = {};
|
|
161
82
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
83
|
+
for (const [key, value] of Object.entries(message)) {
|
|
84
|
+
if (key !== 'messageContextInfo' && key !== 'botForwardedMessage' && key !== 'richResponseMessage') {
|
|
85
|
+
this._extraPayload[key] = structuredClone(value);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return this;
|
|
166
90
|
}
|
|
167
91
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
});
|
|
92
|
+
setResponseId(id) {
|
|
93
|
+
if (typeof id !== 'string') {
|
|
94
|
+
throw new TypeError('ID must be a string');
|
|
95
|
+
}
|
|
96
|
+
this._responseId = id;
|
|
97
|
+
|
|
98
|
+
return this;
|
|
176
99
|
}
|
|
177
100
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const items = Array.isArray(submessage) ? submessage : [submessage];
|
|
101
|
+
refreshResponseId() {
|
|
102
|
+
this._responseId = crypto.randomUUID();
|
|
181
103
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
throw new TypeError('Submessage must be a plain object or array of plain objects');
|
|
185
|
-
}
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
186
106
|
|
|
187
|
-
|
|
107
|
+
setBotResponseId(id) {
|
|
108
|
+
if (typeof id !== 'string') {
|
|
109
|
+
throw new TypeError('ID must be a string');
|
|
188
110
|
}
|
|
111
|
+
this._botResponseId = id;
|
|
189
112
|
|
|
190
113
|
return this;
|
|
191
114
|
}
|
|
192
115
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const items = Array.isArray(section) ? section : [section];
|
|
116
|
+
refreshBotResponseId() {
|
|
117
|
+
this._botResponseId = crypto.randomUUID();
|
|
196
118
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
throw new TypeError('Section must be a plain object or array of plain objects');
|
|
200
|
-
}
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
201
121
|
|
|
202
|
-
|
|
122
|
+
createAlert(type) {
|
|
123
|
+
if (this._unsupportedTypeAlert) {
|
|
124
|
+
return {
|
|
125
|
+
messageType: 2,
|
|
126
|
+
messageText: `[ UNSUPPORTED_TYPE - ${type}]`,
|
|
127
|
+
};
|
|
203
128
|
}
|
|
204
129
|
|
|
205
|
-
return
|
|
130
|
+
return undefined;
|
|
206
131
|
}
|
|
207
132
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (typeof text != 'string') {
|
|
133
|
+
addText(text, { hyperlink = true, citation = true, latex = true, id, replace, insertAt } = {}) {
|
|
134
|
+
if (typeof text !== 'string') {
|
|
211
135
|
throw new TypeError('Text must be a string');
|
|
212
136
|
}
|
|
213
137
|
|
|
@@ -217,195 +141,185 @@ class AIRich extends BaseBuilder {
|
|
|
217
141
|
latex,
|
|
218
142
|
});
|
|
219
143
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
144
|
+
const section = AIRich.newLayout('Single', {
|
|
145
|
+
text: extractedText,
|
|
146
|
+
...(inline_entities.length && { inline_entities }),
|
|
147
|
+
__typename: 'GenAIMarkdownTextUXPrimitive',
|
|
223
148
|
});
|
|
224
149
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
__typename: 'GenAIMarkdownTextUXPrimitive',
|
|
232
|
-
})
|
|
233
|
-
);
|
|
150
|
+
const submessages = [
|
|
151
|
+
{
|
|
152
|
+
messageType: 2,
|
|
153
|
+
messageText: text,
|
|
154
|
+
},
|
|
155
|
+
].filter(Boolean);
|
|
234
156
|
|
|
235
|
-
return this
|
|
157
|
+
return this._addContent(section, submessages, {
|
|
158
|
+
id,
|
|
159
|
+
replace,
|
|
160
|
+
insertAt,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Alias — .d.ts documents this as "addHeading" (FOATextPrimitive), source only ever exposed addFOAText(). */
|
|
165
|
+
addHeading(text, options = {}) {
|
|
166
|
+
return this.addFOAText(text, options);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
addFOAText(text, { id, replace, insertAt } = {}) {
|
|
170
|
+
if (typeof text !== 'string') {
|
|
171
|
+
throw new TypeError('Text must be a string');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const section = AIRich.newLayout('Single', {
|
|
175
|
+
text,
|
|
176
|
+
__typename: 'FOATextPrimitive',
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const submessages = [
|
|
180
|
+
{
|
|
181
|
+
messageType: 2,
|
|
182
|
+
messageText: text,
|
|
183
|
+
},
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
return this._addContent(section, submessages, {
|
|
187
|
+
id,
|
|
188
|
+
replace,
|
|
189
|
+
insertAt,
|
|
190
|
+
});
|
|
236
191
|
}
|
|
237
192
|
|
|
238
|
-
|
|
239
|
-
addCode(language, code) {
|
|
193
|
+
addCode(language, code, { id, replace, insertAt } = {}) {
|
|
240
194
|
if (typeof language !== 'string' || typeof code !== 'string') {
|
|
241
195
|
throw new TypeError('Language and code must be a string');
|
|
242
196
|
}
|
|
243
197
|
|
|
244
198
|
const meta = AIRich.tokenizer(code, language);
|
|
245
199
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
codeBlocks: meta.codeBlock,
|
|
251
|
-
},
|
|
200
|
+
const section = AIRich.newLayout('Single', {
|
|
201
|
+
language,
|
|
202
|
+
code_blocks: meta.unified_codeBlock,
|
|
203
|
+
__typename: 'GenAICodeUXPrimitive',
|
|
252
204
|
});
|
|
253
205
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
206
|
+
const submessages = [
|
|
207
|
+
{
|
|
208
|
+
messageType: 5,
|
|
209
|
+
codeMetadata: {
|
|
210
|
+
codeLanguage: language,
|
|
211
|
+
codeBlocks: meta.codeBlock,
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
];
|
|
261
215
|
|
|
262
|
-
return this
|
|
216
|
+
return this._addContent(section, submessages, {
|
|
217
|
+
id,
|
|
218
|
+
replace,
|
|
219
|
+
insertAt,
|
|
220
|
+
});
|
|
263
221
|
}
|
|
264
222
|
|
|
265
|
-
|
|
266
|
-
addTable(table, { hyperlink = true, citation = true, latex = true } = {}) {
|
|
223
|
+
addTable(table, { hyperlink = true, citation = true, latex = true, id, replace, insertAt } = {}) {
|
|
267
224
|
if (!Array.isArray(table)) {
|
|
268
225
|
throw new TypeError('Table must be an array');
|
|
269
226
|
}
|
|
270
227
|
|
|
271
|
-
const meta = AIRich.toTableMetadata(table, {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
tableMetadata: {
|
|
276
|
-
title: meta.title,
|
|
277
|
-
rows: meta.rows,
|
|
278
|
-
},
|
|
228
|
+
const meta = AIRich.toTableMetadata(table, {
|
|
229
|
+
hyperlink,
|
|
230
|
+
citation,
|
|
231
|
+
latex,
|
|
279
232
|
});
|
|
280
233
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
})
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
return this;
|
|
289
|
-
}
|
|
234
|
+
const section = AIRich.newLayout('Single', {
|
|
235
|
+
rows: meta.unified_rows,
|
|
236
|
+
__typename: 'GenATableUXPrimitive',
|
|
237
|
+
});
|
|
290
238
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (!linkField || typeof linkField !== 'object') throw new TypeError('Each link must be an object');
|
|
298
|
-
const prefix = 'SS_' + index;
|
|
299
|
-
const url = linkField.url || '';
|
|
300
|
-
const text = String(linkField.text ?? '');
|
|
301
|
-
const sources = Array.isArray(linkField.sources) ? linkField.sources.map((sourceField) => ({
|
|
302
|
-
source_type: 'THIRD_PARTY',
|
|
303
|
-
source_display_name: sourceField?.displayName || sourceField?.title || 'Source',
|
|
304
|
-
source_subtitle: sourceField?.subtitle || '',
|
|
305
|
-
source_url: sourceField?.url || url,
|
|
306
|
-
})) : [];
|
|
307
|
-
const entity = {
|
|
308
|
-
key: prefix,
|
|
309
|
-
metadata: {
|
|
310
|
-
reference_id: index + 1,
|
|
311
|
-
reference_url: url,
|
|
312
|
-
reference_title: linkField.title || 'Source',
|
|
313
|
-
reference_display_name: linkField.displayName || linkField.title || 'Source',
|
|
314
|
-
sources,
|
|
315
|
-
__typename: 'GenAISearchCitationItem',
|
|
239
|
+
const submessages = [
|
|
240
|
+
{
|
|
241
|
+
messageType: 4,
|
|
242
|
+
tableMetadata: {
|
|
243
|
+
title: meta.title,
|
|
244
|
+
rows: meta.rows,
|
|
316
245
|
},
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
text: `${text} {{${prefix}}}${url}{{/${prefix}}}`,
|
|
320
|
-
inline_entities: [entity],
|
|
321
|
-
__typename: 'GenAIMarkdownTextUXPrimitive',
|
|
322
|
-
});
|
|
323
|
-
this._sections.push(section);
|
|
324
|
-
this._submessages.push({
|
|
325
|
-
messageType: 2,
|
|
326
|
-
messageText: `${text} {{${prefix}}}¹{{/${prefix}}} `,
|
|
327
|
-
inlineEntities: [entity],
|
|
328
|
-
});
|
|
329
|
-
});
|
|
330
|
-
return this;
|
|
331
|
-
}
|
|
246
|
+
},
|
|
247
|
+
];
|
|
332
248
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
messageType: 9,
|
|
338
|
-
contentItemsMetadata: { itemsMetadata: items, contentType: 1 },
|
|
249
|
+
return this._addContent(section, submessages, {
|
|
250
|
+
id,
|
|
251
|
+
replace,
|
|
252
|
+
insertAt,
|
|
339
253
|
});
|
|
340
|
-
this._sections.push(AIRich.newLayout('Single', {
|
|
341
|
-
items,
|
|
342
|
-
content_type: 1,
|
|
343
|
-
__typename: 'GenAIContentItemsUXPrimitive',
|
|
344
|
-
}));
|
|
345
|
-
return this;
|
|
346
254
|
}
|
|
347
255
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
256
|
+
addSource(sources = [], { id, replace, insertAt } = {}) {
|
|
257
|
+
if (!Array.isArray(sources)) {
|
|
258
|
+
throw new TypeError('Sources must be an array of strings, arrays, or objects');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const isStringArray = sources.every((item) => typeof item === 'string');
|
|
262
|
+
|
|
263
|
+
const isArrayFormat = sources.every((item) => Array.isArray(item) && item.every((value) => typeof value === 'string'));
|
|
264
|
+
|
|
265
|
+
const isObjectFormat = sources.every((item) => item && typeof item === 'object' && !Array.isArray(item));
|
|
266
|
+
|
|
267
|
+
if (!isStringArray && !isArrayFormat && !isObjectFormat) {
|
|
268
|
+
throw new TypeError('Sources must be a string array, array of string arrays, or array of objects');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (isStringArray) {
|
|
272
|
+
sources = [sources];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const normalizedSources = sources.map((source) => {
|
|
276
|
+
if (Array.isArray(source)) {
|
|
277
|
+
const [icon, url, title, subtitle] = source;
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
icon,
|
|
281
|
+
url,
|
|
282
|
+
title,
|
|
283
|
+
subtitle,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
icon: source.favicon ?? source.icon ?? '',
|
|
289
|
+
url: source.url ?? '',
|
|
290
|
+
title: source.title ?? '',
|
|
291
|
+
subtitle: source.subtitle ?? '',
|
|
292
|
+
};
|
|
293
|
+
});
|
|
357
294
|
|
|
358
|
-
|
|
359
|
-
// Accept 3 formats:
|
|
360
|
-
// 1. Array of objects: [{ icon, url, title, subtitle }] — from v4.7 example
|
|
361
|
-
// 2. Array of string arrays: [['iconUrl', 'url', 'text']]
|
|
362
|
-
// 3. Single string array (shorthand for format 2): ['iconUrl', 'url', 'text']
|
|
363
|
-
const isObjArray = Array.isArray(sources) && sources.every((item) => item && typeof item === 'object' && !Array.isArray(item));
|
|
364
|
-
const isStrArrayArray = Array.isArray(sources) && sources.every((item) => Array.isArray(item) && item.every((v) => typeof v === 'string'));
|
|
365
|
-
const isFlatStrArray = Array.isArray(sources) && sources.every((item) => typeof item === 'string');
|
|
366
|
-
|
|
367
|
-
if (!isObjArray && !isStrArrayArray && !isFlatStrArray) {
|
|
368
|
-
throw new TypeError('addSource(): pass an array of objects { icon, url, title, subtitle } or string arrays [iconUrl, url, text]');
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
let normalized;
|
|
372
|
-
if (isObjArray) {
|
|
373
|
-
normalized = sources.map((item) => ({
|
|
374
|
-
icon: item.icon ?? item.iconUrl ?? item.favicon ?? '',
|
|
375
|
-
url: item.url ?? '',
|
|
376
|
-
text: item.title ?? item.displayName ?? item.text ?? '',
|
|
377
|
-
subtitle: item.subtitle ?? 'AI',
|
|
378
|
-
}));
|
|
379
|
-
} else {
|
|
380
|
-
const arr = isFlatStrArray ? [sources] : sources;
|
|
381
|
-
normalized = arr.map(([icon = '', url = '', text = '']) => ({ icon, url, text, subtitle: 'AI' }));
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const source = normalized.map(({ icon, url, text, subtitle }) => ({
|
|
295
|
+
const source = normalizedSources.map(({ icon, url, title, subtitle }) => ({
|
|
385
296
|
source_type: 'THIRD_PARTY',
|
|
386
|
-
source_display_name:
|
|
297
|
+
source_display_name: title,
|
|
387
298
|
source_subtitle: subtitle,
|
|
388
299
|
source_url: url,
|
|
389
300
|
favicon: {
|
|
390
|
-
url: Toolkit.resolveMedia(this.#client, icon, 'image'
|
|
301
|
+
url: Toolkit.resolveMedia(this.#client, icon, 'image'),
|
|
391
302
|
mime_type: 'image/jpeg',
|
|
392
303
|
width: 16,
|
|
393
304
|
height: 16,
|
|
394
305
|
},
|
|
395
306
|
}));
|
|
396
307
|
|
|
397
|
-
this.
|
|
398
|
-
AIRich.newLayout('Single', {
|
|
399
|
-
sources: source,
|
|
400
|
-
__typename: 'GenAISearchResultPrimitive',
|
|
401
|
-
})
|
|
402
|
-
);
|
|
308
|
+
const submessage = this.createAlert('GenAISearchResultPrimitive');
|
|
403
309
|
|
|
404
|
-
|
|
310
|
+
const section = AIRich.newLayout('Single', {
|
|
311
|
+
sources: source,
|
|
312
|
+
__typename: 'GenAISearchResultPrimitive',
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
return this._addContent(section, submessage, {
|
|
316
|
+
id,
|
|
317
|
+
replace,
|
|
318
|
+
insertAt,
|
|
319
|
+
});
|
|
405
320
|
}
|
|
406
321
|
|
|
407
|
-
|
|
408
|
-
addReels(reelsItems = [], { resolveUrl = false } = {}) {
|
|
322
|
+
addReels(reelsItems = [], { id, replace, insertAt } = {}) {
|
|
409
323
|
if (
|
|
410
324
|
!(
|
|
411
325
|
(reelsItems && typeof reelsItems === 'object' && !Array.isArray(reelsItems)) ||
|
|
@@ -415,88 +329,64 @@ class AIRich extends BaseBuilder {
|
|
|
415
329
|
throw new TypeError('Reels items must be an object or an array of objects');
|
|
416
330
|
}
|
|
417
331
|
|
|
418
|
-
|
|
419
|
-
reelsItems = [reelsItems];
|
|
420
|
-
}
|
|
332
|
+
const items = Array.isArray(reelsItems) ? reelsItems : [reelsItems];
|
|
421
333
|
|
|
422
|
-
const reels =
|
|
334
|
+
const reels = items.map((item) => ({
|
|
423
335
|
...item,
|
|
424
|
-
_avatar: Toolkit.resolveMedia(this.#client, item.profileIconUrl ?? item.profile_url ?? item.profile ?? '', 'image'
|
|
425
|
-
_thumbnail: Toolkit.resolveMedia(this.#client, item.thumbnailUrl ?? item.thumbnail ?? '', 'image'
|
|
336
|
+
_avatar: Toolkit.resolveMedia(this.#client, item.profileIconUrl ?? item.profile_url ?? item.profile ?? '', 'image'),
|
|
337
|
+
_thumbnail: Toolkit.resolveMedia(this.#client, item.thumbnailUrl ?? item.thumbnail ?? '', 'image'),
|
|
426
338
|
}));
|
|
427
339
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
340
|
+
const section = AIRich.newLayout(
|
|
341
|
+
'HScroll',
|
|
342
|
+
reels.map((item) => ({
|
|
343
|
+
reels_url: item.videoUrl ?? item.url ?? '',
|
|
344
|
+
thumbnail_url: item._thumbnail,
|
|
345
|
+
creator: item.username ?? item.title ?? '',
|
|
346
|
+
avatar_url: item._avatar,
|
|
347
|
+
reels_title: item.reels_title ?? item.title ?? '',
|
|
348
|
+
likes_count: item.likes_count ?? item.like ?? 0,
|
|
349
|
+
shares_count: item.shares_count ?? item.share ?? 0,
|
|
350
|
+
view_count: item.view_count ?? item.view ?? 0,
|
|
351
|
+
reel_source: item.reel_source ?? item.source ?? 'IG',
|
|
352
|
+
is_verified: !!(item.is_verified || item.verified),
|
|
353
|
+
__typename: 'GenAIReelPrimitive',
|
|
354
|
+
}))
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const submessages = [
|
|
358
|
+
{
|
|
359
|
+
messageType: 9,
|
|
360
|
+
contentItemsMetadata: {
|
|
361
|
+
contentType: 1,
|
|
362
|
+
itemsMetadata: reels.map((item) => ({
|
|
363
|
+
reelItem: {
|
|
364
|
+
title: item.username ?? '',
|
|
365
|
+
profileIconUrl: item._avatar,
|
|
366
|
+
thumbnailUrl: item._thumbnail,
|
|
367
|
+
videoUrl: item.videoUrl ?? item.url ?? '',
|
|
368
|
+
},
|
|
369
|
+
})),
|
|
370
|
+
},
|
|
440
371
|
},
|
|
441
|
-
|
|
372
|
+
];
|
|
442
373
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
sourceProviderURL: item.videoUrl ?? item.url ?? '',
|
|
448
|
-
sourceQuery: '',
|
|
449
|
-
faviconCDNURL: item._avatar,
|
|
450
|
-
citationNumber: idx + 1,
|
|
451
|
-
sourceTitle: item.username ?? '',
|
|
452
|
-
});
|
|
374
|
+
return this._addContent(section, submessages, {
|
|
375
|
+
id,
|
|
376
|
+
replace,
|
|
377
|
+
insertAt,
|
|
453
378
|
});
|
|
454
|
-
|
|
455
|
-
this._sections.push(
|
|
456
|
-
AIRich.newLayout(
|
|
457
|
-
'HScroll',
|
|
458
|
-
reels.map((item) => ({
|
|
459
|
-
reels_url: item.videoUrl ?? item.url ?? '',
|
|
460
|
-
thumbnail_url: item._thumbnail,
|
|
461
|
-
creator: item.username ?? item.title ?? '',
|
|
462
|
-
avatar_url: item._avatar,
|
|
463
|
-
reels_title: item.reels_title ?? item.title ?? '',
|
|
464
|
-
likes_count: item.likes_count ?? item.like ?? 0,
|
|
465
|
-
shares_count: item.shares_count ?? item.share ?? 0,
|
|
466
|
-
view_count: item.view_count ?? item.view ?? 0,
|
|
467
|
-
reel_source: item.reel_source ?? item.source ?? 'IG',
|
|
468
|
-
is_verified: !!(item.is_verified || item.verified),
|
|
469
|
-
__typename: 'GenAIReelPrimitive',
|
|
470
|
-
}))
|
|
471
|
-
)
|
|
472
|
-
);
|
|
473
|
-
|
|
474
|
-
return this;
|
|
475
379
|
}
|
|
476
380
|
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* @param {{ resolveUrl?: boolean, instant?: boolean|'only' }} [options]
|
|
480
|
-
* `instant: true` — sends BOTH: the GRID_IMAGE card (still shows WA's "can't verify"
|
|
481
|
-
* forwarded-download prompt, unavoidable per-design of botForwardedMessage) AND a plain
|
|
482
|
-
* (non-forwarded) imageMessage via send()'s inline-image fallback queue (`_inlineImages`,
|
|
483
|
-
* shared with addInlineImage()) that renders instantly with no prompt. Two images, by design.
|
|
484
|
-
* `instant: 'only'` — Vanz@Add (v4.9.2): skips building the GRID_IMAGE card entirely (no
|
|
485
|
-
* submessage, no GenAIImaginePrimitive section) and queues ONLY the plain imageMessage.
|
|
486
|
-
* One image, no prompt, nothing to download — use this when you don't need the rich card,
|
|
487
|
-
* just the picture to show up immediately.
|
|
488
|
-
*/
|
|
489
|
-
addImage(imageUrl, { resolveUrl = false, instant = false } = {}) {
|
|
381
|
+
addImage(imageUrl, { width, height, status = 'READY', update_text, resolveUrl = false, id, replace, insertAt } = {}) {
|
|
490
382
|
if (!(typeof imageUrl === 'string' || Buffer.isBuffer(imageUrl) || (Array.isArray(imageUrl) && imageUrl.every((v) => typeof v === 'string' || Buffer.isBuffer(v))))) {
|
|
491
383
|
throw new TypeError('imageUrl must be string | buffer | array of string/buffer');
|
|
492
384
|
}
|
|
493
|
-
if (instant !== false && instant !== true && instant !== 'only') {
|
|
494
|
-
throw new TypeError(`instant must be false, true, or 'only' — got ${JSON.stringify(instant)}`);
|
|
495
|
-
}
|
|
496
385
|
|
|
497
386
|
const list = Array.isArray(imageUrl)
|
|
498
387
|
? imageUrl.map((v) => {
|
|
499
388
|
const url = Toolkit.resolveMedia(this.#client, v, 'image', { resolveUrl });
|
|
389
|
+
|
|
500
390
|
return {
|
|
501
391
|
imagePreviewUrl: url,
|
|
502
392
|
imageHighResUrl: url,
|
|
@@ -505,6 +395,7 @@ class AIRich extends BaseBuilder {
|
|
|
505
395
|
})
|
|
506
396
|
: (() => {
|
|
507
397
|
const url = Toolkit.resolveMedia(this.#client, imageUrl, 'image', { resolveUrl });
|
|
398
|
+
|
|
508
399
|
return [
|
|
509
400
|
{
|
|
510
401
|
imagePreviewUrl: url,
|
|
@@ -514,115 +405,46 @@ class AIRich extends BaseBuilder {
|
|
|
514
405
|
];
|
|
515
406
|
})();
|
|
516
407
|
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
if (buildCard) {
|
|
520
|
-
this._submessages.push({
|
|
521
|
-
messageType: 1,
|
|
522
|
-
gridImageMetadata: {
|
|
523
|
-
gridImageUrl: {
|
|
524
|
-
imagePreviewUrl: list[0]?.imagePreviewUrl,
|
|
525
|
-
},
|
|
526
|
-
imageUrls: list,
|
|
527
|
-
},
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
list.forEach(({ imagePreviewUrl }) => {
|
|
532
|
-
if (buildCard) {
|
|
533
|
-
this._sections.push(
|
|
534
|
-
AIRich.newLayout('Single', {
|
|
535
|
-
media: {
|
|
536
|
-
url: imagePreviewUrl,
|
|
537
|
-
mime_type: 'image/png',
|
|
538
|
-
},
|
|
539
|
-
imagine_type: 'IMAGE',
|
|
540
|
-
status: { status: 'READY' },
|
|
541
|
-
__typename: 'GenAIImaginePrimitive',
|
|
542
|
-
})
|
|
543
|
-
);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
if (instant) {
|
|
547
|
-
this._inlineImages.push({ url: imagePreviewUrl, caption: undefined });
|
|
548
|
-
}
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
return this;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// Vanz@Fix 15-08-26 (bug 41) --- addImage() only builds GRID_IMAGE (messageType 1).
|
|
555
|
-
// There was no helper for standalone INLINE_IMAGE (messageType 3): callers were manually
|
|
556
|
-
// pushing addSubmessage() (correct proto shape) + addSection() (WRONG shape — reused the
|
|
557
|
-
// GRID_IMAGE/GenAIImaginePrimitive section schema instead of GenAIInlineImageUXPrimitive),
|
|
558
|
-
// which broke client-side unifiedResponse rendering even though the submessage itself was fine.
|
|
559
|
-
// Mirrors RichSubMessageType.INLINE_IMAGE handling in rich-message-utils.js's toUnified().
|
|
560
|
-
/** Add an image inline with the surrounding text flow (falls back to a plain imageMessage on send() if the client can't render inline images — see skipImageFallback). */
|
|
561
|
-
addInlineImage(imageUrl, { text = '', alignment = 'center', tapLinkUrl = '', resolveUrl = false } = {}) {
|
|
562
|
-
if (!(typeof imageUrl === 'string' || Buffer.isBuffer(imageUrl) || (imageUrl && typeof imageUrl === 'object'))) {
|
|
563
|
-
throw new TypeError('imageUrl must be string | buffer | { imagePreviewUrl, imageHighResUrl, sourceUrl }');
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
const ALIGNMENT_ENUM = { leading: 0, trailing: 1, center: 2 };
|
|
567
|
-
const ALIGNMENT_NAME = ['AI_RICH_RESPONSE_IMAGE_LAYOUT_LEADING_ALIGNED', 'AI_RICH_RESPONSE_IMAGE_LAYOUT_TRAILING_ALIGNED', 'AI_RICH_RESPONSE_IMAGE_LAYOUT_CENTER_ALIGNED'];
|
|
568
|
-
const alignmentNum = typeof alignment === 'number' ? alignment : (ALIGNMENT_ENUM[String(alignment).toLowerCase()] ?? ALIGNMENT_ENUM.center);
|
|
569
|
-
|
|
570
|
-
const url =
|
|
571
|
-
imageUrl && typeof imageUrl === 'object'
|
|
572
|
-
? {
|
|
573
|
-
imagePreviewUrl: imageUrl.imagePreviewUrl || imageUrl.url,
|
|
574
|
-
imageHighResUrl: imageUrl.imageHighResUrl || imageUrl.url,
|
|
575
|
-
sourceUrl: imageUrl.sourceUrl || imageUrl.url,
|
|
576
|
-
}
|
|
577
|
-
: (() => {
|
|
578
|
-
const resolved = Toolkit.resolveMedia(this.#client, imageUrl, 'image', { resolveUrl });
|
|
579
|
-
return { imagePreviewUrl: resolved, imageHighResUrl: resolved, sourceUrl: resolved };
|
|
580
|
-
})();
|
|
581
|
-
|
|
582
|
-
this._submessages.push({
|
|
583
|
-
messageType: 3,
|
|
584
|
-
imageMetadata: {
|
|
585
|
-
imageUrl: url,
|
|
586
|
-
imageText: text,
|
|
587
|
-
alignment: alignmentNum,
|
|
588
|
-
tapLinkUrl,
|
|
589
|
-
},
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
this._sections.push(
|
|
408
|
+
const sections = list.map(({ imagePreviewUrl }) =>
|
|
593
409
|
AIRich.newLayout('Single', {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
410
|
+
media: {
|
|
411
|
+
url: imagePreviewUrl,
|
|
412
|
+
mime_type: 'image/png',
|
|
413
|
+
width,
|
|
414
|
+
height,
|
|
415
|
+
},
|
|
416
|
+
imagine_type: 'IMAGE',
|
|
417
|
+
status: {
|
|
418
|
+
status,
|
|
419
|
+
update_text,
|
|
598
420
|
},
|
|
599
|
-
|
|
600
|
-
alignment: ALIGNMENT_NAME[alignmentNum],
|
|
601
|
-
tap_link_url: tapLinkUrl,
|
|
602
|
-
__typename: 'GenAIInlineImageUXPrimitive',
|
|
421
|
+
__typename: 'GenAIImaginePrimitive',
|
|
603
422
|
})
|
|
604
423
|
);
|
|
605
424
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
425
|
+
const submessage = {
|
|
426
|
+
messageType: 1,
|
|
427
|
+
gridImageMetadata: {
|
|
428
|
+
gridImageUrl: {
|
|
429
|
+
imagePreviewUrl: list[0]?.imagePreviewUrl,
|
|
430
|
+
},
|
|
431
|
+
imageUrls: list,
|
|
432
|
+
},
|
|
433
|
+
};
|
|
611
434
|
|
|
612
|
-
|
|
435
|
+
if (id && sections.length !== 1) {
|
|
436
|
+
throw new Error('Cannot assign one id to multiple image sections');
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return this._addContent(sections, submessage, {
|
|
440
|
+
id,
|
|
441
|
+
replace,
|
|
442
|
+
insertAt,
|
|
443
|
+
});
|
|
613
444
|
}
|
|
614
445
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
// was the main source of blurose's slower response time. Pass { autoFill: true } to opt
|
|
618
|
-
// back into the complete/slow path (real thumbnail + duration + file_length).
|
|
619
|
-
// Vanz@Fix 23-08-26 --- addVideo() had no resolveUrl option at all (unlike addImage()), so the
|
|
620
|
-
// video url always stayed a raw external link, which stock WA clients show a "download" state
|
|
621
|
-
// for before rendering. Mirrors addImage()'s { resolveUrl } — when true, the url is uploaded to
|
|
622
|
-
// WA's own media server first via Toolkit.toUrl() so it renders instantly like WA-native media.
|
|
623
|
-
/** Add a video block. */
|
|
624
|
-
addVideo(videoUrl, { autoFill = false, resolveUrl = false } = {}) {
|
|
625
|
-
const isObjectVideo = (v) => v && typeof v === 'object' && v.url;
|
|
446
|
+
addVideo(videoUrl, { autoFill = true, status = 'READY', estimatedTime, id, replace, insertAt } = {}) {
|
|
447
|
+
const isObjectVideo = (v) => v && typeof v === 'object' && !Array.isArray(v) && v.url;
|
|
626
448
|
|
|
627
449
|
const isValidPrimitive =
|
|
628
450
|
typeof videoUrl === 'string' ||
|
|
@@ -636,17 +458,15 @@ class AIRich extends BaseBuilder {
|
|
|
636
458
|
|
|
637
459
|
const items = Array.isArray(videoUrl) ? videoUrl : [videoUrl];
|
|
638
460
|
|
|
639
|
-
this.
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
461
|
+
const alert = this.createAlert('GenAIImaginePrimitive (ANIMATE)');
|
|
462
|
+
|
|
463
|
+
const sections = [];
|
|
464
|
+
const submessages = [];
|
|
643
465
|
|
|
644
|
-
|
|
466
|
+
for (const item of items) {
|
|
645
467
|
const isObject = isObjectVideo(item);
|
|
646
468
|
|
|
647
|
-
const url = isObject
|
|
648
|
-
? Toolkit.resolveMedia(this.#client, item.url ?? '', 'video', { resolveUrl })
|
|
649
|
-
: Toolkit.resolveMedia(this.#client, item, 'video', { resolveUrl });
|
|
469
|
+
const url = isObject ? Toolkit.resolveMedia(this.#client, item.url ?? '', 'video') : Toolkit.resolveMedia(this.#client, item, 'video');
|
|
650
470
|
|
|
651
471
|
const bufferPromise = autoFill ? Promise.resolve(url).then((u) => Toolkit.fetchBuffer(u)) : null;
|
|
652
472
|
|
|
@@ -672,17 +492,15 @@ class AIRich extends BaseBuilder {
|
|
|
672
492
|
height: 300,
|
|
673
493
|
})
|
|
674
494
|
: autoFill
|
|
675
|
-
? bufferPromise
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
)
|
|
682
|
-
: null
|
|
495
|
+
? bufferPromise?.then((b) =>
|
|
496
|
+
Toolkit.getMp4Preview(b, {
|
|
497
|
+
time: 0,
|
|
498
|
+
result: 'base64',
|
|
499
|
+
})
|
|
500
|
+
)
|
|
683
501
|
: null;
|
|
684
502
|
|
|
685
|
-
|
|
503
|
+
sections.push(
|
|
686
504
|
AIRich.newLayout('Single', {
|
|
687
505
|
media: {
|
|
688
506
|
url,
|
|
@@ -691,34 +509,37 @@ class AIRich extends BaseBuilder {
|
|
|
691
509
|
duration,
|
|
692
510
|
},
|
|
693
511
|
imagine_type: 'ANIMATE',
|
|
694
|
-
status: {
|
|
512
|
+
status: {
|
|
513
|
+
status,
|
|
514
|
+
estimated_completion_time: estimatedTime != null ? Math.floor((Date.now() + estimatedTime) / 1000) : undefined,
|
|
515
|
+
},
|
|
695
516
|
thumbnail: {
|
|
696
517
|
raw_media: thumbnail,
|
|
697
518
|
},
|
|
698
519
|
__typename: 'GenAIImaginePrimitive',
|
|
699
520
|
})
|
|
700
521
|
);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
return this;
|
|
704
|
-
}
|
|
522
|
+
}
|
|
705
523
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
709
|
-
throw new TypeError('Product items must be an object or an array of objects');
|
|
524
|
+
if (alert !== undefined) {
|
|
525
|
+
submessages.push(alert);
|
|
710
526
|
}
|
|
711
527
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
if (missingTitleAt !== -1) {
|
|
715
|
-
throw new TypeError(`addProduct() item[${missingTitleAt}] is missing a required "title"`);
|
|
528
|
+
if (submessages.length > 1) {
|
|
529
|
+
throw new Error('Video content can only have one submessage');
|
|
716
530
|
}
|
|
717
531
|
|
|
718
|
-
this.
|
|
719
|
-
|
|
720
|
-
|
|
532
|
+
return this._addContent(sections, submessages[0], {
|
|
533
|
+
id,
|
|
534
|
+
replace,
|
|
535
|
+
insertAt,
|
|
721
536
|
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
addProduct(data = {}, { id, replace, insertAt } = {}) {
|
|
540
|
+
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
541
|
+
throw new TypeError('Product items must be an object or an array of objects');
|
|
542
|
+
}
|
|
722
543
|
|
|
723
544
|
const items = Array.isArray(data) ? data : [data];
|
|
724
545
|
|
|
@@ -729,41 +550,41 @@ class AIRich extends BaseBuilder {
|
|
|
729
550
|
sale_price: item.sale_price,
|
|
730
551
|
product_url: item.product_url ?? item.url,
|
|
731
552
|
image: {
|
|
732
|
-
url: Toolkit.resolveMedia(this.#client, item.image_url ?? item.image, 'image'
|
|
553
|
+
url: Toolkit.resolveMedia(this.#client, item.image_url ?? item.image, 'image'),
|
|
733
554
|
},
|
|
734
555
|
additional_images: [
|
|
735
556
|
{
|
|
736
|
-
url: Toolkit.resolveMedia(this.#client, item.icon_url ?? item.icon, 'image'
|
|
557
|
+
url: Toolkit.resolveMedia(this.#client, item.icon_url ?? item.icon, 'image'),
|
|
737
558
|
},
|
|
738
559
|
],
|
|
739
560
|
__typename: 'GenAIProductItemCardPrimitive',
|
|
740
561
|
}));
|
|
741
562
|
|
|
742
|
-
|
|
563
|
+
const section = AIRich.newLayout(Array.isArray(data) ? 'HScroll' : 'Single', Array.isArray(data) ? product : product[0]);
|
|
743
564
|
|
|
744
|
-
|
|
565
|
+
const submessage = this.createAlert('GenAIProductItemCardPrimitive');
|
|
566
|
+
|
|
567
|
+
return this._addContent(section, submessage, {
|
|
568
|
+
id,
|
|
569
|
+
replace,
|
|
570
|
+
insertAt,
|
|
571
|
+
});
|
|
745
572
|
}
|
|
746
573
|
|
|
747
|
-
|
|
748
|
-
addPost(data = {}, { resolveUrl = false } = {}) {
|
|
574
|
+
addPost(data = {}, { id, replace, insertAt } = {}) {
|
|
749
575
|
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
750
576
|
throw new TypeError('Post items must be an object or an array of objects');
|
|
751
577
|
}
|
|
752
578
|
|
|
753
579
|
const posts = Array.isArray(data) ? data : [data];
|
|
754
580
|
|
|
755
|
-
this._submessages.push({
|
|
756
|
-
messageType: 2,
|
|
757
|
-
messageText: '[ Postingan tidak dapat dimuat ]',
|
|
758
|
-
});
|
|
759
|
-
|
|
760
581
|
const primitives = posts.map((p) => ({
|
|
761
582
|
title: p.title ?? '',
|
|
762
583
|
subtitle: p.subtitle ?? '',
|
|
763
584
|
username: p.username ?? '',
|
|
764
|
-
profile_picture_url: Toolkit.resolveMedia(this.#client, p.profile_picture_url ?? p.profile_url ?? p.profile ?? '', 'image'
|
|
585
|
+
profile_picture_url: Toolkit.resolveMedia(this.#client, p.profile_picture_url ?? p.profile_url ?? p.profile ?? '', 'image'),
|
|
765
586
|
is_verified: !!(p.is_verified || p.verified),
|
|
766
|
-
thumbnail_url: Toolkit.resolveMedia(this.#client, p.thumbnail_url ?? p.thumbnail ?? '', 'image'
|
|
587
|
+
thumbnail_url: Toolkit.resolveMedia(this.#client, p.thumbnail_url ?? p.thumbnail ?? '', 'image'),
|
|
767
588
|
post_caption: p.post_caption ?? p.caption ?? '',
|
|
768
589
|
likes_count: p.likes_count ?? p.like ?? 0,
|
|
769
590
|
comments_count: p.comments_count ?? p.comment ?? 0,
|
|
@@ -772,456 +593,462 @@ class AIRich extends BaseBuilder {
|
|
|
772
593
|
post_deeplink: p.post_deeplink ?? p.deeplink ?? '',
|
|
773
594
|
source_app: p.source_app || p.source || 'INSTAGRAM',
|
|
774
595
|
footer_label: p.footer_label ?? p.footer ?? '',
|
|
775
|
-
footer_icon: Toolkit.resolveMedia(this.#client, p.footer_icon ?? p.icon ?? '', 'image'
|
|
596
|
+
footer_icon: Toolkit.resolveMedia(this.#client, p.footer_icon ?? p.icon ?? '', 'image'),
|
|
776
597
|
is_carousel: posts.length > 1,
|
|
777
598
|
orientation: p.orientation ?? 'LANDSCAPE',
|
|
778
599
|
post_type: p.post_type ?? 'VIDEO',
|
|
779
600
|
__typename: 'GenAIPostPrimitive',
|
|
780
601
|
}));
|
|
781
602
|
|
|
782
|
-
|
|
603
|
+
const section = AIRich.newLayout('HScroll', primitives);
|
|
783
604
|
|
|
784
|
-
|
|
605
|
+
const submessage = this.createAlert('GenAIPostPrimitive');
|
|
606
|
+
|
|
607
|
+
return this._addContent(section, submessage, {
|
|
608
|
+
id,
|
|
609
|
+
replace,
|
|
610
|
+
insertAt,
|
|
611
|
+
});
|
|
785
612
|
}
|
|
786
613
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
614
|
+
addMetadata(text, { id, replace, insertAt } = {}) {
|
|
615
|
+
if (typeof text !== 'string') {
|
|
616
|
+
throw new TypeError('Text must be a string');
|
|
617
|
+
}
|
|
791
618
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
return this;
|
|
797
|
-
}
|
|
619
|
+
const section = AIRich.newLayout('Single', {
|
|
620
|
+
text,
|
|
621
|
+
__typename: 'GenAIMetadataTextPrimitive',
|
|
622
|
+
});
|
|
798
623
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
}
|
|
624
|
+
const submessage = {
|
|
625
|
+
messageType: 2,
|
|
626
|
+
messageText: text,
|
|
627
|
+
};
|
|
804
628
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
629
|
+
return this._addContent(section, submessage, {
|
|
630
|
+
id,
|
|
631
|
+
replace,
|
|
632
|
+
insertAt,
|
|
633
|
+
});
|
|
810
634
|
}
|
|
811
635
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
}
|
|
636
|
+
addTip(text, { id, replace, insertAt } = {}) {
|
|
637
|
+
if (typeof text !== 'string') {
|
|
638
|
+
throw new TypeError('Text must be a string');
|
|
639
|
+
}
|
|
817
640
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
// to query or undo one after the fact — you could insertAt an id but never inspect, check, or
|
|
823
|
-
// remove it. These 4 read/delete that same Map, so no changes to the Proxy itself were needed.
|
|
641
|
+
const section = AIRich.newLayout('Single', {
|
|
642
|
+
text: 'ⓘ ' + text,
|
|
643
|
+
__typename: 'GenAIMetadataTextPrimitive',
|
|
644
|
+
});
|
|
824
645
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
646
|
+
const submessage = {
|
|
647
|
+
messageType: 2,
|
|
648
|
+
messageText: text,
|
|
649
|
+
};
|
|
829
650
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
651
|
+
return this._addContent(section, submessage, {
|
|
652
|
+
id,
|
|
653
|
+
replace,
|
|
654
|
+
insertAt,
|
|
655
|
+
});
|
|
833
656
|
}
|
|
834
657
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
658
|
+
addWidget(data, { layout, id, replace, insertAt, ...options } = {}) {
|
|
659
|
+
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
660
|
+
throw new TypeError('Widget must be an object or an array of objects');
|
|
661
|
+
}
|
|
839
662
|
|
|
840
|
-
|
|
841
|
-
}
|
|
663
|
+
const isArray = Array.isArray(data);
|
|
842
664
|
|
|
843
|
-
|
|
844
|
-
delete(id) {
|
|
845
|
-
const block = this._blocks.get(id);
|
|
846
|
-
if (!block) throw new Error(`delete(id): no block registered with id "${id}"`);
|
|
665
|
+
const items = isArray ? data : [data];
|
|
847
666
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
if (idx !== -1) this._submessages.splice(idx, 1);
|
|
851
|
-
}
|
|
852
|
-
for (const item of block.secItems) {
|
|
853
|
-
const idx = this._sections.indexOf(item);
|
|
854
|
-
if (idx !== -1) this._sections.splice(idx, 1);
|
|
855
|
-
}
|
|
667
|
+
const widgets = items.map((item) => ({
|
|
668
|
+
__typename: 'GenAI3PExtWidgetPrimitive',
|
|
856
669
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
670
|
+
header: {
|
|
671
|
+
__typename: 'GenAI3PExtWidgetStandardHeader',
|
|
672
|
+
title: item.title ?? '',
|
|
673
|
+
...(item.header ?? {}),
|
|
674
|
+
},
|
|
860
675
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
676
|
+
body: {
|
|
677
|
+
__typename: 'GenAI3PExtCalendarEventList',
|
|
678
|
+
sections: item.sections ?? [],
|
|
679
|
+
|
|
680
|
+
ctas: (item.actions ?? []).map((action) => ({
|
|
681
|
+
__typename: 'GenAI3PExtWidgetCTA',
|
|
682
|
+
label: action.label ?? '',
|
|
683
|
+
state: action.state ?? 'PENDING',
|
|
684
|
+
kind: action.kind ?? 'OTHER',
|
|
685
|
+
tool_call_id: action.tool_call_id ?? action.id ?? '',
|
|
686
|
+
|
|
687
|
+
...(action.toast && {
|
|
688
|
+
toast: {
|
|
689
|
+
__typename: 'GenAI3PExtWidgetToast',
|
|
690
|
+
label: action.toast.label ?? action.label ?? '',
|
|
691
|
+
},
|
|
692
|
+
}),
|
|
693
|
+
})),
|
|
864
694
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
});
|
|
695
|
+
...(item.body ?? {}),
|
|
696
|
+
},
|
|
697
|
+
}));
|
|
869
698
|
|
|
870
|
-
|
|
871
|
-
AIRich.newLayout('Single', {
|
|
872
|
-
text,
|
|
873
|
-
__typename: 'GenAIMetadataTextPrimitive',
|
|
874
|
-
})
|
|
875
|
-
);
|
|
699
|
+
const section = AIRich.newLayout(layout ?? (isArray ? 'HScroll' : 'Single'), isArray ? widgets : widgets[0], options);
|
|
876
700
|
|
|
877
|
-
|
|
701
|
+
const submessage = this.createAlert('GenAI3PExtWidgetStandardHeader');
|
|
702
|
+
|
|
703
|
+
return this._addContent(section, submessage, {
|
|
704
|
+
id,
|
|
705
|
+
replace,
|
|
706
|
+
insertAt,
|
|
707
|
+
});
|
|
878
708
|
}
|
|
879
709
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
throw new TypeError('addTip(text) requires a non-empty string');
|
|
710
|
+
addFooterAction(data, { layout, id, replace, insertAt, ...options } = {}) {
|
|
711
|
+
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
712
|
+
throw new TypeError('Footer action must be an object or an array of objects');
|
|
884
713
|
}
|
|
885
714
|
|
|
886
|
-
|
|
887
|
-
messageType: 2,
|
|
888
|
-
messageText: text,
|
|
889
|
-
});
|
|
715
|
+
const isArray = Array.isArray(data);
|
|
890
716
|
|
|
891
|
-
|
|
892
|
-
AIRich.newLayout('Single', {
|
|
893
|
-
text,
|
|
894
|
-
__typename: 'GenAIMetadataTextPrimitive',
|
|
895
|
-
})
|
|
896
|
-
);
|
|
717
|
+
const items = isArray ? data : [data];
|
|
897
718
|
|
|
898
|
-
|
|
899
|
-
|
|
719
|
+
const actions = items.map((item) => ({
|
|
720
|
+
__typename: 'GenAIFooterActionPrimitive',
|
|
900
721
|
|
|
901
|
-
|
|
902
|
-
// reverse-engineered from captured Meta-AI-in-WhatsApp traffic that this project's own crm/snip
|
|
903
|
-
// tooling (see rich-message-utils.js) dumps for study. Not in any public Baileys schema, so
|
|
904
|
-
// unknown enum values (kind/state on addWidget's ctas) are passed through as observed rather
|
|
905
|
-
// than guessed at, and documented as experimental below.
|
|
906
|
-
// Vanz@Fix 25-08-26 --- removed addImageCard(): its GenAIImagePrimitive/preview_image+full_image
|
|
907
|
-
// shape was mis-reverse-engineered (not a real WA schema) and crashed the client renderer on
|
|
908
|
-
// arrival. addImage() already covers static image cards correctly — use that instead.
|
|
722
|
+
cta_text: item.text ?? item.cta_text ?? '',
|
|
909
723
|
|
|
910
|
-
|
|
911
|
-
addHeading(text) {
|
|
912
|
-
if (typeof text !== 'string' || !text) {
|
|
913
|
-
throw new TypeError('addHeading(text) requires a non-empty string');
|
|
914
|
-
}
|
|
724
|
+
cta_type: item.type ?? item.cta_type ?? 'OPEN_URL',
|
|
915
725
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
messageText: text,
|
|
919
|
-
});
|
|
726
|
+
cta_url: item.url ?? item.cta_url ?? '',
|
|
727
|
+
}));
|
|
920
728
|
|
|
921
|
-
|
|
922
|
-
AIRich.newLayout('Single', {
|
|
923
|
-
text,
|
|
924
|
-
__typename: 'FOATextPrimitive',
|
|
925
|
-
})
|
|
926
|
-
);
|
|
729
|
+
const section = AIRich.newLayout(layout ?? (isArray ? 'HScroll' : 'Single'), isArray ? actions : actions[0], options);
|
|
927
730
|
|
|
928
|
-
|
|
731
|
+
const submessage = this.createAlert('GenAIFooterActionPrimitive');
|
|
732
|
+
|
|
733
|
+
return this._addContent(section, submessage, {
|
|
734
|
+
id,
|
|
735
|
+
replace,
|
|
736
|
+
insertAt,
|
|
737
|
+
});
|
|
929
738
|
}
|
|
930
739
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
* pick different renderings (e.g. one `HScroll` row, one `ActionRow` stack) instead of always
|
|
939
|
-
* inferring HScroll-for-array/Single-for-object from the shape of `data`. Also accepts either
|
|
940
|
-
* `ctas` (original key, matches the wire field) or `actions` (alias) on each item — whichever
|
|
941
|
-
* is present is used; `ctas` wins if both are somehow given.
|
|
942
|
-
* @param {Record<string, any>|Record<string, any>[]} data `{ title, ctas|actions: [{ label, tool_call_id?, kind?, state?, toast? }] }` (single or array).
|
|
943
|
-
* @param {{layout?: 'Single'|'HScroll'|'ActionRow'|string}} [options] `layout` overrides the default single/array inference.
|
|
944
|
-
*/
|
|
945
|
-
addWidget(data = {}, { layout } = {}) {
|
|
946
|
-
const items = Array.isArray(data) ? data : [data];
|
|
740
|
+
addTask(data, { id, replace, insertAt } = {}) {
|
|
741
|
+
if (Array.isArray(data) && data.length === 0) {
|
|
742
|
+
throw new TypeError('Task array must not be empty');
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const isValidSingle = data && typeof data === 'object' && !Array.isArray(data);
|
|
746
|
+
const isValidArray = Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item));
|
|
947
747
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
// newLayout()). Previously an explicit { layout: 'Single' } combined with a multi-item
|
|
951
|
-
// array silently dropped every item past the first with no error. Fail loud instead.
|
|
952
|
-
if (layout === 'Single' && items.length > 1) {
|
|
953
|
-
throw new TypeError(`addWidget(): layout "Single" can only hold one widget (got ${items.length}) — use "HScroll"/"ActionRow" (or omit layout) for multiple`);
|
|
748
|
+
if (!isValidSingle && !isValidArray) {
|
|
749
|
+
throw new TypeError('Task must be an object or a non-empty array of objects');
|
|
954
750
|
}
|
|
955
751
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
const hasTitle = item?.title || item?.header?.title;
|
|
959
|
-
if (!hasTitle) {
|
|
960
|
-
throw new TypeError(`addWidget() item[${i}] is missing a required "title" (or "header.title")`);
|
|
961
|
-
}
|
|
962
|
-
const ctas = item.ctas ?? item.actions;
|
|
963
|
-
if (!Array.isArray(ctas) || !ctas.length) {
|
|
964
|
-
throw new TypeError(`addWidget() item[${i}] requires a non-empty "ctas" (or "actions") array`);
|
|
965
|
-
}
|
|
966
|
-
});
|
|
752
|
+
const isArray = Array.isArray(data);
|
|
753
|
+
const items = isArray ? data : [data];
|
|
967
754
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
755
|
+
const tasks = items.map((item) => {
|
|
756
|
+
if (typeof item.title !== 'string' || !item.title) {
|
|
757
|
+
throw new TypeError('addTask() requires a "title"');
|
|
758
|
+
}
|
|
972
759
|
|
|
973
|
-
// Vanz@Fix (bug 45) --- auto tool_call_id used to be `idx` scoped per-item (ctas.map's own
|
|
974
|
-
// index), resetting to 0 for every widget item. Two items (or two separate addWidget()
|
|
975
|
-
// calls) that both omit tool_call_id/id ended up minting the identical auto id ("00"),
|
|
976
|
-
// so a CTA tap could route to the wrong widget's tool call. Track the counter on the
|
|
977
|
-
// instance instead so every auto-generated id is unique for this AIRich's lifetime.
|
|
978
|
-
this._widgetCtaCounter ??= 0;
|
|
979
|
-
|
|
980
|
-
const widgets = items.map((item) => {
|
|
981
|
-
const ctas = item.ctas ?? item.actions;
|
|
982
|
-
// header accepts either a string title (legacy) or an object { title, subtitle }
|
|
983
|
-
const headerTitle = item.header?.title ?? item.title;
|
|
984
|
-
const headerSubtitle = item.header?.subtitle ?? item.subtitle ?? undefined;
|
|
985
760
|
return {
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
body: {
|
|
992
|
-
sections: item.sections ?? [],
|
|
993
|
-
ctas: ctas.map((cta) => ({
|
|
994
|
-
label: cta.label ?? '',
|
|
995
|
-
state: cta.state ?? 'PENDING',
|
|
996
|
-
kind: cta.kind ?? 'OTHER',
|
|
997
|
-
tool_call_id: cta.tool_call_id ?? cta.id ?? String(this._widgetCtaCounter++).padStart(2, '0'),
|
|
998
|
-
...(cta.toast !== false && {
|
|
999
|
-
toast: { label: typeof cta.toast === 'string' ? cta.toast : headerTitle, __typename: 'GenAI3PExtWidgetToast' },
|
|
1000
|
-
}),
|
|
1001
|
-
__typename: 'GenAI3PExtWidgetCTA',
|
|
1002
|
-
})),
|
|
1003
|
-
__typename: item.body_typename ?? 'GenAI3PExtCalendarEventList',
|
|
1004
|
-
},
|
|
1005
|
-
__typename: 'GenAI3PExtWidgetPrimitive',
|
|
761
|
+
task_id: item.task_id ?? '',
|
|
762
|
+
title: item.title,
|
|
763
|
+
subtitle: item.subtitle ?? '',
|
|
764
|
+
status: item.status ?? 'PENDING',
|
|
765
|
+
__typename: 'GenAITaskPrimitive',
|
|
1006
766
|
};
|
|
1007
767
|
});
|
|
1008
768
|
|
|
1009
|
-
const
|
|
1010
|
-
const asArray = resolvedLayout !== 'Single';
|
|
769
|
+
const section = AIRich.newLayout(isArray ? 'HScroll' : 'Single', isArray ? tasks : tasks[0]);
|
|
1011
770
|
|
|
1012
|
-
this.
|
|
771
|
+
const submessage = this.createAlert('GenAITaskPrimitive');
|
|
1013
772
|
|
|
1014
|
-
return this
|
|
773
|
+
return this._addContent(section, submessage, {
|
|
774
|
+
id,
|
|
775
|
+
replace,
|
|
776
|
+
insertAt,
|
|
777
|
+
});
|
|
1015
778
|
}
|
|
1016
779
|
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
*/
|
|
1022
|
-
addFooterAction(actions) {
|
|
1023
|
-
const items = Array.isArray(actions) ? actions : [actions];
|
|
780
|
+
addQuotaUpsell(data, { id, replace, insertAt } = {}) {
|
|
781
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
782
|
+
throw new TypeError('Quota upsell must be an object');
|
|
783
|
+
}
|
|
1024
784
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
}
|
|
1029
|
-
});
|
|
785
|
+
if (typeof data.title !== 'string' || !data.title) {
|
|
786
|
+
throw new TypeError('addQuotaUpsell() requires a "title"');
|
|
787
|
+
}
|
|
1030
788
|
|
|
1031
|
-
const
|
|
1032
|
-
cta_text: item.text,
|
|
1033
|
-
cta_type: item.type ?? 'OPEN_URL',
|
|
1034
|
-
cta_url: item.url,
|
|
1035
|
-
__typename: 'GenAIFooterActionPrimitive',
|
|
1036
|
-
}));
|
|
789
|
+
const buttons = Array.isArray(data.buttons) ? data.buttons : [];
|
|
1037
790
|
|
|
1038
|
-
|
|
791
|
+
const primitive = {
|
|
792
|
+
__typename: 'GenAIMetaSubsQuotaUpsellPrimitive',
|
|
793
|
+
title: data.title,
|
|
794
|
+
body_line1: data.body_line1 ?? data.body ?? '',
|
|
795
|
+
body_line2: data.body_line2 ?? '',
|
|
796
|
+
|
|
797
|
+
buttons: buttons.map((btn) => ({
|
|
798
|
+
__typename: 'GenAIMetaSubsQuotaUpsellButton',
|
|
799
|
+
label: btn.label ?? '',
|
|
800
|
+
action: btn.action ?? 'OPEN_DEEPLINK',
|
|
801
|
+
deeplink: btn.deeplink ?? '',
|
|
802
|
+
})),
|
|
803
|
+
};
|
|
1039
804
|
|
|
1040
|
-
|
|
805
|
+
const section = AIRich.newLayout('Single', primitive);
|
|
806
|
+
|
|
807
|
+
const submessage = this.createAlert('GenAIMetaSubsQuotaUpsellPrimitive');
|
|
808
|
+
|
|
809
|
+
return this._addContent(section, submessage, {
|
|
810
|
+
id,
|
|
811
|
+
replace,
|
|
812
|
+
insertAt,
|
|
813
|
+
});
|
|
1041
814
|
}
|
|
1042
815
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
this._sections.push(AIRich.newLayout('Single', { __typename: 'GenAIDividerPrimitive' }));
|
|
1055
|
-
return this;
|
|
816
|
+
/** GenAIDividerPrimitive — plain horizontal line, no content. */
|
|
817
|
+
addDivider({ id, replace, insertAt } = {}) {
|
|
818
|
+
const section = AIRich.newLayout('Single', {
|
|
819
|
+
__typename: 'GenAIDividerPrimitive',
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
return this._addContent(section, undefined, {
|
|
823
|
+
id,
|
|
824
|
+
replace,
|
|
825
|
+
insertAt,
|
|
826
|
+
});
|
|
1056
827
|
}
|
|
1057
828
|
|
|
1058
|
-
/**
|
|
1059
|
-
addSpacer(spacing =
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
return this
|
|
829
|
+
/** GenAISpacerPrimitive — blank vertical spacing. */
|
|
830
|
+
addSpacer(spacing, { id, replace, insertAt } = {}) {
|
|
831
|
+
const section = AIRich.newLayout('Single', {
|
|
832
|
+
spacing,
|
|
833
|
+
__typename: 'GenAISpacerPrimitive',
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
return this._addContent(section, undefined, {
|
|
837
|
+
id,
|
|
838
|
+
replace,
|
|
839
|
+
insertAt,
|
|
840
|
+
});
|
|
1066
841
|
}
|
|
1067
842
|
|
|
1068
843
|
/**
|
|
1069
|
-
*
|
|
1070
|
-
*
|
|
1071
|
-
*
|
|
844
|
+
* GenAILatexItem — corrected after cross-checking an older, actually-shipped build
|
|
845
|
+
* (MessageBuilder v4.7, pre-dates this primitive-card guess). LaTeX in this ecosystem
|
|
846
|
+
* is NOT a standalone card — the real, working mechanism is an inline entity inside
|
|
847
|
+
* addText()'s own extractIE() parser (`[expr|width|height|font_height|padding]<imageUrl>`
|
|
848
|
+
* syntax), and it requires a PRE-RENDERED image of the equation — WhatsApp does not
|
|
849
|
+
* render raw LaTeX text client-side. The previous addLatex() (standalone
|
|
850
|
+
* GenAILatexUXPrimitive card, text-only) was confirmed via eval to render blank because
|
|
851
|
+
* that primitive/approach never existed in the first place. This version is a thin
|
|
852
|
+
* wrapper around the real, already-working addText() inline syntax.
|
|
1072
853
|
*/
|
|
1073
|
-
addLatex(expression) {
|
|
854
|
+
addLatex(expression, { url, width, height, font_height, padding, id, replace, insertAt } = {}) {
|
|
1074
855
|
if (typeof expression !== 'string' || !expression) {
|
|
1075
856
|
throw new TypeError('addLatex(expression) requires a non-empty string');
|
|
1076
857
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
858
|
+
|
|
859
|
+
if (!url) {
|
|
860
|
+
throw new TypeError('addLatex() requires a pre-rendered equation image "url" — WhatsApp renders LaTeX as an inline image, not raw text');
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
const extras = [width, height, font_height, padding];
|
|
864
|
+
|
|
865
|
+
while (extras.length && extras[extras.length - 1] == null) {
|
|
866
|
+
extras.pop();
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const raw = [expression, ...extras].join('|');
|
|
870
|
+
|
|
871
|
+
return this.addText(`[${raw}]<${url}>`, {
|
|
872
|
+
id,
|
|
873
|
+
replace,
|
|
874
|
+
insertAt,
|
|
1080
875
|
});
|
|
1081
|
-
this._sections.push(AIRich.newLayout('Single', { latex_expression: expression, __typename: 'GenAILatexUXPrimitive' }));
|
|
1082
|
-
return this;
|
|
1083
876
|
}
|
|
1084
877
|
|
|
1085
878
|
/**
|
|
1086
|
-
*
|
|
1087
|
-
*
|
|
879
|
+
* GenAIInlineImageUXPrimitive — messageType 3 (INLINE_IMAGE) submessage, field layout
|
|
880
|
+
* ported from rich-message-utils.js toUnified() INLINE_IMAGE case (same image-url-object
|
|
881
|
+
* fix as addImage()/bug 40 round 2), not guessed from the .d.ts alone.
|
|
1088
882
|
*/
|
|
1089
|
-
|
|
1090
|
-
if (!
|
|
1091
|
-
throw new TypeError('
|
|
1092
|
-
}
|
|
1093
|
-
this._submessages.push({ messageType: 2, messageText: `Tugas: ${data.title}` });
|
|
1094
|
-
this._sections.push(
|
|
1095
|
-
AIRich.newLayout('Single', {
|
|
1096
|
-
task_id: data.task_id ?? '',
|
|
1097
|
-
title: data.title,
|
|
1098
|
-
subtitle: data.subtitle ?? '',
|
|
1099
|
-
status: data.status ?? 'IN_PROGRESS',
|
|
1100
|
-
__typename: 'GenAITaskPrimitive',
|
|
1101
|
-
})
|
|
1102
|
-
);
|
|
1103
|
-
// Safety net: GenAITaskPrimitive is a custom AI-only component the stock WA client
|
|
1104
|
-
// doesn't render visibly. Append a plain text section so the task is still visible.
|
|
1105
|
-
// Set data.textFallback = false to skip.
|
|
1106
|
-
if (data.textFallback !== false) {
|
|
1107
|
-
const fallbackText = data.subtitle ? `${data.title} — ${data.subtitle}` : data.title;
|
|
1108
|
-
this._sections.push(AIRich.newLayout('Single', { text: `Tugas: ${fallbackText}`, __typename: 'FOATextPrimitive' }));
|
|
883
|
+
addInlineImage(imageUrl, { text = '', alignment = 'center', tapLinkUrl = '', resolveUrl = false, id, replace, insertAt } = {}) {
|
|
884
|
+
if (!(typeof imageUrl === 'string' || Buffer.isBuffer(imageUrl))) {
|
|
885
|
+
throw new TypeError('imageUrl must be string | buffer');
|
|
1109
886
|
}
|
|
1110
|
-
|
|
887
|
+
|
|
888
|
+
const url = Toolkit.resolveMedia(this.#client, imageUrl, 'image', { resolveUrl });
|
|
889
|
+
|
|
890
|
+
const ALIGNMENT_INDEX = { leading: 0, trailing: 1, center: 2 };
|
|
891
|
+
const ALIGNMENT_NAME = ['AI_RICH_RESPONSE_IMAGE_LAYOUT_LEADING_ALIGNED', 'AI_RICH_RESPONSE_IMAGE_LAYOUT_TRAILING_ALIGNED', 'AI_RICH_RESPONSE_IMAGE_LAYOUT_CENTER_ALIGNED'];
|
|
892
|
+
const alignmentIndex = ALIGNMENT_INDEX[String(alignment).toLowerCase()] ?? ALIGNMENT_INDEX.center;
|
|
893
|
+
|
|
894
|
+
const section = AIRich.newLayout('Single', {
|
|
895
|
+
image_url: {
|
|
896
|
+
image_preview_url: url,
|
|
897
|
+
image_high_res_url: url,
|
|
898
|
+
source_url: url,
|
|
899
|
+
},
|
|
900
|
+
image_text: text,
|
|
901
|
+
alignment: ALIGNMENT_NAME[alignmentIndex],
|
|
902
|
+
tap_link_url: tapLinkUrl,
|
|
903
|
+
__typename: 'GenAIInlineImageUXPrimitive',
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
const submessage = {
|
|
907
|
+
messageType: 3,
|
|
908
|
+
imageMetadata: {
|
|
909
|
+
imageUrl: { imagePreviewUrl: url, imageHighResUrl: url, sourceUrl: url },
|
|
910
|
+
imageText: text,
|
|
911
|
+
alignment: alignmentIndex,
|
|
912
|
+
tapLinkUrl,
|
|
913
|
+
},
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
return this._addContent(section, submessage, {
|
|
917
|
+
id,
|
|
918
|
+
replace,
|
|
919
|
+
insertAt,
|
|
920
|
+
});
|
|
1111
921
|
}
|
|
1112
922
|
|
|
1113
923
|
/**
|
|
1114
|
-
*
|
|
1115
|
-
*
|
|
1116
|
-
* icon/typename.
|
|
1117
|
-
* @param {string} title
|
|
1118
|
-
* @param {{icon?: string, is_in_progress?: boolean}} [options]
|
|
924
|
+
* GenAIContentItemsUXPrimitive — messageType 9 (CONTENT_ITEMS) submessage, ported from
|
|
925
|
+
* rich-message-utils.js toUnified() CONTENT_ITEMS case (bug 40: was returning {}).
|
|
1119
926
|
*/
|
|
1120
|
-
|
|
927
|
+
addContentItems(items = [], { content_type = 1, id, replace, insertAt } = {}) {
|
|
928
|
+
if (!Array.isArray(items)) {
|
|
929
|
+
throw new TypeError('addContentItems() requires an array of items');
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
const section = AIRich.newLayout('Single', {
|
|
933
|
+
items,
|
|
934
|
+
content_type,
|
|
935
|
+
__typename: 'GenAIContentItemsUXPrimitive',
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
const submessage = {
|
|
939
|
+
messageType: 9,
|
|
940
|
+
contentItemsMetadata: {
|
|
941
|
+
itemsMetadata: items,
|
|
942
|
+
contentType: content_type,
|
|
943
|
+
},
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
return this._addContent(section, submessage, {
|
|
947
|
+
id,
|
|
948
|
+
replace,
|
|
949
|
+
insertAt,
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/** GenAIBotProgressStatusPrimitive — one-shot "searching/working" status chip. */
|
|
954
|
+
addProgressStatus(title, { icon, is_in_progress = true, target_secondary_screen_id, target_secondary_screen_tab_id, id, replace, insertAt } = {}) {
|
|
1121
955
|
if (typeof title !== 'string' || !title) {
|
|
1122
|
-
throw new TypeError('addProgressStatus(
|
|
956
|
+
throw new TypeError('addProgressStatus() requires a "title"');
|
|
1123
957
|
}
|
|
1124
|
-
|
|
1125
|
-
const
|
|
958
|
+
|
|
959
|
+
const section = AIRich.newLayout('Single', {
|
|
1126
960
|
title,
|
|
1127
961
|
icon,
|
|
1128
962
|
is_in_progress,
|
|
1129
|
-
|
|
963
|
+
target_secondary_screen_id,
|
|
964
|
+
target_secondary_screen_tab_id,
|
|
1130
965
|
__typename: 'GenAIBotProgressStatusPrimitive',
|
|
1131
|
-
};
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
const submessage = this.createAlert('GenAIBotProgressStatusPrimitive');
|
|
969
|
+
|
|
970
|
+
return this._addContent(section, submessage, {
|
|
971
|
+
id,
|
|
972
|
+
replace,
|
|
973
|
+
insertAt,
|
|
974
|
+
});
|
|
1139
975
|
}
|
|
1140
976
|
|
|
1141
|
-
/**
|
|
1142
|
-
addThinkingStatus(title, { icon
|
|
977
|
+
/** GenAIBotThinkingStatusPrimitive — one-shot "thinking" status chip. */
|
|
978
|
+
addThinkingStatus(title, { icon, is_in_progress = true, target_secondary_screen_id, target_secondary_screen_tab_id, textFallback = true, id, replace, insertAt } = {}) {
|
|
1143
979
|
if (typeof title !== 'string' || !title) {
|
|
1144
|
-
throw new TypeError('addThinkingStatus(
|
|
980
|
+
throw new TypeError('addThinkingStatus() requires a "title"');
|
|
1145
981
|
}
|
|
1146
|
-
|
|
1147
|
-
const
|
|
982
|
+
|
|
983
|
+
const section = AIRich.newLayout('Single', {
|
|
1148
984
|
title,
|
|
1149
985
|
icon,
|
|
1150
986
|
is_in_progress,
|
|
1151
|
-
|
|
987
|
+
target_secondary_screen_id,
|
|
988
|
+
target_secondary_screen_tab_id,
|
|
1152
989
|
__typename: 'GenAIBotThinkingStatusPrimitive',
|
|
1153
|
-
};
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
this.
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
this._sections.push(AIRich.newLayout('Single', { text: title, __typename: 'FOATextPrimitive' }));
|
|
1163
|
-
}
|
|
1164
|
-
return this;
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
const submessage = textFallback ? this.createAlert('GenAIBotThinkingStatusPrimitive') : undefined;
|
|
993
|
+
|
|
994
|
+
return this._addContent(section, submessage, {
|
|
995
|
+
id,
|
|
996
|
+
replace,
|
|
997
|
+
insertAt,
|
|
998
|
+
});
|
|
1165
999
|
}
|
|
1166
1000
|
|
|
1167
|
-
/**
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
addQuotaUpsell(data = {}) {
|
|
1172
|
-
if (!data?.title) {
|
|
1173
|
-
throw new TypeError('addQuotaUpsell() requires a "title"');
|
|
1001
|
+
/** FOABloksPrimitive — raw Bloks payload; most experimental primitive, fields passed through as-is. */
|
|
1002
|
+
addBloks(data, { id, replace, insertAt } = {}) {
|
|
1003
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
1004
|
+
throw new TypeError('Bloks payload must be an object');
|
|
1174
1005
|
}
|
|
1175
|
-
this._submessages.push({ messageType: 2, messageText: data.title });
|
|
1176
|
-
this._sections.push(
|
|
1177
|
-
AIRich.newLayout('Single', {
|
|
1178
|
-
title: data.title,
|
|
1179
|
-
body: data.body ?? '',
|
|
1180
|
-
body_line1: data.body_line1 ?? '',
|
|
1181
|
-
body_line2: data.body_line2 ?? '',
|
|
1182
|
-
buttons: (data.buttons ?? []).map((b) => ({
|
|
1183
|
-
label: b.label ?? '',
|
|
1184
|
-
action: b.action ?? 'OPEN_DEEPLINK',
|
|
1185
|
-
deeplink: b.deeplink ?? '',
|
|
1186
|
-
})),
|
|
1187
|
-
__typename: 'GenAIMetaSubsQuotaUpsellPrimitive',
|
|
1188
|
-
})
|
|
1189
|
-
);
|
|
1190
|
-
return this;
|
|
1191
|
-
}
|
|
1192
1006
|
|
|
1193
|
-
|
|
1194
|
-
* Add a raw Bloks payload (`FOABloksPrimitive`) — Meta's internal UI-description format.
|
|
1195
|
-
* Escape hatch: field meaning beyond what's passed through is undocumented, so this is the
|
|
1196
|
-
* most experimental primitive in this block; pass whatever your captured traffic shows.
|
|
1197
|
-
* @param {{type: string, data: string, uuid?: string, initial_response?: any, versioning_id?: string}} data
|
|
1198
|
-
*/
|
|
1199
|
-
addBloks(data = {}) {
|
|
1200
|
-
if (!data?.type) {
|
|
1007
|
+
if (typeof data.type !== 'string' || !data.type) {
|
|
1201
1008
|
throw new TypeError('addBloks() requires a "type"');
|
|
1202
1009
|
}
|
|
1203
|
-
|
|
1204
|
-
const
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
versioning_id: data.versioning_id ?? '',
|
|
1010
|
+
|
|
1011
|
+
const { textFallback = true, ...rest } = data;
|
|
1012
|
+
|
|
1013
|
+
const section = AIRich.newLayout('Single', {
|
|
1014
|
+
...rest,
|
|
1209
1015
|
__typename: 'FOABloksPrimitive',
|
|
1210
|
-
};
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
const submessage = textFallback ? this.createAlert('FOABloksPrimitive') : undefined;
|
|
1019
|
+
|
|
1020
|
+
return this._addContent(section, submessage, {
|
|
1021
|
+
id,
|
|
1022
|
+
replace,
|
|
1023
|
+
insertAt,
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* GenAIImaginePrimitive with status GENERATING — pending-generation placeholder.
|
|
1029
|
+
* Mirrors addVideo()'s ANIMATE/GENERATING branch but with no media yet.
|
|
1030
|
+
*/
|
|
1031
|
+
addGenerating({ imagine_type = 'IMAGE', estimated_completion_time, textFallback = true, id, replace, insertAt } = {}) {
|
|
1032
|
+
const section = AIRich.newLayout('Single', {
|
|
1033
|
+
imagine_type,
|
|
1034
|
+
status: {
|
|
1035
|
+
status: 'GENERATING',
|
|
1036
|
+
// Matches addVideo()'s convention: input is a ms-from-now duration, stored as a unix-seconds timestamp.
|
|
1037
|
+
estimated_completion_time: estimated_completion_time != null ? Math.floor((Date.now() + estimated_completion_time) / 1000) : undefined,
|
|
1038
|
+
},
|
|
1039
|
+
__typename: 'GenAIImaginePrimitive',
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
const submessage = textFallback ? this.createAlert(`GenAIImaginePrimitive (${imagine_type === 'ANIMATE' ? 'ANIMATE' : 'IMAGE'} GENERATING)`) : undefined;
|
|
1043
|
+
|
|
1044
|
+
return this._addContent(section, submessage, {
|
|
1045
|
+
id,
|
|
1046
|
+
replace,
|
|
1047
|
+
insertAt,
|
|
1048
|
+
});
|
|
1221
1049
|
}
|
|
1222
1050
|
|
|
1223
|
-
|
|
1224
|
-
addSuggest(suggestion, { scroll = true, layout } = {}) {
|
|
1051
|
+
addSuggest(suggestion, { scroll = true, layout, id, replace, insertAt } = {}) {
|
|
1225
1052
|
if (!(typeof suggestion === 'string' || (Array.isArray(suggestion) && suggestion.every((v) => typeof v === 'string')))) {
|
|
1226
1053
|
throw new TypeError('Suggestion must be a string or array of strings');
|
|
1227
1054
|
}
|
|
@@ -1242,18 +1069,28 @@ class AIRich extends BaseBuilder {
|
|
|
1242
1069
|
|
|
1243
1070
|
const type = layout ?? (suggest.length === 1 ? 'Single' : scroll ? 'HScroll' : 'ActionRow');
|
|
1244
1071
|
|
|
1245
|
-
|
|
1072
|
+
const section = AIRich.newLayout(type, type === 'Single' ? suggest[0] : suggest, {
|
|
1073
|
+
__typename: 'GenAIUnifiedResponseSection',
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
const submessage = this.createAlert('GenAIFollowUpSuggestionPillPrimitive');
|
|
1246
1077
|
|
|
1247
|
-
return this
|
|
1078
|
+
return this._addContent(section, submessage, {
|
|
1079
|
+
id,
|
|
1080
|
+
replace,
|
|
1081
|
+
insertAt,
|
|
1082
|
+
});
|
|
1248
1083
|
}
|
|
1249
1084
|
|
|
1250
|
-
|
|
1251
|
-
|
|
1085
|
+
async build(
|
|
1086
|
+
jid,
|
|
1087
|
+
{ bypassDownload = true, forwarded = true, notification = false, includesUnifiedResponse = true, includesSubmessages = true, quoted, quotedParticipant, messageId, ...options } = {}
|
|
1088
|
+
) {
|
|
1252
1089
|
const forward = forwarded
|
|
1253
1090
|
? {
|
|
1254
1091
|
forwardingScore: 1,
|
|
1255
1092
|
isForwarded: true,
|
|
1256
|
-
forwardedAiBotMessageInfo: { botJid: '
|
|
1093
|
+
forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' },
|
|
1257
1094
|
forwardOrigin: 4,
|
|
1258
1095
|
}
|
|
1259
1096
|
: {};
|
|
@@ -1271,7 +1108,7 @@ class AIRich extends BaseBuilder {
|
|
|
1271
1108
|
const qObj = quoted
|
|
1272
1109
|
? {
|
|
1273
1110
|
stanzaId: quoted?.key?.id || quoted?.id,
|
|
1274
|
-
participant: quotedParticipant || quoted?.key?.participant || quoted?.key?.remoteJid,
|
|
1111
|
+
participant: quotedParticipant || quoted?.key?.participant || quoted?.participant || quoted?.key?.remoteJid,
|
|
1275
1112
|
quotedType: 0,
|
|
1276
1113
|
quotedMessage: typeof quoted === 'object' && quoted !== null ? (quoted.message ?? quoted) : undefined,
|
|
1277
1114
|
}
|
|
@@ -1287,140 +1124,91 @@ class AIRich extends BaseBuilder {
|
|
|
1287
1124
|
]
|
|
1288
1125
|
: [...(await waitAllPromises(this._sections))];
|
|
1289
1126
|
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
// Vanz@Fix 24-08-26 --- was `const responseId = crypto.randomUUID()` shared for BOTH
|
|
1295
|
-
// unifiedResponse.response_id and botMetadata.botResponseId, generated fresh every build()
|
|
1296
|
-
// with no override. Now each has its own id, pinned via setResponseId()/setBotResponseId()
|
|
1297
|
-
// if the caller set one (for sendEdit()-style in-place message updates), otherwise still
|
|
1298
|
-
// defaults to a fresh randomUUID() per build() exactly like before.
|
|
1299
|
-
const responseId = this._responseId ?? crypto.randomUUID();
|
|
1300
|
-
const botResponseId = this._botResponseId ?? crypto.randomUUID();
|
|
1127
|
+
if (this._dynamic) {
|
|
1128
|
+
this.refreshResponseId();
|
|
1129
|
+
this.refreshBotResponseId();
|
|
1130
|
+
}
|
|
1301
1131
|
|
|
1302
|
-
return
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
certificateChain: [botMetadataCertificate(), botMetadataCertificate(892)],
|
|
1314
|
-
version: 1,
|
|
1315
|
-
useCase: 1,
|
|
1316
|
-
signature: botMetadataSignature(),
|
|
1317
|
-
},
|
|
1318
|
-
],
|
|
1132
|
+
return generateWAMessageFromContent(
|
|
1133
|
+
jid,
|
|
1134
|
+
{
|
|
1135
|
+
messageContextInfo: {
|
|
1136
|
+
deviceListMetadata: {},
|
|
1137
|
+
deviceListMetadataVersion: 2,
|
|
1138
|
+
botMetadata: {
|
|
1139
|
+
messageDisclaimerText: this._title,
|
|
1140
|
+
...notif,
|
|
1141
|
+
verificationMetadata: AIRich.generateVerificationMetadata(),
|
|
1142
|
+
botResponseId: this._botResponseId,
|
|
1319
1143
|
},
|
|
1320
|
-
...notif,
|
|
1321
1144
|
},
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1145
|
+
...this._extraPayload,
|
|
1146
|
+
botForwardedMessage: {
|
|
1147
|
+
message: {
|
|
1148
|
+
richResponseMessage: {
|
|
1149
|
+
messageType: 1,
|
|
1150
|
+
submessages: includesSubmessages ? await waitAllPromises(this._submessages) : [],
|
|
1151
|
+
unifiedResponse: {
|
|
1152
|
+
data: includesUnifiedResponse ? Buffer.from(Toolkit.stringifyEscaped({ response_id: this._responseId, sections })).toString('base64') : '',
|
|
1153
|
+
},
|
|
1154
|
+
originalRecipientMetadata: {
|
|
1155
|
+
data: includesUnifiedResponse ? Buffer.from(Toolkit.stringifyEscaped({ response_id: this._responseId, sections })).toString('base64') : '',
|
|
1156
|
+
},
|
|
1157
|
+
contextInfo: {
|
|
1158
|
+
...forward,
|
|
1159
|
+
...qObj,
|
|
1160
|
+
...this._contextInfo,
|
|
1161
|
+
},
|
|
1336
1162
|
},
|
|
1337
1163
|
},
|
|
1338
1164
|
},
|
|
1339
1165
|
},
|
|
1340
|
-
|
|
1166
|
+
{ messageId: messageId || generateMessageIDV2(), ...options }
|
|
1167
|
+
);
|
|
1341
1168
|
}
|
|
1342
1169
|
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
// { skipImageFallback: true } to opt out and send only the (image-less-looking) rich card.
|
|
1347
|
-
// Vanz@Fix: don't spread relayMessage-shaped `options` into sendMessage()'s options param —
|
|
1348
|
-
// the two calls expect different option shapes, so the fallback now only forwards `quoted`
|
|
1349
|
-
// (the one option that clearly applies to both) instead of blindly spreading everything.
|
|
1350
|
-
/** Build and send this AI-rich message. @param {string} jid Destination chat/group jid. @param {boolean} [skipImageFallback] Skip auto-resending inline images as a plain imageMessage. */
|
|
1351
|
-
async send(jid, { forwarded, notification, includesUnifiedResponse, includesSubmessages, skipImageFallback = false, quoted, messageId, ...options } = {}) {
|
|
1352
|
-
const msg = await this.build({ forwarded, notification, includesUnifiedResponse, includesSubmessages, quoted, ...options });
|
|
1353
|
-
|
|
1354
|
-
if (!skipImageFallback && this._inlineImages.length) {
|
|
1355
|
-
for (const { url, caption } of this._inlineImages) {
|
|
1356
|
-
try {
|
|
1357
|
-
await this.#client.sendMessage(jid, { image: { url }, caption }, quoted ? { quoted } : {});
|
|
1358
|
-
} catch (err) {
|
|
1359
|
-
// Vanz@Fix: don't let a fallback image failure block the actual rich card from sending
|
|
1360
|
-
this.#client.logger?.warn?.({ err, url }, 'inline image fallback failed, continuing with rich card');
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1170
|
+
async buildEdit(targetJid, targetId, { msg, messageId, ...options } = {}) {
|
|
1171
|
+
if (!msg) {
|
|
1172
|
+
msg = (await this.build(targetJid, options)).message;
|
|
1363
1173
|
}
|
|
1364
1174
|
|
|
1365
|
-
|
|
1366
|
-
// so we know exactly which id was sent, and stash it as _lastMessageKey. That's what lets
|
|
1367
|
-
// sendEdit() be called with no args afterwards and still know which message to patch.
|
|
1368
|
-
messageId = messageId || generateMessageIDV2();
|
|
1369
|
-
|
|
1370
|
-
await this.#client.relayMessage(jid, msg, { messageId, ...options });
|
|
1371
|
-
|
|
1372
|
-
this._lastMessageKey = { remoteJid: jid, fromMe: true, id: messageId };
|
|
1373
|
-
|
|
1374
|
-
return { key: this._lastMessageKey, message: msg };
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
|
-
/**
|
|
1378
|
-
* Build a `protocolMessage` (type EDIT) that patches an already-sent AIRich message in place.
|
|
1379
|
-
* @param {string} targetJid Chat the original message lives in.
|
|
1380
|
-
* @param {string} targetId `key.id` of the original message (the id `send()`/`sendEdit()` returned).
|
|
1381
|
-
* @param {object} [opts] Pass `{ msg }` to reuse an already-built content object instead of rebuilding via build().
|
|
1382
|
-
*/
|
|
1383
|
-
async buildEdit(targetJid, targetId, { msg, messageId, ...options } = {}) {
|
|
1384
|
-
const editedMessage = msg || (await this.build({ ...options }));
|
|
1175
|
+
const editedMessage = msg;
|
|
1385
1176
|
|
|
1386
1177
|
if (!editedMessage) {
|
|
1387
|
-
throw new Error('buildEdit:
|
|
1178
|
+
throw new Error('buildEdit: msg does not contain botForwardedMessage');
|
|
1388
1179
|
}
|
|
1389
1180
|
|
|
1390
1181
|
return generateWAMessageFromContent(
|
|
1391
1182
|
targetJid,
|
|
1392
1183
|
{
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1184
|
+
botForwardedMessage: {
|
|
1185
|
+
message: {
|
|
1186
|
+
protocolMessage: {
|
|
1187
|
+
key: {
|
|
1188
|
+
remoteJid: targetJid,
|
|
1189
|
+
fromMe: true,
|
|
1190
|
+
id: targetId,
|
|
1191
|
+
},
|
|
1192
|
+
type: 14,
|
|
1193
|
+
editedMessage,
|
|
1194
|
+
},
|
|
1398
1195
|
},
|
|
1399
|
-
type: 14, // MESSAGE_EDIT
|
|
1400
|
-
editedMessage,
|
|
1401
1196
|
},
|
|
1402
1197
|
},
|
|
1403
1198
|
{ messageId: messageId || generateMessageIDV2(), ...options }
|
|
1404
1199
|
);
|
|
1405
1200
|
}
|
|
1406
1201
|
|
|
1407
|
-
/**
|
|
1408
|
-
* Rebuild this AIRich message's current content and patch it into an already-sent message in place
|
|
1409
|
-
* (WA edits the bubble instead of showing a new one). With no args, edits the message from the last
|
|
1410
|
-
* send()/sendEdit() call — that's the flow `.addX(...); await rich.sendEdit();` relies on.
|
|
1411
|
-
* @param {string} [jid] Defaults to the jid from the last send()/sendEdit().
|
|
1412
|
-
* @param {string} [id] Defaults to the message id from the last send()/sendEdit().
|
|
1413
|
-
*/
|
|
1414
1202
|
async sendEdit(jid, id, { msg, messageId, additionalNodes = [], ...options } = {}) {
|
|
1415
1203
|
jid = jid ?? this._lastMessageKey?.remoteJid;
|
|
1416
1204
|
id = id ?? this._lastMessageKey?.id;
|
|
1417
1205
|
|
|
1418
1206
|
if (!jid) {
|
|
1419
|
-
throw new Error('
|
|
1207
|
+
throw new Error('JID is required');
|
|
1420
1208
|
}
|
|
1421
1209
|
|
|
1422
1210
|
if (!id) {
|
|
1423
|
-
throw new Error('
|
|
1211
|
+
throw new Error('Message id is required');
|
|
1424
1212
|
}
|
|
1425
1213
|
|
|
1426
1214
|
const msgEdit = await this.buildEdit(jid, id, {
|
|
@@ -1434,13 +1222,36 @@ class AIRich extends BaseBuilder {
|
|
|
1434
1222
|
additionalNodes,
|
|
1435
1223
|
});
|
|
1436
1224
|
|
|
1437
|
-
// Vanz@Note --- deliberately NOT overwriting _lastMessageKey with msgEdit.key here: the
|
|
1438
|
-
// protocolMessage envelope has its own id, but the message the user actually sees (and the
|
|
1439
|
-
// one future sendEdit() calls need to keep patching) is still `id`/`jid` above.
|
|
1440
1225
|
return msgEdit;
|
|
1441
1226
|
}
|
|
1442
1227
|
|
|
1443
|
-
|
|
1228
|
+
async send(jid, { bypassDownload = true, forwarded = true, notification = false, includesUnifiedResponse = true, includesSubmessages = true, messageId, additionalNodes = [], ...options } = {}) {
|
|
1229
|
+
const msg = await this.build(jid, {
|
|
1230
|
+
forwarded,
|
|
1231
|
+
notification,
|
|
1232
|
+
includesUnifiedResponse,
|
|
1233
|
+
includesSubmessages,
|
|
1234
|
+
messageId,
|
|
1235
|
+
...options,
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
|
|
1239
|
+
messageId: msg.key.id,
|
|
1240
|
+
additionalNodes,
|
|
1241
|
+
...options,
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1244
|
+
if (includesUnifiedResponse && bypassDownload) {
|
|
1245
|
+
await this.sendEdit(jid, msg.key.id, {
|
|
1246
|
+
msg: msg.message,
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
this._lastMessageKey = msg.key;
|
|
1251
|
+
|
|
1252
|
+
return msg;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1444
1255
|
static tokenizer(code, lang = 'javascript') {
|
|
1445
1256
|
const keywordsMap = {
|
|
1446
1257
|
javascript: new Set([
|
|
@@ -2106,7 +1917,6 @@ class AIRich extends BaseBuilder {
|
|
|
2106
1917
|
};
|
|
2107
1918
|
}
|
|
2108
1919
|
|
|
2109
|
-
/** Convert a raw `string[][]` grid into the table metadata shape addTable()/addText() produce internally. */
|
|
2110
1920
|
static toTableMetadata(arr, { hyperlink = true, citation = true, latex = true } = {}) {
|
|
2111
1921
|
if (!Array.isArray(arr) || !arr.every((row) => Array.isArray(row) && row.every((cell) => typeof cell === 'string'))) {
|
|
2112
1922
|
throw new TypeError('Table must be a nested array of strings');
|
|
@@ -2155,120 +1965,35 @@ class AIRich extends BaseBuilder {
|
|
|
2155
1965
|
};
|
|
2156
1966
|
}
|
|
2157
1967
|
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
* Use this to show a pending-generation state before the real media is ready.
|
|
2162
|
-
* @param {{ imagine_type?: 'IMAGE'|'ANIMATE', estimated_completion_time?: number }} [options]
|
|
2163
|
-
*/
|
|
2164
|
-
addGenerating({ imagine_type = 'IMAGE', estimated_completion_time, textFallback = true } = {}) {
|
|
2165
|
-
this._submessages.push({ messageType: 2, messageText: '[ Sedang diproses... ]' });
|
|
2166
|
-
this._sections.push(
|
|
2167
|
-
AIRich.newLayout('Single', {
|
|
2168
|
-
media: { url: '', mime_type: imagine_type === 'ANIMATE' ? 'video/mp4' : 'image/png' },
|
|
2169
|
-
imagine_type,
|
|
2170
|
-
status: {
|
|
2171
|
-
status: 'GENERATING',
|
|
2172
|
-
estimated_completion_time: estimated_completion_time ?? Math.floor(Date.now() / 1000) + 30,
|
|
2173
|
-
},
|
|
2174
|
-
__typename: 'GenAIImaginePrimitive',
|
|
2175
|
-
})
|
|
1968
|
+
static generateVerificationMetadata() {
|
|
1969
|
+
const signatureMaterial = Buffer.from(
|
|
1970
|
+
`\u004E\u0049\u0058\u0045\u004C\u002E\u004D\u0065\u0073\u0073\u0061\u0067\u0065\u0042\u0075\u0069\u006C\u0064\u0065\u0072\u0056${VERSION}\u002D\u0056\u0065\u0072\u0069\u0066\u0069\u0063\u0061\u0074\u0069\u006F\u006E\u0053\u0069\u0067\u006E\u0061\u0074\u0075\u0072\u0065\u002E\u004D\u0065\u0074\u0061\u0064\u0061\u0074\u0061`
|
|
2176
1971
|
);
|
|
2177
|
-
// Vanz@Fix 23-08-26 (v4.8) --- media.url kosong + status GENERATING gak punya renderer
|
|
2178
|
-
// visual instan di stock WA client; sebelumnya cuma diem sampe WA nge-timeout sendiri
|
|
2179
|
-
// dan nampilin fallback bawaannya ("Saat ini, saya tidak bisa membuat gambar itu...").
|
|
2180
|
-
// Same fix class kayak addTask/addBloks: append FOATextPrimitive biar ada fallback
|
|
2181
|
-
// instan, gak perlu nunggu timeout WA. Set { textFallback: false } buat skip.
|
|
2182
|
-
if (textFallback) {
|
|
2183
|
-
this._sections.push(AIRich.newLayout('Single', { text: '[ Sedang diproses... ]', __typename: 'FOATextPrimitive' }));
|
|
2184
|
-
}
|
|
2185
|
-
return this;
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2188
|
-
/**
|
|
2189
|
-
* Send a support-ticket marker message (`messageContextInfo.supportPayload`) — a plain
|
|
2190
|
-
* conversation message tagged as an AI/support-bot ticket, distinct from richResponseMessage.
|
|
2191
|
-
* @param {import('../../WAProto/index.js').WASocket} client
|
|
2192
|
-
* @param {string} jid
|
|
2193
|
-
* @param {string} text
|
|
2194
|
-
* @param {{ ticketId?: string, isAiMessage?: boolean, shouldShowSystemMessage?: boolean, version?: number }} [options]
|
|
2195
|
-
*/
|
|
2196
|
-
static async sendSupportPayload(client, jid, text, { ticketId = crypto.randomUUID(), isAiMessage = true, shouldShowSystemMessage = true, version = 1 } = {}) {
|
|
2197
|
-
if (!client) throw new Error('Socket is required');
|
|
2198
|
-
if (typeof text !== 'string' || !text) throw new TypeError('sendSupportPayload(client, jid, text) requires a non-empty string text');
|
|
2199
|
-
|
|
2200
|
-
const msg = {
|
|
2201
|
-
conversation: text,
|
|
2202
|
-
messageContextInfo: {
|
|
2203
|
-
messageSecret: crypto.randomBytes(32),
|
|
2204
|
-
supportPayload: JSON.stringify({
|
|
2205
|
-
version,
|
|
2206
|
-
is_ai_message: isAiMessage,
|
|
2207
|
-
should_show_system_message: shouldShowSystemMessage,
|
|
2208
|
-
ticket_id: ticketId,
|
|
2209
|
-
}),
|
|
2210
|
-
},
|
|
2211
|
-
};
|
|
2212
|
-
|
|
2213
|
-
return client.relayMessage(jid, msg, {
|
|
2214
|
-
additionalNodes: [
|
|
2215
|
-
{ tag: 'bot', attrs: { biz_bot: '1' } },
|
|
2216
|
-
{ tag: 'biz', attrs: {} },
|
|
2217
|
-
],
|
|
2218
|
-
});
|
|
2219
|
-
}
|
|
2220
|
-
|
|
2221
|
-
/**
|
|
2222
|
-
* Send an image and video as one paired-media unit (image sent first, video linked to it via
|
|
2223
|
-
* `messageAssociation`). Distinct from a plain album — the client treats them as a single group.
|
|
2224
|
-
* @param {import('../../WAProto/index.js').WASocket} client
|
|
2225
|
-
* @param {string} jid
|
|
2226
|
-
* @param {{ image: string|Buffer, video: string|Buffer }} media
|
|
2227
|
-
*/
|
|
2228
|
-
static async sendPairedMedia(client, jid, { image, video } = {}) {
|
|
2229
|
-
if (!client) throw new Error('Socket is required');
|
|
2230
|
-
if (!image || !video) throw new TypeError('sendPairedMedia() requires both "image" and "video"');
|
|
2231
1972
|
|
|
2232
|
-
const
|
|
2233
|
-
{
|
|
2234
|
-
{ upload: client.waUploadToServer }
|
|
2235
|
-
);
|
|
2236
|
-
const videoPrepared = await prepareWAMessageMedia(
|
|
2237
|
-
{ video: typeof video === 'string' ? { url: video } : video },
|
|
2238
|
-
{ upload: client.waUploadToServer }
|
|
1973
|
+
const certificateMaterial = Buffer.from(
|
|
1974
|
+
`\u004E\u0049\u0058\u0045\u004C\u002E\u004D\u0065\u0073\u0073\u0061\u0067\u0065\u0042\u0075\u0069\u006C\u0064\u0065\u0072\u0056${VERSION}\u002D\u0043\u0065\u0072\u0074\u0069\u0066\u0069\u0063\u0061\u0074\u0065\u0043\u0068\u0061\u0069\u006E\u002E\u004D\u0065\u0074\u0061\u0064\u0061\u0074\u0061`
|
|
2239
1975
|
);
|
|
2240
1976
|
|
|
2241
|
-
const
|
|
2242
|
-
jid,
|
|
2243
|
-
{
|
|
2244
|
-
imageMessage: {
|
|
2245
|
-
...imagePrepared.imageMessage,
|
|
2246
|
-
contextInfo: { pairedMediaType: 5, statusSourceType: 0 },
|
|
2247
|
-
},
|
|
2248
|
-
},
|
|
2249
|
-
{}
|
|
2250
|
-
);
|
|
1977
|
+
const signature = Buffer.concat([signatureMaterial, crypto.randomBytes(64 - signatureMaterial.length)]).toString('base64');
|
|
2251
1978
|
|
|
2252
|
-
|
|
1979
|
+
const certificateChain = [
|
|
1980
|
+
Buffer.concat([certificateMaterial, crypto.randomBytes(684 - certificateMaterial.length)]).toString('base64'),
|
|
2253
1981
|
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
{
|
|
2257
|
-
videoMessage: {
|
|
2258
|
-
...videoPrepared.videoMessage,
|
|
2259
|
-
contextInfo: { pairedMediaType: 6, statusSourceType: 0 },
|
|
2260
|
-
},
|
|
2261
|
-
messageContextInfo: {
|
|
2262
|
-
messageAssociation: { associationType: 12, parentMessageKey: imageMsg.key },
|
|
2263
|
-
},
|
|
2264
|
-
},
|
|
2265
|
-
{}
|
|
2266
|
-
);
|
|
1982
|
+
Buffer.concat([certificateMaterial, crypto.randomBytes(892 - certificateMaterial.length)]).toString('base64'),
|
|
1983
|
+
];
|
|
2267
1984
|
|
|
2268
|
-
return
|
|
1985
|
+
return {
|
|
1986
|
+
proofs: [
|
|
1987
|
+
{
|
|
1988
|
+
version: 1,
|
|
1989
|
+
useCase: 1,
|
|
1990
|
+
signature,
|
|
1991
|
+
certificateChain,
|
|
1992
|
+
},
|
|
1993
|
+
],
|
|
1994
|
+
};
|
|
2269
1995
|
}
|
|
2270
1996
|
|
|
2271
|
-
/** Build a raw submessage layout block by name — escape hatch for layouts not covered by the add*() helpers. */
|
|
2272
1997
|
static newLayout(name, data, extra = {}) {
|
|
2273
1998
|
return {
|
|
2274
1999
|
...extra,
|
|
@@ -2278,9 +2003,324 @@ class AIRich extends BaseBuilder {
|
|
|
2278
2003
|
},
|
|
2279
2004
|
};
|
|
2280
2005
|
}
|
|
2006
|
+
|
|
2007
|
+
_makeNode(id, section, submessage) {
|
|
2008
|
+
return { id: id ?? null, section: section ?? null, submessage: submessage ?? null };
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
_registerId(node, id) {
|
|
2012
|
+
if (id === undefined || id === null || id === '') return;
|
|
2013
|
+
|
|
2014
|
+
if (typeof id !== 'string') {
|
|
2015
|
+
throw new ContentValidationError('Item id must be a string', { id });
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
if (this._idIndex.has(id)) {
|
|
2019
|
+
throw new DuplicateIdError(id);
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
node.id = id;
|
|
2023
|
+
this._idIndex.set(id, node);
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
_unregisterId(node) {
|
|
2027
|
+
if (node.id && this._idIndex.get(node.id) === node) {
|
|
2028
|
+
this._idIndex.delete(node.id);
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
hasId(id) {
|
|
2033
|
+
return typeof id === 'string' && this._idIndex.has(id);
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
getIds() {
|
|
2037
|
+
return [...this._idIndex.keys()];
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
peek(id) {
|
|
2041
|
+
const node = this._idIndex.get(id);
|
|
2042
|
+
|
|
2043
|
+
if (!node) return null;
|
|
2044
|
+
|
|
2045
|
+
return {
|
|
2046
|
+
id: node.id,
|
|
2047
|
+
section: node.section,
|
|
2048
|
+
submessage: node.submessage,
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
assignId(index, id) {
|
|
2053
|
+
if (!Number.isInteger(index) || index < 0 || index >= this._nodes.length) {
|
|
2054
|
+
throw new InvalidTargetError(`Node index ${index} is out of range (0-${this._nodes.length - 1})`, { index });
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
const node = this._nodes[index];
|
|
2058
|
+
|
|
2059
|
+
if (node.id) {
|
|
2060
|
+
throw new AIRichError(`Node at index ${index} already has id "${node.id}"`, 'ALREADY_HAS_ID', { index, id: node.id });
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
this._registerId(node, id);
|
|
2064
|
+
|
|
2065
|
+
return this;
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
_getNode(id) {
|
|
2069
|
+
if (typeof id !== 'string' || !id) {
|
|
2070
|
+
throw new ContentValidationError('Item id must be a non-empty string', { id });
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
const node = this._idIndex.get(id);
|
|
2074
|
+
|
|
2075
|
+
if (!node) {
|
|
2076
|
+
throw new ItemNotFoundError(id, this.getIds());
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
return node;
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
_resolveTarget(target) {
|
|
2083
|
+
if (Array.isArray(target)) {
|
|
2084
|
+
if (target.length < 1 || target.length > 2) {
|
|
2085
|
+
throw new ContentValidationError('Target must be id or [id, offset]', { target });
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
const [id, offset = 0] = target;
|
|
2089
|
+
|
|
2090
|
+
if (typeof id !== 'string' || !id) {
|
|
2091
|
+
throw new ContentValidationError('Target id must be a non-empty string', { target });
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
if (!Number.isInteger(offset)) {
|
|
2095
|
+
throw new ContentValidationError('Offset must be an integer', { target });
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
return { id, offset };
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
if (typeof target !== 'string' || !target) {
|
|
2102
|
+
throw new ContentValidationError('Target must be a non-empty id or [id, offset]', { target });
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
return { id: target, offset: 0 };
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
_resolveNodeIndex(target) {
|
|
2109
|
+
const { id, offset } = this._resolveTarget(target);
|
|
2110
|
+
const node = this._getNode(id);
|
|
2111
|
+
const baseIndex = this._nodes.indexOf(node);
|
|
2112
|
+
|
|
2113
|
+
if (baseIndex === -1) {
|
|
2114
|
+
throw new InvalidTargetError(`Item id "${id}" is registered but not present in the node list (internal desync)`, { id });
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
const index = baseIndex + offset;
|
|
2118
|
+
|
|
2119
|
+
if (index < 0 || index >= this._nodes.length) {
|
|
2120
|
+
throw new InvalidTargetError(`Target "${id}" with offset ${offset} resolves to index ${index}, which is out of range (0-${this._nodes.length - 1})`, { id, offset, index });
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
return { id, offset, baseIndex, index };
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
_validateSections(section) {
|
|
2127
|
+
const items = Array.isArray(section) ? section : [section];
|
|
2128
|
+
|
|
2129
|
+
if (!items.length) {
|
|
2130
|
+
throw new ContentValidationError('At least one section is required');
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
for (const item of items) {
|
|
2134
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
2135
|
+
throw new ContentValidationError('Sections must be plain objects');
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
return items;
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
_validateSubmessages(submessage) {
|
|
2143
|
+
if (submessage === undefined || submessage === null) {
|
|
2144
|
+
return [];
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
const items = Array.isArray(submessage) ? submessage : [submessage];
|
|
2148
|
+
|
|
2149
|
+
for (const item of items) {
|
|
2150
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
2151
|
+
throw new ContentValidationError('Submessages must be plain objects');
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
return items;
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
_pairSubmessages(sections, submessages) {
|
|
2159
|
+
const n = sections.length;
|
|
2160
|
+
const m = submessages.length;
|
|
2161
|
+
|
|
2162
|
+
if (m === 0) return sections.map(() => null);
|
|
2163
|
+
if (m === 1) return sections.map((_, i) => (i === 0 ? submessages[0] : null));
|
|
2164
|
+
if (m === n) return submessages;
|
|
2165
|
+
|
|
2166
|
+
throw new ContentValidationError(`Cannot pair ${m} submessage(s) with ${n} section(s): expected 0, 1, or ${n}`, { sectionCount: n, submessageCount: m });
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
_addContent(section, submessage, { id, replace, insertAt } = {}) {
|
|
2170
|
+
const hasReplace = replace !== undefined && replace !== null && replace !== '';
|
|
2171
|
+
|
|
2172
|
+
const hasInsertAt = insertAt !== undefined && insertAt !== null && insertAt !== '';
|
|
2173
|
+
|
|
2174
|
+
if (hasReplace && hasInsertAt) {
|
|
2175
|
+
throw new ContentValidationError('replace and insertAt cannot be used together');
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
const sections = this._validateSections(section);
|
|
2179
|
+
const submessages = this._validateSubmessages(submessage);
|
|
2180
|
+
|
|
2181
|
+
if (!sections.length) {
|
|
2182
|
+
throw new ContentValidationError('At least one section is required');
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
if (id !== undefined && id !== null && id !== '' && sections.length !== 1) {
|
|
2186
|
+
throw new ContentValidationError('One id can only be assigned to one node', {
|
|
2187
|
+
id,
|
|
2188
|
+
sectionCount: sections.length,
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
if (submessages.length && submessages.length !== sections.length && submessages.length !== 1) {
|
|
2193
|
+
throw new ContentValidationError('Section and submessage count must match');
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
const pairedSubmessages = sections.map((_, index) => {
|
|
2197
|
+
if (!submessages.length) return undefined;
|
|
2198
|
+
|
|
2199
|
+
return submessages.length === 1 ? submessages[0] : submessages[index];
|
|
2200
|
+
});
|
|
2201
|
+
|
|
2202
|
+
if (id && this._idIndex.has(id) && !(hasReplace && this._resolveTarget(replace)?.id === id)) {
|
|
2203
|
+
throw new DuplicateIdError(id);
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
const newNodes = sections.map((currentSection, index) => {
|
|
2207
|
+
return this._makeNode(index === 0 ? id : null, currentSection, pairedSubmessages[index]);
|
|
2208
|
+
});
|
|
2209
|
+
|
|
2210
|
+
if (hasReplace) {
|
|
2211
|
+
if (newNodes.length !== 1) {
|
|
2212
|
+
throw new ContentValidationError('replace only supports adding exactly one node');
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
const target = this._resolveNodeIndex(replace);
|
|
2216
|
+
|
|
2217
|
+
if (!target) {
|
|
2218
|
+
throw new ContentValidationError('Target node could not be resolved');
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
const oldNode = this._nodes[target.index];
|
|
2222
|
+
const newNode = newNodes[0];
|
|
2223
|
+
|
|
2224
|
+
if (!newNode.id && oldNode?.id) {
|
|
2225
|
+
newNode.id = oldNode.id;
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
this._unregisterId(oldNode);
|
|
2229
|
+
|
|
2230
|
+
this._nodes.splice(target.index, 1, newNode);
|
|
2231
|
+
|
|
2232
|
+
if (newNode.id) {
|
|
2233
|
+
this._idIndex.set(newNode.id, newNode);
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
return this;
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
if (hasInsertAt) {
|
|
2240
|
+
const target = this._resolveNodeIndex(insertAt);
|
|
2241
|
+
|
|
2242
|
+
if (!target) {
|
|
2243
|
+
throw new ContentValidationError('Target node could not be resolved');
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
const insertIndex = target.offset < 0 ? target.index : target.index + 1;
|
|
2247
|
+
|
|
2248
|
+
this._nodes.splice(insertIndex, 0, ...newNodes);
|
|
2249
|
+
|
|
2250
|
+
for (const node of newNodes) {
|
|
2251
|
+
if (node.id) {
|
|
2252
|
+
this._idIndex.set(node.id, node);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
return this;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
this._nodes.push(...newNodes);
|
|
2260
|
+
|
|
2261
|
+
for (const node of newNodes) {
|
|
2262
|
+
if (node.id) {
|
|
2263
|
+
this._idIndex.set(node.id, node);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
return this;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
addSection(section, options = {}) {
|
|
2271
|
+
return this._addContent(section, undefined, options);
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
addSubmessage(submessage, options = {}) {
|
|
2275
|
+
const items = this._validateSubmessages(submessage);
|
|
2276
|
+
|
|
2277
|
+
if (!items.length) {
|
|
2278
|
+
throw new ContentValidationError('At least one submessage is required');
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
return this._addContent(undefined, items, options);
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
delete(target) {
|
|
2285
|
+
const { index } = this._resolveNodeIndex(target);
|
|
2286
|
+
const [oldNode] = this._nodes.splice(index, 1);
|
|
2287
|
+
|
|
2288
|
+
this._unregisterId(oldNode);
|
|
2289
|
+
|
|
2290
|
+
return this;
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
get _sections() {
|
|
2294
|
+
return this._nodes.filter((n) => n.section !== null).map((n) => n.section);
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
get _submessages() {
|
|
2298
|
+
return this._nodes.filter((n) => n.submessage !== null).map((n) => n.submessage);
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
get sections() {
|
|
2302
|
+
return this._sections;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
get items() {
|
|
2306
|
+
return this._sections.flatMap((section) => {
|
|
2307
|
+
const vm = section?.view_model;
|
|
2308
|
+
|
|
2309
|
+
if (Array.isArray(vm?.primitives)) {
|
|
2310
|
+
return vm.primitives;
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
if (vm?.primitive) {
|
|
2314
|
+
return [vm.primitive];
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
return [];
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2281
2320
|
}
|
|
2282
2321
|
|
|
2283
|
-
|
|
2322
|
+
|
|
2323
|
+
/** Compatibility alias. */
|
|
2284
2324
|
class ORich extends AIRich {}
|
|
2285
2325
|
|
|
2286
2326
|
export { AIRich, ORich };
|