@vanzxy/baileys 2.0.1 → 2.0.2
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 +18 -3
- package/lib/Builders/AIRich.js +914 -1153
- package/lib/Builders/Album.js +71 -0
- package/lib/Builders/Contact.js +83 -0
- package/lib/Builders/Location.js +151 -0
- package/lib/Builders/NativeFlow.js +210 -0
- package/lib/Builders/VanzxyBaileys.js +38 -0
- package/lib/Builders/index.js +5 -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
|
-
const secBefore = target._sections.length;
|
|
102
|
-
|
|
103
|
-
// Vanz@Fix 23-08-26 --- was orig.apply(receiver, args): calling the real method bound to
|
|
104
|
-
// the Proxy itself (`receiver`) makes any `this.#client` access inside throw
|
|
105
|
-
// "Cannot read private member #client from an object whose class did not declare it",
|
|
106
|
-
// because a Proxy is never the branded instance a private field was declared on —
|
|
107
|
-
// this hit every add*() that touches #client via Toolkit.resolveMedia(this.#client, ...)
|
|
108
|
-
// (addProduct/addPost/addReels/addSource, and would eventually hit addImage/addVideo
|
|
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);
|
|
159
72
|
|
|
160
|
-
|
|
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
|
+
}
|
|
161
80
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
81
|
+
this._extraPayload = {};
|
|
82
|
+
|
|
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,180 @@ 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
|
+
addFOAText(text, { id, replace, insertAt } = {}) {
|
|
165
|
+
if (typeof text !== 'string') {
|
|
166
|
+
throw new TypeError('Text must be a string');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const section = AIRich.newLayout('Single', {
|
|
170
|
+
text,
|
|
171
|
+
__typename: 'FOATextPrimitive',
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const submessages = [
|
|
175
|
+
{
|
|
176
|
+
messageType: 2,
|
|
177
|
+
messageText: text,
|
|
178
|
+
},
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
return this._addContent(section, submessages, {
|
|
182
|
+
id,
|
|
183
|
+
replace,
|
|
184
|
+
insertAt,
|
|
185
|
+
});
|
|
236
186
|
}
|
|
237
187
|
|
|
238
|
-
|
|
239
|
-
addCode(language, code) {
|
|
188
|
+
addCode(language, code, { id, replace, insertAt } = {}) {
|
|
240
189
|
if (typeof language !== 'string' || typeof code !== 'string') {
|
|
241
190
|
throw new TypeError('Language and code must be a string');
|
|
242
191
|
}
|
|
243
192
|
|
|
244
193
|
const meta = AIRich.tokenizer(code, language);
|
|
245
194
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
codeBlocks: meta.codeBlock,
|
|
251
|
-
},
|
|
195
|
+
const section = AIRich.newLayout('Single', {
|
|
196
|
+
language,
|
|
197
|
+
code_blocks: meta.unified_codeBlock,
|
|
198
|
+
__typename: 'GenAICodeUXPrimitive',
|
|
252
199
|
});
|
|
253
200
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
201
|
+
const submessages = [
|
|
202
|
+
{
|
|
203
|
+
messageType: 5,
|
|
204
|
+
codeMetadata: {
|
|
205
|
+
codeLanguage: language,
|
|
206
|
+
codeBlocks: meta.codeBlock,
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
];
|
|
261
210
|
|
|
262
|
-
return this
|
|
211
|
+
return this._addContent(section, submessages, {
|
|
212
|
+
id,
|
|
213
|
+
replace,
|
|
214
|
+
insertAt,
|
|
215
|
+
});
|
|
263
216
|
}
|
|
264
217
|
|
|
265
|
-
|
|
266
|
-
addTable(table, { hyperlink = true, citation = true, latex = true } = {}) {
|
|
218
|
+
addTable(table, { hyperlink = true, citation = true, latex = true, id, replace, insertAt } = {}) {
|
|
267
219
|
if (!Array.isArray(table)) {
|
|
268
220
|
throw new TypeError('Table must be an array');
|
|
269
221
|
}
|
|
270
222
|
|
|
271
|
-
const meta = AIRich.toTableMetadata(table, {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
tableMetadata: {
|
|
276
|
-
title: meta.title,
|
|
277
|
-
rows: meta.rows,
|
|
278
|
-
},
|
|
223
|
+
const meta = AIRich.toTableMetadata(table, {
|
|
224
|
+
hyperlink,
|
|
225
|
+
citation,
|
|
226
|
+
latex,
|
|
279
227
|
});
|
|
280
228
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
})
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
return this;
|
|
289
|
-
}
|
|
229
|
+
const section = AIRich.newLayout('Single', {
|
|
230
|
+
rows: meta.unified_rows,
|
|
231
|
+
__typename: 'GenATableUXPrimitive',
|
|
232
|
+
});
|
|
290
233
|
|
|
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',
|
|
234
|
+
const submessages = [
|
|
235
|
+
{
|
|
236
|
+
messageType: 4,
|
|
237
|
+
tableMetadata: {
|
|
238
|
+
title: meta.title,
|
|
239
|
+
rows: meta.rows,
|
|
316
240
|
},
|
|
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
|
-
}
|
|
241
|
+
},
|
|
242
|
+
];
|
|
332
243
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
messageType: 9,
|
|
338
|
-
contentItemsMetadata: { itemsMetadata: items, contentType: 1 },
|
|
244
|
+
return this._addContent(section, submessages, {
|
|
245
|
+
id,
|
|
246
|
+
replace,
|
|
247
|
+
insertAt,
|
|
339
248
|
});
|
|
340
|
-
this._sections.push(AIRich.newLayout('Single', {
|
|
341
|
-
items,
|
|
342
|
-
content_type: 1,
|
|
343
|
-
__typename: 'GenAIContentItemsUXPrimitive',
|
|
344
|
-
}));
|
|
345
|
-
return this;
|
|
346
249
|
}
|
|
347
250
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
251
|
+
addSource(sources = [], { id, replace, insertAt } = {}) {
|
|
252
|
+
if (!Array.isArray(sources)) {
|
|
253
|
+
throw new TypeError('Sources must be an array of strings, arrays, or objects');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const isStringArray = sources.every((item) => typeof item === 'string');
|
|
257
|
+
|
|
258
|
+
const isArrayFormat = sources.every((item) => Array.isArray(item) && item.every((value) => typeof value === 'string'));
|
|
259
|
+
|
|
260
|
+
const isObjectFormat = sources.every((item) => item && typeof item === 'object' && !Array.isArray(item));
|
|
261
|
+
|
|
262
|
+
if (!isStringArray && !isArrayFormat && !isObjectFormat) {
|
|
263
|
+
throw new TypeError('Sources must be a string array, array of string arrays, or array of objects');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (isStringArray) {
|
|
267
|
+
sources = [sources];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const normalizedSources = sources.map((source) => {
|
|
271
|
+
if (Array.isArray(source)) {
|
|
272
|
+
const [icon, url, title, subtitle] = source;
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
icon,
|
|
276
|
+
url,
|
|
277
|
+
title,
|
|
278
|
+
subtitle,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
357
281
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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 }) => ({
|
|
282
|
+
return {
|
|
283
|
+
icon: source.favicon ?? source.icon ?? '',
|
|
284
|
+
url: source.url ?? '',
|
|
285
|
+
title: source.title ?? '',
|
|
286
|
+
subtitle: source.subtitle ?? '',
|
|
287
|
+
};
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const source = normalizedSources.map(({ icon, url, title, subtitle }) => ({
|
|
385
291
|
source_type: 'THIRD_PARTY',
|
|
386
|
-
source_display_name:
|
|
292
|
+
source_display_name: title,
|
|
387
293
|
source_subtitle: subtitle,
|
|
388
294
|
source_url: url,
|
|
389
295
|
favicon: {
|
|
390
|
-
url: Toolkit.resolveMedia(this.#client, icon, 'image'
|
|
296
|
+
url: Toolkit.resolveMedia(this.#client, icon, 'image'),
|
|
391
297
|
mime_type: 'image/jpeg',
|
|
392
298
|
width: 16,
|
|
393
299
|
height: 16,
|
|
394
300
|
},
|
|
395
301
|
}));
|
|
396
302
|
|
|
397
|
-
this.
|
|
398
|
-
AIRich.newLayout('Single', {
|
|
399
|
-
sources: source,
|
|
400
|
-
__typename: 'GenAISearchResultPrimitive',
|
|
401
|
-
})
|
|
402
|
-
);
|
|
303
|
+
const submessage = this.createAlert('GenAISearchResultPrimitive');
|
|
403
304
|
|
|
404
|
-
|
|
305
|
+
const section = AIRich.newLayout('Single', {
|
|
306
|
+
sources: source,
|
|
307
|
+
__typename: 'GenAISearchResultPrimitive',
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
return this._addContent(section, submessage, {
|
|
311
|
+
id,
|
|
312
|
+
replace,
|
|
313
|
+
insertAt,
|
|
314
|
+
});
|
|
405
315
|
}
|
|
406
316
|
|
|
407
|
-
|
|
408
|
-
addReels(reelsItems = [], { resolveUrl = false } = {}) {
|
|
317
|
+
addReels(reelsItems = [], { id, replace, insertAt } = {}) {
|
|
409
318
|
if (
|
|
410
319
|
!(
|
|
411
320
|
(reelsItems && typeof reelsItems === 'object' && !Array.isArray(reelsItems)) ||
|
|
@@ -415,88 +324,64 @@ class AIRich extends BaseBuilder {
|
|
|
415
324
|
throw new TypeError('Reels items must be an object or an array of objects');
|
|
416
325
|
}
|
|
417
326
|
|
|
418
|
-
|
|
419
|
-
reelsItems = [reelsItems];
|
|
420
|
-
}
|
|
327
|
+
const items = Array.isArray(reelsItems) ? reelsItems : [reelsItems];
|
|
421
328
|
|
|
422
|
-
const reels =
|
|
329
|
+
const reels = items.map((item) => ({
|
|
423
330
|
...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'
|
|
331
|
+
_avatar: Toolkit.resolveMedia(this.#client, item.profileIconUrl ?? item.profile_url ?? item.profile ?? '', 'image'),
|
|
332
|
+
_thumbnail: Toolkit.resolveMedia(this.#client, item.thumbnailUrl ?? item.thumbnail ?? '', 'image'),
|
|
426
333
|
}));
|
|
427
334
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
335
|
+
const section = AIRich.newLayout(
|
|
336
|
+
'HScroll',
|
|
337
|
+
reels.map((item) => ({
|
|
338
|
+
reels_url: item.videoUrl ?? item.url ?? '',
|
|
339
|
+
thumbnail_url: item._thumbnail,
|
|
340
|
+
creator: item.username ?? item.title ?? '',
|
|
341
|
+
avatar_url: item._avatar,
|
|
342
|
+
reels_title: item.reels_title ?? item.title ?? '',
|
|
343
|
+
likes_count: item.likes_count ?? item.like ?? 0,
|
|
344
|
+
shares_count: item.shares_count ?? item.share ?? 0,
|
|
345
|
+
view_count: item.view_count ?? item.view ?? 0,
|
|
346
|
+
reel_source: item.reel_source ?? item.source ?? 'IG',
|
|
347
|
+
is_verified: !!(item.is_verified || item.verified),
|
|
348
|
+
__typename: 'GenAIReelPrimitive',
|
|
349
|
+
}))
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const submessages = [
|
|
353
|
+
{
|
|
354
|
+
messageType: 9,
|
|
355
|
+
contentItemsMetadata: {
|
|
356
|
+
contentType: 1,
|
|
357
|
+
itemsMetadata: reels.map((item) => ({
|
|
358
|
+
reelItem: {
|
|
359
|
+
title: item.username ?? '',
|
|
360
|
+
profileIconUrl: item._avatar,
|
|
361
|
+
thumbnailUrl: item._thumbnail,
|
|
362
|
+
videoUrl: item.videoUrl ?? item.url ?? '',
|
|
363
|
+
},
|
|
364
|
+
})),
|
|
365
|
+
},
|
|
440
366
|
},
|
|
441
|
-
|
|
367
|
+
];
|
|
442
368
|
|
|
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
|
-
});
|
|
369
|
+
return this._addContent(section, submessages, {
|
|
370
|
+
id,
|
|
371
|
+
replace,
|
|
372
|
+
insertAt,
|
|
453
373
|
});
|
|
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
374
|
}
|
|
476
375
|
|
|
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 } = {}) {
|
|
376
|
+
addImage(imageUrl, { width, height, status = 'READY', update_text, resolveUrl = false, id, replace, insertAt } = {}) {
|
|
490
377
|
if (!(typeof imageUrl === 'string' || Buffer.isBuffer(imageUrl) || (Array.isArray(imageUrl) && imageUrl.every((v) => typeof v === 'string' || Buffer.isBuffer(v))))) {
|
|
491
378
|
throw new TypeError('imageUrl must be string | buffer | array of string/buffer');
|
|
492
379
|
}
|
|
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
380
|
|
|
497
381
|
const list = Array.isArray(imageUrl)
|
|
498
382
|
? imageUrl.map((v) => {
|
|
499
383
|
const url = Toolkit.resolveMedia(this.#client, v, 'image', { resolveUrl });
|
|
384
|
+
|
|
500
385
|
return {
|
|
501
386
|
imagePreviewUrl: url,
|
|
502
387
|
imageHighResUrl: url,
|
|
@@ -505,6 +390,7 @@ class AIRich extends BaseBuilder {
|
|
|
505
390
|
})
|
|
506
391
|
: (() => {
|
|
507
392
|
const url = Toolkit.resolveMedia(this.#client, imageUrl, 'image', { resolveUrl });
|
|
393
|
+
|
|
508
394
|
return [
|
|
509
395
|
{
|
|
510
396
|
imagePreviewUrl: url,
|
|
@@ -514,115 +400,46 @@ class AIRich extends BaseBuilder {
|
|
|
514
400
|
];
|
|
515
401
|
})();
|
|
516
402
|
|
|
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(
|
|
403
|
+
const sections = list.map(({ imagePreviewUrl }) =>
|
|
593
404
|
AIRich.newLayout('Single', {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
405
|
+
media: {
|
|
406
|
+
url: imagePreviewUrl,
|
|
407
|
+
mime_type: 'image/png',
|
|
408
|
+
width,
|
|
409
|
+
height,
|
|
410
|
+
},
|
|
411
|
+
imagine_type: 'IMAGE',
|
|
412
|
+
status: {
|
|
413
|
+
status,
|
|
414
|
+
update_text,
|
|
598
415
|
},
|
|
599
|
-
|
|
600
|
-
alignment: ALIGNMENT_NAME[alignmentNum],
|
|
601
|
-
tap_link_url: tapLinkUrl,
|
|
602
|
-
__typename: 'GenAIInlineImageUXPrimitive',
|
|
416
|
+
__typename: 'GenAIImaginePrimitive',
|
|
603
417
|
})
|
|
604
418
|
);
|
|
605
419
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
420
|
+
const submessage = {
|
|
421
|
+
messageType: 1,
|
|
422
|
+
gridImageMetadata: {
|
|
423
|
+
gridImageUrl: {
|
|
424
|
+
imagePreviewUrl: list[0]?.imagePreviewUrl,
|
|
425
|
+
},
|
|
426
|
+
imageUrls: list,
|
|
427
|
+
},
|
|
428
|
+
};
|
|
611
429
|
|
|
612
|
-
|
|
430
|
+
if (id && sections.length !== 1) {
|
|
431
|
+
throw new Error('Cannot assign one id to multiple image sections');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return this._addContent(sections, submessage, {
|
|
435
|
+
id,
|
|
436
|
+
replace,
|
|
437
|
+
insertAt,
|
|
438
|
+
});
|
|
613
439
|
}
|
|
614
440
|
|
|
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;
|
|
441
|
+
addVideo(videoUrl, { autoFill = true, status = 'READY', estimatedTime, id, replace, insertAt } = {}) {
|
|
442
|
+
const isObjectVideo = (v) => v && typeof v === 'object' && !Array.isArray(v) && v.url;
|
|
626
443
|
|
|
627
444
|
const isValidPrimitive =
|
|
628
445
|
typeof videoUrl === 'string' ||
|
|
@@ -636,17 +453,15 @@ class AIRich extends BaseBuilder {
|
|
|
636
453
|
|
|
637
454
|
const items = Array.isArray(videoUrl) ? videoUrl : [videoUrl];
|
|
638
455
|
|
|
639
|
-
this.
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
456
|
+
const alert = this.createAlert('GenAIImaginePrimitive (ANIMATE)');
|
|
457
|
+
|
|
458
|
+
const sections = [];
|
|
459
|
+
const submessages = [];
|
|
643
460
|
|
|
644
|
-
|
|
461
|
+
for (const item of items) {
|
|
645
462
|
const isObject = isObjectVideo(item);
|
|
646
463
|
|
|
647
|
-
const url = isObject
|
|
648
|
-
? Toolkit.resolveMedia(this.#client, item.url ?? '', 'video', { resolveUrl })
|
|
649
|
-
: Toolkit.resolveMedia(this.#client, item, 'video', { resolveUrl });
|
|
464
|
+
const url = isObject ? Toolkit.resolveMedia(this.#client, item.url ?? '', 'video') : Toolkit.resolveMedia(this.#client, item, 'video');
|
|
650
465
|
|
|
651
466
|
const bufferPromise = autoFill ? Promise.resolve(url).then((u) => Toolkit.fetchBuffer(u)) : null;
|
|
652
467
|
|
|
@@ -672,17 +487,15 @@ class AIRich extends BaseBuilder {
|
|
|
672
487
|
height: 300,
|
|
673
488
|
})
|
|
674
489
|
: autoFill
|
|
675
|
-
? bufferPromise
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
)
|
|
682
|
-
: null
|
|
490
|
+
? bufferPromise?.then((b) =>
|
|
491
|
+
Toolkit.getMp4Preview(b, {
|
|
492
|
+
time: 0,
|
|
493
|
+
result: 'base64',
|
|
494
|
+
})
|
|
495
|
+
)
|
|
683
496
|
: null;
|
|
684
497
|
|
|
685
|
-
|
|
498
|
+
sections.push(
|
|
686
499
|
AIRich.newLayout('Single', {
|
|
687
500
|
media: {
|
|
688
501
|
url,
|
|
@@ -691,34 +504,37 @@ class AIRich extends BaseBuilder {
|
|
|
691
504
|
duration,
|
|
692
505
|
},
|
|
693
506
|
imagine_type: 'ANIMATE',
|
|
694
|
-
status: {
|
|
507
|
+
status: {
|
|
508
|
+
status,
|
|
509
|
+
estimated_completion_time: estimatedTime != null ? Math.floor((Date.now() + estimatedTime) / 1000) : undefined,
|
|
510
|
+
},
|
|
695
511
|
thumbnail: {
|
|
696
512
|
raw_media: thumbnail,
|
|
697
513
|
},
|
|
698
514
|
__typename: 'GenAIImaginePrimitive',
|
|
699
515
|
})
|
|
700
516
|
);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
return this;
|
|
704
|
-
}
|
|
517
|
+
}
|
|
705
518
|
|
|
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');
|
|
519
|
+
if (alert !== undefined) {
|
|
520
|
+
submessages.push(alert);
|
|
710
521
|
}
|
|
711
522
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
if (missingTitleAt !== -1) {
|
|
715
|
-
throw new TypeError(`addProduct() item[${missingTitleAt}] is missing a required "title"`);
|
|
523
|
+
if (submessages.length > 1) {
|
|
524
|
+
throw new Error('Video content can only have one submessage');
|
|
716
525
|
}
|
|
717
526
|
|
|
718
|
-
this.
|
|
719
|
-
|
|
720
|
-
|
|
527
|
+
return this._addContent(sections, submessages[0], {
|
|
528
|
+
id,
|
|
529
|
+
replace,
|
|
530
|
+
insertAt,
|
|
721
531
|
});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
addProduct(data = {}, { id, replace, insertAt } = {}) {
|
|
535
|
+
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
536
|
+
throw new TypeError('Product items must be an object or an array of objects');
|
|
537
|
+
}
|
|
722
538
|
|
|
723
539
|
const items = Array.isArray(data) ? data : [data];
|
|
724
540
|
|
|
@@ -729,41 +545,41 @@ class AIRich extends BaseBuilder {
|
|
|
729
545
|
sale_price: item.sale_price,
|
|
730
546
|
product_url: item.product_url ?? item.url,
|
|
731
547
|
image: {
|
|
732
|
-
url: Toolkit.resolveMedia(this.#client, item.image_url ?? item.image, 'image'
|
|
548
|
+
url: Toolkit.resolveMedia(this.#client, item.image_url ?? item.image, 'image'),
|
|
733
549
|
},
|
|
734
550
|
additional_images: [
|
|
735
551
|
{
|
|
736
|
-
url: Toolkit.resolveMedia(this.#client, item.icon_url ?? item.icon, 'image'
|
|
552
|
+
url: Toolkit.resolveMedia(this.#client, item.icon_url ?? item.icon, 'image'),
|
|
737
553
|
},
|
|
738
554
|
],
|
|
739
555
|
__typename: 'GenAIProductItemCardPrimitive',
|
|
740
556
|
}));
|
|
741
557
|
|
|
742
|
-
|
|
558
|
+
const section = AIRich.newLayout(Array.isArray(data) ? 'HScroll' : 'Single', Array.isArray(data) ? product : product[0]);
|
|
743
559
|
|
|
744
|
-
|
|
560
|
+
const submessage = this.createAlert('GenAIProductItemCardPrimitive');
|
|
561
|
+
|
|
562
|
+
return this._addContent(section, submessage, {
|
|
563
|
+
id,
|
|
564
|
+
replace,
|
|
565
|
+
insertAt,
|
|
566
|
+
});
|
|
745
567
|
}
|
|
746
568
|
|
|
747
|
-
|
|
748
|
-
addPost(data = {}, { resolveUrl = false } = {}) {
|
|
569
|
+
addPost(data = {}, { id, replace, insertAt } = {}) {
|
|
749
570
|
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
750
571
|
throw new TypeError('Post items must be an object or an array of objects');
|
|
751
572
|
}
|
|
752
573
|
|
|
753
574
|
const posts = Array.isArray(data) ? data : [data];
|
|
754
575
|
|
|
755
|
-
this._submessages.push({
|
|
756
|
-
messageType: 2,
|
|
757
|
-
messageText: '[ Postingan tidak dapat dimuat ]',
|
|
758
|
-
});
|
|
759
|
-
|
|
760
576
|
const primitives = posts.map((p) => ({
|
|
761
577
|
title: p.title ?? '',
|
|
762
578
|
subtitle: p.subtitle ?? '',
|
|
763
579
|
username: p.username ?? '',
|
|
764
|
-
profile_picture_url: Toolkit.resolveMedia(this.#client, p.profile_picture_url ?? p.profile_url ?? p.profile ?? '', 'image'
|
|
580
|
+
profile_picture_url: Toolkit.resolveMedia(this.#client, p.profile_picture_url ?? p.profile_url ?? p.profile ?? '', 'image'),
|
|
765
581
|
is_verified: !!(p.is_verified || p.verified),
|
|
766
|
-
thumbnail_url: Toolkit.resolveMedia(this.#client, p.thumbnail_url ?? p.thumbnail ?? '', 'image'
|
|
582
|
+
thumbnail_url: Toolkit.resolveMedia(this.#client, p.thumbnail_url ?? p.thumbnail ?? '', 'image'),
|
|
767
583
|
post_caption: p.post_caption ?? p.caption ?? '',
|
|
768
584
|
likes_count: p.likes_count ?? p.like ?? 0,
|
|
769
585
|
comments_count: p.comments_count ?? p.comment ?? 0,
|
|
@@ -772,456 +588,191 @@ class AIRich extends BaseBuilder {
|
|
|
772
588
|
post_deeplink: p.post_deeplink ?? p.deeplink ?? '',
|
|
773
589
|
source_app: p.source_app || p.source || 'INSTAGRAM',
|
|
774
590
|
footer_label: p.footer_label ?? p.footer ?? '',
|
|
775
|
-
footer_icon: Toolkit.resolveMedia(this.#client, p.footer_icon ?? p.icon ?? '', 'image'
|
|
591
|
+
footer_icon: Toolkit.resolveMedia(this.#client, p.footer_icon ?? p.icon ?? '', 'image'),
|
|
776
592
|
is_carousel: posts.length > 1,
|
|
777
593
|
orientation: p.orientation ?? 'LANDSCAPE',
|
|
778
594
|
post_type: p.post_type ?? 'VIDEO',
|
|
779
595
|
__typename: 'GenAIPostPrimitive',
|
|
780
596
|
}));
|
|
781
597
|
|
|
782
|
-
|
|
598
|
+
const section = AIRich.newLayout('HScroll', primitives);
|
|
783
599
|
|
|
784
|
-
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
// Vanz@Add 24-08-26 --- ported from temen's MessageBuilderV4.7 (setResponseId/setBotResponseId/
|
|
788
|
-
// refreshResponseId/refreshBotResponseId), rewritten for this fork. Pins the two ids build()
|
|
789
|
-
// generates (see constructor comment) so a rebuilt message can reuse the same response_id/
|
|
790
|
-
// botResponseId — needed for editing an already-sent AIRich message in place.
|
|
791
|
-
|
|
792
|
-
/** Pin `unifiedResponse.response_id` to a specific value instead of a fresh random one each build() — needed to re-send an edited version of an already-sent message in place. */
|
|
793
|
-
setResponseId(id) {
|
|
794
|
-
if (typeof id !== 'string' || !id) throw new TypeError('setResponseId(id) requires a non-empty string');
|
|
795
|
-
this._responseId = id;
|
|
796
|
-
return this;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
/** Un-pin `unifiedResponse.response_id`, generating a fresh crypto.randomUUID() immediately (not deferred to the next build()). */
|
|
800
|
-
refreshResponseId() {
|
|
801
|
-
this._responseId = crypto.randomUUID();
|
|
802
|
-
return this;
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
/** Pin `botMetadata.botResponseId` to a specific value instead of a fresh random one each build(). */
|
|
806
|
-
setBotResponseId(id) {
|
|
807
|
-
if (typeof id !== 'string' || !id) throw new TypeError('setBotResponseId(id) requires a non-empty string');
|
|
808
|
-
this._botResponseId = id;
|
|
809
|
-
return this;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
/** Un-pin `botMetadata.botResponseId`, generating a fresh crypto.randomUUID() immediately. */
|
|
813
|
-
refreshBotResponseId() {
|
|
814
|
-
this._botResponseId = crypto.randomUUID();
|
|
815
|
-
return this;
|
|
816
|
-
}
|
|
600
|
+
const submessage = this.createAlert('GenAIPostPrimitive');
|
|
817
601
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
// remove it. These 4 read/delete that same Map, so no changes to the Proxy itself were needed.
|
|
824
|
-
|
|
825
|
-
/** Check whether a block id was registered by an earlier `add*()`/`set*()` call passing `{id}`. */
|
|
826
|
-
hasId(id) {
|
|
827
|
-
return typeof id === 'string' && this._blocks.has(id);
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
/** List every block id registered so far, in no particular order. */
|
|
831
|
-
getIds() {
|
|
832
|
-
return [...this._blocks.keys()];
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
/** Inspect a registered block without modifying it. Returns `null` if `id` isn't registered. */
|
|
836
|
-
peek(id) {
|
|
837
|
-
const block = this._blocks.get(id);
|
|
838
|
-
if (!block) return null;
|
|
839
|
-
|
|
840
|
-
return { id, sections: [...block.secItems], submessages: [...block.subItems] };
|
|
602
|
+
return this._addContent(section, submessage, {
|
|
603
|
+
id,
|
|
604
|
+
replace,
|
|
605
|
+
insertAt,
|
|
606
|
+
});
|
|
841
607
|
}
|
|
842
608
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
if (!block) throw new Error(`delete(id): no block registered with id "${id}"`);
|
|
847
|
-
|
|
848
|
-
for (const item of block.subItems) {
|
|
849
|
-
const idx = this._submessages.indexOf(item);
|
|
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);
|
|
609
|
+
addMetadata(text, { id, replace, insertAt } = {}) {
|
|
610
|
+
if (typeof text !== 'string') {
|
|
611
|
+
throw new TypeError('Text must be a string');
|
|
855
612
|
}
|
|
856
613
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
/** Add a small metadata-style text line (`GenAIMetadataTextPrimitive`) — same visual style as the auto-appended footer/`addTip()`'s callout, but insertable anywhere and without `addTip()`'s icon prefix. */
|
|
862
|
-
addMetadata(text) {
|
|
863
|
-
if (typeof text !== 'string' || !text) throw new TypeError('addMetadata(text) requires a non-empty string');
|
|
614
|
+
const section = AIRich.newLayout('Single', {
|
|
615
|
+
text,
|
|
616
|
+
__typename: 'GenAIMetadataTextPrimitive',
|
|
617
|
+
});
|
|
864
618
|
|
|
865
|
-
|
|
619
|
+
const submessage = {
|
|
866
620
|
messageType: 2,
|
|
867
621
|
messageText: text,
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
this._sections.push(
|
|
871
|
-
AIRich.newLayout('Single', {
|
|
872
|
-
text,
|
|
873
|
-
__typename: 'GenAIMetadataTextPrimitive',
|
|
874
|
-
})
|
|
875
|
-
);
|
|
622
|
+
};
|
|
876
623
|
|
|
877
|
-
return this
|
|
624
|
+
return this._addContent(section, submessage, {
|
|
625
|
+
id,
|
|
626
|
+
replace,
|
|
627
|
+
insertAt,
|
|
628
|
+
});
|
|
878
629
|
}
|
|
879
630
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
throw new TypeError('addTip(text) requires a non-empty string');
|
|
631
|
+
addTip(text, { id, replace, insertAt } = {}) {
|
|
632
|
+
if (typeof text !== 'string') {
|
|
633
|
+
throw new TypeError('Text must be a string');
|
|
884
634
|
}
|
|
885
635
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
636
|
+
const section = AIRich.newLayout('Single', {
|
|
637
|
+
text: 'ⓘ ' + text,
|
|
638
|
+
__typename: 'GenAIMetadataTextPrimitive',
|
|
889
639
|
});
|
|
890
640
|
|
|
891
|
-
|
|
892
|
-
AIRich.newLayout('Single', {
|
|
893
|
-
text,
|
|
894
|
-
__typename: 'GenAIMetadataTextPrimitive',
|
|
895
|
-
})
|
|
896
|
-
);
|
|
897
|
-
|
|
898
|
-
return this;
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
// Vanz@Add 22-08-26 (v4.7) --- addHeading/addWidget/addFooterAction: 3 primitives
|
|
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.
|
|
909
|
-
|
|
910
|
-
/** Add a large heading-style text block (`FOATextPrimitive`) — visually distinct from `addText()`'s regular paragraph text. */
|
|
911
|
-
addHeading(text) {
|
|
912
|
-
if (typeof text !== 'string' || !text) {
|
|
913
|
-
throw new TypeError('addHeading(text) requires a non-empty string');
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
this._submessages.push({
|
|
641
|
+
const submessage = {
|
|
917
642
|
messageType: 2,
|
|
918
643
|
messageText: text,
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
this._sections.push(
|
|
922
|
-
AIRich.newLayout('Single', {
|
|
923
|
-
text,
|
|
924
|
-
__typename: 'FOATextPrimitive',
|
|
925
|
-
})
|
|
926
|
-
);
|
|
644
|
+
};
|
|
927
645
|
|
|
928
|
-
return this
|
|
646
|
+
return this._addContent(section, submessage, {
|
|
647
|
+
id,
|
|
648
|
+
replace,
|
|
649
|
+
insertAt,
|
|
650
|
+
});
|
|
929
651
|
}
|
|
930
652
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
* rather than opening a url; `kind`/`state` semantics beyond the observed `'OTHER'`/`'PENDING'`
|
|
935
|
-
* defaults aren't publicly documented, so treat this as experimental.
|
|
936
|
-
*
|
|
937
|
-
* Vanz@Add (v4.8) --- accepts an `{ layout }` override so consecutive `addWidget()` calls can
|
|
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];
|
|
947
|
-
|
|
948
|
-
// Vanz@Fix (bug 44) --- layout: 'Single' forces `widgets[0]` below (a "Single" layout's
|
|
949
|
-
// view_model can only ever hold one `primitive`, never a `primitives` array — see
|
|
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`);
|
|
653
|
+
addWidget(data, { layout, id, replace, insertAt, ...options } = {}) {
|
|
654
|
+
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
655
|
+
throw new TypeError('Widget must be an object or an array of objects');
|
|
954
656
|
}
|
|
955
657
|
|
|
956
|
-
|
|
957
|
-
// header.title or top-level title required
|
|
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
|
-
});
|
|
967
|
-
|
|
968
|
-
this._submessages.push({
|
|
969
|
-
messageType: 2,
|
|
970
|
-
messageText: items.map((item) => item.header?.title ?? item.title).join(', '),
|
|
971
|
-
});
|
|
972
|
-
|
|
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
|
-
return {
|
|
986
|
-
header: {
|
|
987
|
-
title: headerTitle,
|
|
988
|
-
...(headerSubtitle !== undefined && { subtitle: headerSubtitle }),
|
|
989
|
-
__typename: 'GenAI3PExtWidgetStandardHeader',
|
|
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',
|
|
1006
|
-
};
|
|
1007
|
-
});
|
|
658
|
+
const isArray = Array.isArray(data);
|
|
1008
659
|
|
|
1009
|
-
const
|
|
1010
|
-
const asArray = resolvedLayout !== 'Single';
|
|
660
|
+
const items = isArray ? data : [data];
|
|
1011
661
|
|
|
1012
|
-
|
|
662
|
+
const widgets = items.map((item) => ({
|
|
663
|
+
__typename: 'GenAI3PExtWidgetPrimitive',
|
|
1013
664
|
|
|
1014
|
-
|
|
1015
|
-
|
|
665
|
+
header: {
|
|
666
|
+
__typename: 'GenAI3PExtWidgetStandardHeader',
|
|
667
|
+
title: item.title ?? '',
|
|
668
|
+
...(item.header ?? {}),
|
|
669
|
+
},
|
|
1016
670
|
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
671
|
+
body: {
|
|
672
|
+
__typename: 'GenAI3PExtCalendarEventList',
|
|
673
|
+
sections: item.sections ?? [],
|
|
674
|
+
|
|
675
|
+
ctas: (item.actions ?? []).map((action) => ({
|
|
676
|
+
__typename: 'GenAI3PExtWidgetCTA',
|
|
677
|
+
label: action.label ?? '',
|
|
678
|
+
state: action.state ?? 'PENDING',
|
|
679
|
+
kind: action.kind ?? 'OTHER',
|
|
680
|
+
tool_call_id: action.tool_call_id ?? action.id ?? '',
|
|
681
|
+
|
|
682
|
+
...(action.toast && {
|
|
683
|
+
toast: {
|
|
684
|
+
__typename: 'GenAI3PExtWidgetToast',
|
|
685
|
+
label: action.toast.label ?? action.label ?? '',
|
|
686
|
+
},
|
|
687
|
+
}),
|
|
688
|
+
})),
|
|
1030
689
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
cta_type: item.type ?? 'OPEN_URL',
|
|
1034
|
-
cta_url: item.url,
|
|
1035
|
-
__typename: 'GenAIFooterActionPrimitive',
|
|
690
|
+
...(item.body ?? {}),
|
|
691
|
+
},
|
|
1036
692
|
}));
|
|
1037
693
|
|
|
1038
|
-
|
|
694
|
+
const section = AIRich.newLayout(layout ?? (isArray ? 'HScroll' : 'Single'), isArray ? widgets : widgets[0], options);
|
|
1039
695
|
|
|
1040
|
-
|
|
1041
|
-
}
|
|
696
|
+
const submessage = this.createAlert('GenAI3PExtWidgetStandardHeader');
|
|
1042
697
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
// (AI_RICH_RESPONSE_LATEX = 8, confirmed in WAProto) with its own latexMetadata, so that one
|
|
1049
|
-
// gets a proper submessage instead of the text fallback.
|
|
1050
|
-
|
|
1051
|
-
/** Add a plain horizontal divider line (`GenAIDividerPrimitive`, no content). */
|
|
1052
|
-
addDivider() {
|
|
1053
|
-
this._submessages.push({ messageType: 2, messageText: '---' });
|
|
1054
|
-
this._sections.push(AIRich.newLayout('Single', { __typename: 'GenAIDividerPrimitive' }));
|
|
1055
|
-
return this;
|
|
698
|
+
return this._addContent(section, submessage, {
|
|
699
|
+
id,
|
|
700
|
+
replace,
|
|
701
|
+
insertAt,
|
|
702
|
+
});
|
|
1056
703
|
}
|
|
1057
704
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
throw new TypeError('addSpacer(spacing) requires a non-negative number');
|
|
705
|
+
addFooterAction(data, { layout, id, replace, insertAt, ...options } = {}) {
|
|
706
|
+
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
707
|
+
throw new TypeError('Footer action must be an object or an array of objects');
|
|
1062
708
|
}
|
|
1063
|
-
this._submessages.push({ messageType: 2, messageText: `spasi ${spacing}` });
|
|
1064
|
-
this._sections.push(AIRich.newLayout('Single', { spacing, __typename: 'GenAISpacerPrimitive' }));
|
|
1065
|
-
return this;
|
|
1066
|
-
}
|
|
1067
709
|
|
|
1068
|
-
|
|
1069
|
-
* Add a rendered LaTeX expression (`GenAILatexUXPrimitive`), with a real `AI_RICH_RESPONSE_LATEX`
|
|
1070
|
-
* submessage (unlike most primitives in this block, this one has a proper proto type).
|
|
1071
|
-
* @param {string} expression LaTeX source, e.g. `'$$E = mc^2$$'`.
|
|
1072
|
-
*/
|
|
1073
|
-
addLatex(expression) {
|
|
1074
|
-
if (typeof expression !== 'string' || !expression) {
|
|
1075
|
-
throw new TypeError('addLatex(expression) requires a non-empty string');
|
|
1076
|
-
}
|
|
1077
|
-
this._submessages.push({
|
|
1078
|
-
messageType: 8,
|
|
1079
|
-
latexMetadata: { text: expression, expressions: [{ latexExpression: expression }] },
|
|
1080
|
-
});
|
|
1081
|
-
this._sections.push(AIRich.newLayout('Single', { latex_expression: expression, __typename: 'GenAILatexUXPrimitive' }));
|
|
1082
|
-
return this;
|
|
1083
|
-
}
|
|
710
|
+
const isArray = Array.isArray(data);
|
|
1084
711
|
|
|
1085
|
-
|
|
1086
|
-
* Add a task/checklist card (`GenAITaskPrimitive`).
|
|
1087
|
-
* @param {{task_id?: string, title: string, subtitle?: string, status?: string}} data
|
|
1088
|
-
*/
|
|
1089
|
-
addTask(data = {}) {
|
|
1090
|
-
if (!data?.title) {
|
|
1091
|
-
throw new TypeError('addTask() requires a "title"');
|
|
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' }));
|
|
1109
|
-
}
|
|
1110
|
-
return this;
|
|
1111
|
-
}
|
|
712
|
+
const items = isArray ? data : [data];
|
|
1112
713
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
* status chip (unlike `addSuggest`, this isn't tappable). Distinct from `addThinkingStatus()`'s
|
|
1116
|
-
* icon/typename.
|
|
1117
|
-
* @param {string} title
|
|
1118
|
-
* @param {{icon?: string, is_in_progress?: boolean}} [options]
|
|
1119
|
-
*/
|
|
1120
|
-
addProgressStatus(title, { icon = 'SEARCH', is_in_progress = true, target_secondary_screen_id, target_secondary_screen_tab_id } = {}) {
|
|
1121
|
-
if (typeof title !== 'string' || !title) {
|
|
1122
|
-
throw new TypeError('addProgressStatus(title) requires a non-empty string');
|
|
1123
|
-
}
|
|
1124
|
-
this._submessages.push({ messageType: 2, messageText: title });
|
|
1125
|
-
const primitive = {
|
|
1126
|
-
title,
|
|
1127
|
-
icon,
|
|
1128
|
-
is_in_progress,
|
|
1129
|
-
meta_search_apps: [],
|
|
1130
|
-
__typename: 'GenAIBotProgressStatusPrimitive',
|
|
1131
|
-
};
|
|
1132
|
-
// NOTE: these two fields must be OMITTED when unset, not sent as `null` —
|
|
1133
|
-
// an explicit null here was reproducibly crashing the WA client renderer
|
|
1134
|
-
// on group-open/media-download. Only include when the caller actually passes one.
|
|
1135
|
-
if (target_secondary_screen_id != null) primitive.target_secondary_screen_id = target_secondary_screen_id;
|
|
1136
|
-
if (target_secondary_screen_tab_id != null) primitive.target_secondary_screen_tab_id = target_secondary_screen_tab_id;
|
|
1137
|
-
this._sections.push(AIRich.newLayout('Single', primitive));
|
|
1138
|
-
return this;
|
|
1139
|
-
}
|
|
714
|
+
const actions = items.map((item) => ({
|
|
715
|
+
__typename: 'GenAIFooterActionPrimitive',
|
|
1140
716
|
|
|
1141
|
-
|
|
1142
|
-
addThinkingStatus(title, { icon = 'THINKING', is_in_progress = true, target_secondary_screen_id, target_secondary_screen_tab_id, textFallback = true } = {}) {
|
|
1143
|
-
if (typeof title !== 'string' || !title) {
|
|
1144
|
-
throw new TypeError('addThinkingStatus(title) requires a non-empty string');
|
|
1145
|
-
}
|
|
1146
|
-
this._submessages.push({ messageType: 2, messageText: title });
|
|
1147
|
-
const primitive = {
|
|
1148
|
-
title,
|
|
1149
|
-
icon,
|
|
1150
|
-
is_in_progress,
|
|
1151
|
-
meta_search_apps: [],
|
|
1152
|
-
__typename: 'GenAIBotThinkingStatusPrimitive',
|
|
1153
|
-
};
|
|
1154
|
-
// Same crash-avoidance rule as addProgressStatus(): omit, never null.
|
|
1155
|
-
if (target_secondary_screen_id != null) primitive.target_secondary_screen_id = target_secondary_screen_id;
|
|
1156
|
-
if (target_secondary_screen_tab_id != null) primitive.target_secondary_screen_tab_id = target_secondary_screen_tab_id;
|
|
1157
|
-
this._sections.push(AIRich.newLayout('Single', primitive));
|
|
1158
|
-
// Safety net: stock WA client doesn't render this primitive's own view (it's meant
|
|
1159
|
-
// as a transient spinner in the official app), so the card shows blank when forwarded.
|
|
1160
|
-
// Append a plain text section so the title is still visible. Set { textFallback: false } to skip.
|
|
1161
|
-
if (textFallback) {
|
|
1162
|
-
this._sections.push(AIRich.newLayout('Single', { text: title, __typename: 'FOATextPrimitive' }));
|
|
1163
|
-
}
|
|
1164
|
-
return this;
|
|
1165
|
-
}
|
|
717
|
+
cta_text: item.text ?? item.cta_text ?? '',
|
|
1166
718
|
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
this.
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
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;
|
|
719
|
+
cta_type: item.type ?? item.cta_type ?? 'OPEN_URL',
|
|
720
|
+
|
|
721
|
+
cta_url: item.url ?? item.cta_url ?? '',
|
|
722
|
+
}));
|
|
723
|
+
|
|
724
|
+
const section = AIRich.newLayout(layout ?? (isArray ? 'HScroll' : 'Single'), isArray ? actions : actions[0], options);
|
|
725
|
+
|
|
726
|
+
const submessage = this.createAlert('GenAIFooterActionPrimitive');
|
|
727
|
+
|
|
728
|
+
return this._addContent(section, submessage, {
|
|
729
|
+
id,
|
|
730
|
+
replace,
|
|
731
|
+
insertAt,
|
|
732
|
+
});
|
|
1191
733
|
}
|
|
1192
734
|
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
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) {
|
|
1201
|
-
throw new TypeError('addBloks() requires a "type"');
|
|
1202
|
-
}
|
|
1203
|
-
this._submessages.push({ messageType: 2, messageText: 'Bloks' });
|
|
1204
|
-
const primitive = {
|
|
1205
|
-
type: data.type,
|
|
1206
|
-
data: data.data ?? '{}',
|
|
1207
|
-
uuid: data.uuid ?? '',
|
|
1208
|
-
versioning_id: data.versioning_id ?? '',
|
|
1209
|
-
__typename: 'FOABloksPrimitive',
|
|
1210
|
-
};
|
|
1211
|
-
// Omit initial_response entirely when unset — same null-field crash as addProgressStatus/addThinkingStatus.
|
|
1212
|
-
if (data.initial_response != null) primitive.initial_response = data.initial_response;
|
|
1213
|
-
this._sections.push(AIRich.newLayout('Single', primitive));
|
|
1214
|
-
// Safety net: FOABloksPrimitive needs a real, client-registered Bloks screen to render
|
|
1215
|
-
// anything — arbitrary/placeholder payloads show up blank. Append a plain text section
|
|
1216
|
-
// so the card isn't empty. Set data.textFallback = false to skip.
|
|
1217
|
-
if (data.textFallback !== false) {
|
|
1218
|
-
this._sections.push(AIRich.newLayout('Single', { text: `Bloks: ${data.type}`, __typename: 'FOATextPrimitive' }));
|
|
735
|
+
addTask(data, { id, replace, insertAt } = {}) {
|
|
736
|
+
if (Array.isArray(data) && data.length === 0) {
|
|
737
|
+
throw new TypeError('Task array must not be empty');
|
|
1219
738
|
}
|
|
1220
|
-
|
|
739
|
+
|
|
740
|
+
const isValidSingle = data && typeof data === 'object' && !Array.isArray(data);
|
|
741
|
+
const isValidArray = Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item));
|
|
742
|
+
|
|
743
|
+
if (!isValidSingle && !isValidArray) {
|
|
744
|
+
throw new TypeError('Task must be an object or a non-empty array of objects');
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
const isArray = Array.isArray(data);
|
|
748
|
+
const items = isArray ? data : [data];
|
|
749
|
+
|
|
750
|
+
const tasks = items.map((item) => {
|
|
751
|
+
if (typeof item.title !== 'string' || !item.title) {
|
|
752
|
+
throw new TypeError('addTask() requires a "title"');
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
return {
|
|
756
|
+
task_id: item.task_id ?? '',
|
|
757
|
+
title: item.title,
|
|
758
|
+
subtitle: item.subtitle ?? '',
|
|
759
|
+
status: item.status ?? 'PENDING',
|
|
760
|
+
__typename: 'GenAITaskPrimitive',
|
|
761
|
+
};
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
const section = AIRich.newLayout(isArray ? 'HScroll' : 'Single', isArray ? tasks : tasks[0]);
|
|
765
|
+
|
|
766
|
+
const submessage = this.createAlert('GenAITaskPrimitive');
|
|
767
|
+
|
|
768
|
+
return this._addContent(section, submessage, {
|
|
769
|
+
id,
|
|
770
|
+
replace,
|
|
771
|
+
insertAt,
|
|
772
|
+
});
|
|
1221
773
|
}
|
|
1222
774
|
|
|
1223
|
-
|
|
1224
|
-
addSuggest(suggestion, { scroll = true, layout } = {}) {
|
|
775
|
+
addSuggest(suggestion, { scroll = true, layout, id, replace, insertAt } = {}) {
|
|
1225
776
|
if (!(typeof suggestion === 'string' || (Array.isArray(suggestion) && suggestion.every((v) => typeof v === 'string')))) {
|
|
1226
777
|
throw new TypeError('Suggestion must be a string or array of strings');
|
|
1227
778
|
}
|
|
@@ -1242,18 +793,28 @@ class AIRich extends BaseBuilder {
|
|
|
1242
793
|
|
|
1243
794
|
const type = layout ?? (suggest.length === 1 ? 'Single' : scroll ? 'HScroll' : 'ActionRow');
|
|
1244
795
|
|
|
1245
|
-
|
|
796
|
+
const section = AIRich.newLayout(type, type === 'Single' ? suggest[0] : suggest, {
|
|
797
|
+
__typename: 'GenAIUnifiedResponseSection',
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
const submessage = this.createAlert('GenAIFollowUpSuggestionPillPrimitive');
|
|
1246
801
|
|
|
1247
|
-
return this
|
|
802
|
+
return this._addContent(section, submessage, {
|
|
803
|
+
id,
|
|
804
|
+
replace,
|
|
805
|
+
insertAt,
|
|
806
|
+
});
|
|
1248
807
|
}
|
|
1249
808
|
|
|
1250
|
-
|
|
1251
|
-
|
|
809
|
+
async build(
|
|
810
|
+
jid,
|
|
811
|
+
{ bypassDownload = true, forwarded = true, notification = false, includesUnifiedResponse = true, includesSubmessages = true, quoted, quotedParticipant, messageId, ...options } = {}
|
|
812
|
+
) {
|
|
1252
813
|
const forward = forwarded
|
|
1253
814
|
? {
|
|
1254
815
|
forwardingScore: 1,
|
|
1255
816
|
isForwarded: true,
|
|
1256
|
-
forwardedAiBotMessageInfo: { botJid: '
|
|
817
|
+
forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' },
|
|
1257
818
|
forwardOrigin: 4,
|
|
1258
819
|
}
|
|
1259
820
|
: {};
|
|
@@ -1271,7 +832,7 @@ class AIRich extends BaseBuilder {
|
|
|
1271
832
|
const qObj = quoted
|
|
1272
833
|
? {
|
|
1273
834
|
stanzaId: quoted?.key?.id || quoted?.id,
|
|
1274
|
-
participant: quotedParticipant || quoted?.key?.participant || quoted?.key?.remoteJid,
|
|
835
|
+
participant: quotedParticipant || quoted?.key?.participant || quoted?.participant || quoted?.key?.remoteJid,
|
|
1275
836
|
quotedType: 0,
|
|
1276
837
|
quotedMessage: typeof quoted === 'object' && quoted !== null ? (quoted.message ?? quoted) : undefined,
|
|
1277
838
|
}
|
|
@@ -1287,140 +848,88 @@ class AIRich extends BaseBuilder {
|
|
|
1287
848
|
]
|
|
1288
849
|
: [...(await waitAllPromises(this._sections))];
|
|
1289
850
|
|
|
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();
|
|
851
|
+
if (this._dynamic) {
|
|
852
|
+
this.refreshResponseId();
|
|
853
|
+
this.refreshBotResponseId();
|
|
854
|
+
}
|
|
1301
855
|
|
|
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
|
-
],
|
|
856
|
+
return generateWAMessageFromContent(
|
|
857
|
+
jid,
|
|
858
|
+
{
|
|
859
|
+
messageContextInfo: {
|
|
860
|
+
deviceListMetadata: {},
|
|
861
|
+
deviceListMetadataVersion: 2,
|
|
862
|
+
botMetadata: {
|
|
863
|
+
messageDisclaimerText: this._title,
|
|
864
|
+
...notif,
|
|
865
|
+
verificationMetadata: AIRich.generateVerificationMetadata(),
|
|
866
|
+
botResponseId: this._botResponseId,
|
|
1319
867
|
},
|
|
1320
|
-
...notif,
|
|
1321
868
|
},
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
869
|
+
...this._extraPayload,
|
|
870
|
+
botForwardedMessage: {
|
|
871
|
+
message: {
|
|
872
|
+
richResponseMessage: {
|
|
873
|
+
messageType: 1,
|
|
874
|
+
submessages: includesSubmessages ? await waitAllPromises(this._submessages) : [],
|
|
875
|
+
unifiedResponse: {
|
|
876
|
+
data: includesUnifiedResponse ? Buffer.from(Toolkit.stringifyEscaped({ response_id: this._responseId, sections })).toString('base64') : '',
|
|
877
|
+
},
|
|
878
|
+
contextInfo: {
|
|
879
|
+
...forward,
|
|
880
|
+
...qObj,
|
|
881
|
+
...this._contextInfo,
|
|
882
|
+
},
|
|
1336
883
|
},
|
|
1337
884
|
},
|
|
1338
885
|
},
|
|
1339
886
|
},
|
|
1340
|
-
|
|
887
|
+
{ messageId: messageId || generateMessageIDV2(), ...options }
|
|
888
|
+
);
|
|
1341
889
|
}
|
|
1342
890
|
|
|
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
|
-
}
|
|
891
|
+
async buildEdit(targetJid, targetId, { msg, messageId, ...options } = {}) {
|
|
892
|
+
if (!msg) {
|
|
893
|
+
msg = (await this.build(targetJid, options)).message;
|
|
1363
894
|
}
|
|
1364
895
|
|
|
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 }));
|
|
896
|
+
const editedMessage = msg;
|
|
1385
897
|
|
|
1386
898
|
if (!editedMessage) {
|
|
1387
|
-
throw new Error('buildEdit:
|
|
899
|
+
throw new Error('buildEdit: msg does not contain botForwardedMessage');
|
|
1388
900
|
}
|
|
1389
901
|
|
|
1390
902
|
return generateWAMessageFromContent(
|
|
1391
903
|
targetJid,
|
|
1392
904
|
{
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
905
|
+
botForwardedMessage: {
|
|
906
|
+
message: {
|
|
907
|
+
protocolMessage: {
|
|
908
|
+
key: {
|
|
909
|
+
remoteJid: targetJid,
|
|
910
|
+
fromMe: true,
|
|
911
|
+
id: targetId,
|
|
912
|
+
},
|
|
913
|
+
type: 14,
|
|
914
|
+
editedMessage,
|
|
915
|
+
},
|
|
1398
916
|
},
|
|
1399
|
-
type: 14, // MESSAGE_EDIT
|
|
1400
|
-
editedMessage,
|
|
1401
917
|
},
|
|
1402
918
|
},
|
|
1403
919
|
{ messageId: messageId || generateMessageIDV2(), ...options }
|
|
1404
920
|
);
|
|
1405
921
|
}
|
|
1406
922
|
|
|
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
923
|
async sendEdit(jid, id, { msg, messageId, additionalNodes = [], ...options } = {}) {
|
|
1415
924
|
jid = jid ?? this._lastMessageKey?.remoteJid;
|
|
1416
925
|
id = id ?? this._lastMessageKey?.id;
|
|
1417
926
|
|
|
1418
927
|
if (!jid) {
|
|
1419
|
-
throw new Error('
|
|
928
|
+
throw new Error('JID is required');
|
|
1420
929
|
}
|
|
1421
930
|
|
|
1422
931
|
if (!id) {
|
|
1423
|
-
throw new Error('
|
|
932
|
+
throw new Error('Message id is required');
|
|
1424
933
|
}
|
|
1425
934
|
|
|
1426
935
|
const msgEdit = await this.buildEdit(jid, id, {
|
|
@@ -1434,13 +943,36 @@ class AIRich extends BaseBuilder {
|
|
|
1434
943
|
additionalNodes,
|
|
1435
944
|
});
|
|
1436
945
|
|
|
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
946
|
return msgEdit;
|
|
1441
947
|
}
|
|
1442
948
|
|
|
1443
|
-
|
|
949
|
+
async send(jid, { bypassDownload = true, forwarded = true, notification = false, includesUnifiedResponse = true, includesSubmessages = true, messageId, additionalNodes = [], ...options } = {}) {
|
|
950
|
+
const msg = await this.build(jid, {
|
|
951
|
+
forwarded,
|
|
952
|
+
notification,
|
|
953
|
+
includesUnifiedResponse,
|
|
954
|
+
includesSubmessages,
|
|
955
|
+
messageId,
|
|
956
|
+
...options,
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
|
|
960
|
+
messageId: msg.key.id,
|
|
961
|
+
additionalNodes,
|
|
962
|
+
...options,
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
if (includesUnifiedResponse && bypassDownload) {
|
|
966
|
+
await this.sendEdit(jid, msg.key.id, {
|
|
967
|
+
msg: msg.message,
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
this._lastMessageKey = msg.key;
|
|
972
|
+
|
|
973
|
+
return msg;
|
|
974
|
+
}
|
|
975
|
+
|
|
1444
976
|
static tokenizer(code, lang = 'javascript') {
|
|
1445
977
|
const keywordsMap = {
|
|
1446
978
|
javascript: new Set([
|
|
@@ -2106,7 +1638,6 @@ class AIRich extends BaseBuilder {
|
|
|
2106
1638
|
};
|
|
2107
1639
|
}
|
|
2108
1640
|
|
|
2109
|
-
/** Convert a raw `string[][]` grid into the table metadata shape addTable()/addText() produce internally. */
|
|
2110
1641
|
static toTableMetadata(arr, { hyperlink = true, citation = true, latex = true } = {}) {
|
|
2111
1642
|
if (!Array.isArray(arr) || !arr.every((row) => Array.isArray(row) && row.every((cell) => typeof cell === 'string'))) {
|
|
2112
1643
|
throw new TypeError('Table must be a nested array of strings');
|
|
@@ -2155,120 +1686,35 @@ class AIRich extends BaseBuilder {
|
|
|
2155
1686
|
};
|
|
2156
1687
|
}
|
|
2157
1688
|
|
|
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
|
-
})
|
|
1689
|
+
static generateVerificationMetadata() {
|
|
1690
|
+
const signatureMaterial = Buffer.from(
|
|
1691
|
+
`\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
1692
|
);
|
|
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
1693
|
|
|
2221
|
-
|
|
2222
|
-
|
|
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
|
-
|
|
2232
|
-
const imagePrepared = await prepareWAMessageMedia(
|
|
2233
|
-
{ image: typeof image === 'string' ? { url: image } : image },
|
|
2234
|
-
{ upload: client.waUploadToServer }
|
|
2235
|
-
);
|
|
2236
|
-
const videoPrepared = await prepareWAMessageMedia(
|
|
2237
|
-
{ video: typeof video === 'string' ? { url: video } : video },
|
|
2238
|
-
{ upload: client.waUploadToServer }
|
|
1694
|
+
const certificateMaterial = Buffer.from(
|
|
1695
|
+
`\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
1696
|
);
|
|
2240
1697
|
|
|
2241
|
-
const
|
|
2242
|
-
jid,
|
|
2243
|
-
{
|
|
2244
|
-
imageMessage: {
|
|
2245
|
-
...imagePrepared.imageMessage,
|
|
2246
|
-
contextInfo: { pairedMediaType: 5, statusSourceType: 0 },
|
|
2247
|
-
},
|
|
2248
|
-
},
|
|
2249
|
-
{}
|
|
2250
|
-
);
|
|
1698
|
+
const signature = Buffer.concat([signatureMaterial, crypto.randomBytes(64 - signatureMaterial.length)]).toString('base64');
|
|
2251
1699
|
|
|
2252
|
-
|
|
1700
|
+
const certificateChain = [
|
|
1701
|
+
Buffer.concat([certificateMaterial, crypto.randomBytes(684 - certificateMaterial.length)]).toString('base64'),
|
|
2253
1702
|
|
|
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
|
-
);
|
|
1703
|
+
Buffer.concat([certificateMaterial, crypto.randomBytes(892 - certificateMaterial.length)]).toString('base64'),
|
|
1704
|
+
];
|
|
2267
1705
|
|
|
2268
|
-
return
|
|
1706
|
+
return {
|
|
1707
|
+
proofs: [
|
|
1708
|
+
{
|
|
1709
|
+
version: 1,
|
|
1710
|
+
useCase: 1,
|
|
1711
|
+
signature,
|
|
1712
|
+
certificateChain,
|
|
1713
|
+
},
|
|
1714
|
+
],
|
|
1715
|
+
};
|
|
2269
1716
|
}
|
|
2270
1717
|
|
|
2271
|
-
/** Build a raw submessage layout block by name — escape hatch for layouts not covered by the add*() helpers. */
|
|
2272
1718
|
static newLayout(name, data, extra = {}) {
|
|
2273
1719
|
return {
|
|
2274
1720
|
...extra,
|
|
@@ -2278,9 +1724,324 @@ class AIRich extends BaseBuilder {
|
|
|
2278
1724
|
},
|
|
2279
1725
|
};
|
|
2280
1726
|
}
|
|
1727
|
+
|
|
1728
|
+
_makeNode(id, section, submessage) {
|
|
1729
|
+
return { id: id ?? null, section: section ?? null, submessage: submessage ?? null };
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
_registerId(node, id) {
|
|
1733
|
+
if (id === undefined || id === null || id === '') return;
|
|
1734
|
+
|
|
1735
|
+
if (typeof id !== 'string') {
|
|
1736
|
+
throw new ContentValidationError('Item id must be a string', { id });
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
if (this._idIndex.has(id)) {
|
|
1740
|
+
throw new DuplicateIdError(id);
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
node.id = id;
|
|
1744
|
+
this._idIndex.set(id, node);
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
_unregisterId(node) {
|
|
1748
|
+
if (node.id && this._idIndex.get(node.id) === node) {
|
|
1749
|
+
this._idIndex.delete(node.id);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
hasId(id) {
|
|
1754
|
+
return typeof id === 'string' && this._idIndex.has(id);
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
getIds() {
|
|
1758
|
+
return [...this._idIndex.keys()];
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
peek(id) {
|
|
1762
|
+
const node = this._idIndex.get(id);
|
|
1763
|
+
|
|
1764
|
+
if (!node) return null;
|
|
1765
|
+
|
|
1766
|
+
return {
|
|
1767
|
+
id: node.id,
|
|
1768
|
+
section: node.section,
|
|
1769
|
+
submessage: node.submessage,
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
assignId(index, id) {
|
|
1774
|
+
if (!Number.isInteger(index) || index < 0 || index >= this._nodes.length) {
|
|
1775
|
+
throw new InvalidTargetError(`Node index ${index} is out of range (0-${this._nodes.length - 1})`, { index });
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
const node = this._nodes[index];
|
|
1779
|
+
|
|
1780
|
+
if (node.id) {
|
|
1781
|
+
throw new AIRichError(`Node at index ${index} already has id "${node.id}"`, 'ALREADY_HAS_ID', { index, id: node.id });
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
this._registerId(node, id);
|
|
1785
|
+
|
|
1786
|
+
return this;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
_getNode(id) {
|
|
1790
|
+
if (typeof id !== 'string' || !id) {
|
|
1791
|
+
throw new ContentValidationError('Item id must be a non-empty string', { id });
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
const node = this._idIndex.get(id);
|
|
1795
|
+
|
|
1796
|
+
if (!node) {
|
|
1797
|
+
throw new ItemNotFoundError(id, this.getIds());
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
return node;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
_resolveTarget(target) {
|
|
1804
|
+
if (Array.isArray(target)) {
|
|
1805
|
+
if (target.length < 1 || target.length > 2) {
|
|
1806
|
+
throw new ContentValidationError('Target must be id or [id, offset]', { target });
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
const [id, offset = 0] = target;
|
|
1810
|
+
|
|
1811
|
+
if (typeof id !== 'string' || !id) {
|
|
1812
|
+
throw new ContentValidationError('Target id must be a non-empty string', { target });
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
if (!Number.isInteger(offset)) {
|
|
1816
|
+
throw new ContentValidationError('Offset must be an integer', { target });
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
return { id, offset };
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
if (typeof target !== 'string' || !target) {
|
|
1823
|
+
throw new ContentValidationError('Target must be a non-empty id or [id, offset]', { target });
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
return { id: target, offset: 0 };
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
_resolveNodeIndex(target) {
|
|
1830
|
+
const { id, offset } = this._resolveTarget(target);
|
|
1831
|
+
const node = this._getNode(id);
|
|
1832
|
+
const baseIndex = this._nodes.indexOf(node);
|
|
1833
|
+
|
|
1834
|
+
if (baseIndex === -1) {
|
|
1835
|
+
throw new InvalidTargetError(`Item id "${id}" is registered but not present in the node list (internal desync)`, { id });
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
const index = baseIndex + offset;
|
|
1839
|
+
|
|
1840
|
+
if (index < 0 || index >= this._nodes.length) {
|
|
1841
|
+
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 });
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
return { id, offset, baseIndex, index };
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
_validateSections(section) {
|
|
1848
|
+
const items = Array.isArray(section) ? section : [section];
|
|
1849
|
+
|
|
1850
|
+
if (!items.length) {
|
|
1851
|
+
throw new ContentValidationError('At least one section is required');
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
for (const item of items) {
|
|
1855
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
1856
|
+
throw new ContentValidationError('Sections must be plain objects');
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
return items;
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
_validateSubmessages(submessage) {
|
|
1864
|
+
if (submessage === undefined || submessage === null) {
|
|
1865
|
+
return [];
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
const items = Array.isArray(submessage) ? submessage : [submessage];
|
|
1869
|
+
|
|
1870
|
+
for (const item of items) {
|
|
1871
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
1872
|
+
throw new ContentValidationError('Submessages must be plain objects');
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
return items;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
_pairSubmessages(sections, submessages) {
|
|
1880
|
+
const n = sections.length;
|
|
1881
|
+
const m = submessages.length;
|
|
1882
|
+
|
|
1883
|
+
if (m === 0) return sections.map(() => null);
|
|
1884
|
+
if (m === 1) return sections.map((_, i) => (i === 0 ? submessages[0] : null));
|
|
1885
|
+
if (m === n) return submessages;
|
|
1886
|
+
|
|
1887
|
+
throw new ContentValidationError(`Cannot pair ${m} submessage(s) with ${n} section(s): expected 0, 1, or ${n}`, { sectionCount: n, submessageCount: m });
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
_addContent(section, submessage, { id, replace, insertAt } = {}) {
|
|
1891
|
+
const hasReplace = replace !== undefined && replace !== null && replace !== '';
|
|
1892
|
+
|
|
1893
|
+
const hasInsertAt = insertAt !== undefined && insertAt !== null && insertAt !== '';
|
|
1894
|
+
|
|
1895
|
+
if (hasReplace && hasInsertAt) {
|
|
1896
|
+
throw new ContentValidationError('replace and insertAt cannot be used together');
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
const sections = this._validateSections(section);
|
|
1900
|
+
const submessages = this._validateSubmessages(submessage);
|
|
1901
|
+
|
|
1902
|
+
if (!sections.length) {
|
|
1903
|
+
throw new ContentValidationError('At least one section is required');
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
if (id !== undefined && id !== null && id !== '' && sections.length !== 1) {
|
|
1907
|
+
throw new ContentValidationError('One id can only be assigned to one node', {
|
|
1908
|
+
id,
|
|
1909
|
+
sectionCount: sections.length,
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
if (submessages.length && submessages.length !== sections.length && submessages.length !== 1) {
|
|
1914
|
+
throw new ContentValidationError('Section and submessage count must match');
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
const pairedSubmessages = sections.map((_, index) => {
|
|
1918
|
+
if (!submessages.length) return undefined;
|
|
1919
|
+
|
|
1920
|
+
return submessages.length === 1 ? submessages[0] : submessages[index];
|
|
1921
|
+
});
|
|
1922
|
+
|
|
1923
|
+
if (id && this._idIndex.has(id) && !(hasReplace && this._resolveTarget(replace)?.id === id)) {
|
|
1924
|
+
throw new DuplicateIdError(id);
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
const newNodes = sections.map((currentSection, index) => {
|
|
1928
|
+
return this._makeNode(index === 0 ? id : null, currentSection, pairedSubmessages[index]);
|
|
1929
|
+
});
|
|
1930
|
+
|
|
1931
|
+
if (hasReplace) {
|
|
1932
|
+
if (newNodes.length !== 1) {
|
|
1933
|
+
throw new ContentValidationError('replace only supports adding exactly one node');
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
const target = this._resolveNodeIndex(replace);
|
|
1937
|
+
|
|
1938
|
+
if (!target) {
|
|
1939
|
+
throw new ContentValidationError('Target node could not be resolved');
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
const oldNode = this._nodes[target.index];
|
|
1943
|
+
const newNode = newNodes[0];
|
|
1944
|
+
|
|
1945
|
+
if (!newNode.id && oldNode?.id) {
|
|
1946
|
+
newNode.id = oldNode.id;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
this._unregisterId(oldNode);
|
|
1950
|
+
|
|
1951
|
+
this._nodes.splice(target.index, 1, newNode);
|
|
1952
|
+
|
|
1953
|
+
if (newNode.id) {
|
|
1954
|
+
this._idIndex.set(newNode.id, newNode);
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
return this;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
if (hasInsertAt) {
|
|
1961
|
+
const target = this._resolveNodeIndex(insertAt);
|
|
1962
|
+
|
|
1963
|
+
if (!target) {
|
|
1964
|
+
throw new ContentValidationError('Target node could not be resolved');
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
const insertIndex = target.offset < 0 ? target.index : target.index + 1;
|
|
1968
|
+
|
|
1969
|
+
this._nodes.splice(insertIndex, 0, ...newNodes);
|
|
1970
|
+
|
|
1971
|
+
for (const node of newNodes) {
|
|
1972
|
+
if (node.id) {
|
|
1973
|
+
this._idIndex.set(node.id, node);
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
return this;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
this._nodes.push(...newNodes);
|
|
1981
|
+
|
|
1982
|
+
for (const node of newNodes) {
|
|
1983
|
+
if (node.id) {
|
|
1984
|
+
this._idIndex.set(node.id, node);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
return this;
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
addSection(section, options = {}) {
|
|
1992
|
+
return this._addContent(section, undefined, options);
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
addSubmessage(submessage, options = {}) {
|
|
1996
|
+
const items = this._validateSubmessages(submessage);
|
|
1997
|
+
|
|
1998
|
+
if (!items.length) {
|
|
1999
|
+
throw new ContentValidationError('At least one submessage is required');
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
return this._addContent(undefined, items, options);
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
delete(target) {
|
|
2006
|
+
const { index } = this._resolveNodeIndex(target);
|
|
2007
|
+
const [oldNode] = this._nodes.splice(index, 1);
|
|
2008
|
+
|
|
2009
|
+
this._unregisterId(oldNode);
|
|
2010
|
+
|
|
2011
|
+
return this;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
get _sections() {
|
|
2015
|
+
return this._nodes.filter((n) => n.section !== null).map((n) => n.section);
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
get _submessages() {
|
|
2019
|
+
return this._nodes.filter((n) => n.submessage !== null).map((n) => n.submessage);
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
get sections() {
|
|
2023
|
+
return this._sections;
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
get items() {
|
|
2027
|
+
return this._sections.flatMap((section) => {
|
|
2028
|
+
const vm = section?.view_model;
|
|
2029
|
+
|
|
2030
|
+
if (Array.isArray(vm?.primitives)) {
|
|
2031
|
+
return vm.primitives;
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
if (vm?.primitive) {
|
|
2035
|
+
return [vm.primitive];
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
return [];
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2281
2041
|
}
|
|
2282
2042
|
|
|
2283
|
-
|
|
2043
|
+
|
|
2044
|
+
/** Compatibility alias. */
|
|
2284
2045
|
class ORich extends AIRich {}
|
|
2285
2046
|
|
|
2286
2047
|
export { AIRich, ORich };
|