@vanzxy/baileys 1.5.3 → 1.5.5
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/lib/Utils/MessageBuilder.js +958 -943
- package/package.json +1 -1
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
const MESSAGE_BUILDER_VERSION = '4.9';
|
|
48
48
|
|
|
49
49
|
import { generateWAMessageFromContent, prepareWAMessageMedia } from './messages.js';
|
|
50
|
+
import { generateMessageIDV2 } from './generics.js';
|
|
50
51
|
import { botMetadataSignature, botMetadataCertificate } from './rich-message-utils.js';
|
|
51
52
|
import crypto from 'crypto';
|
|
52
53
|
import { PassThrough, Readable } from 'stream';
|
|
@@ -293,15 +294,19 @@ class Toolkit {
|
|
|
293
294
|
return await waitAllPromises(input);
|
|
294
295
|
}
|
|
295
296
|
|
|
296
|
-
/** Fetch `url` into a Buffer. @param {boolean} [silent] Return an empty Buffer instead of throwing on failure. */
|
|
297
|
-
static async fetchBuffer(url, options = {}, { silent = true } = {}) {
|
|
297
|
+
/** Fetch `url` into a Buffer. @param {boolean} [silent] Return an empty Buffer instead of throwing on failure. @param {number} [timeout] Abort after this many ms (default 15s) instead of hanging indefinitely on a dead/slow host. */
|
|
298
|
+
static async fetchBuffer(url, options = {}, { silent = true, timeout = 15000 } = {}) {
|
|
299
|
+
const controller = new AbortController();
|
|
300
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
298
301
|
try {
|
|
299
|
-
let response = await fetch(url, options);
|
|
302
|
+
let response = await fetch(url, { ...options, signal: options.signal ?? controller.signal });
|
|
300
303
|
if (!response.ok) throw Error(`HTTP ${response.status}`);
|
|
301
304
|
return Buffer.from(await response.arrayBuffer());
|
|
302
305
|
} catch (error) {
|
|
303
306
|
if (silent) return Buffer.alloc(0);
|
|
304
307
|
throw error;
|
|
308
|
+
} finally {
|
|
309
|
+
clearTimeout(timer);
|
|
305
310
|
}
|
|
306
311
|
}
|
|
307
312
|
|
|
@@ -527,6 +532,10 @@ class Toolkit {
|
|
|
527
532
|
}
|
|
528
533
|
});
|
|
529
534
|
}
|
|
535
|
+
|
|
536
|
+
static stringifyEscaped(obj) {
|
|
537
|
+
return JSON.stringify(obj).replace(/[\u007f-\uffff]/g, (c) => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0'));
|
|
538
|
+
}
|
|
530
539
|
}
|
|
531
540
|
|
|
532
541
|
/**
|
|
@@ -1568,10 +1577,48 @@ class Poll extends BaseBuilder {
|
|
|
1568
1577
|
* everything ChatGPT/Gemini-in-WhatsApp-style bots typically render.
|
|
1569
1578
|
* Also exported as `AIVanzxy` / `LeafRich` / `VanzxyAI` / `VanzxyRich` (identical class, alternate names).
|
|
1570
1579
|
*/
|
|
1580
|
+
|
|
1581
|
+
class AIRichError extends Error {
|
|
1582
|
+
constructor(message, code, meta = {}) {
|
|
1583
|
+
super(message);
|
|
1584
|
+
this.name = 'AIRichError';
|
|
1585
|
+
this.code = code;
|
|
1586
|
+
Object.assign(this, meta);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
class ItemNotFoundError extends AIRichError {
|
|
1591
|
+
constructor(id, knownIds = []) {
|
|
1592
|
+
super(`Item with id "${id}" not found`, 'ITEM_NOT_FOUND', { id, knownIds });
|
|
1593
|
+
this.name = 'ItemNotFoundError';
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
class DuplicateIdError extends AIRichError {
|
|
1598
|
+
constructor(id) {
|
|
1599
|
+
super(`Id "${id}" is already in use`, 'DUPLICATE_ID', { id });
|
|
1600
|
+
this.name = 'DuplicateIdError';
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
class InvalidTargetError extends AIRichError {
|
|
1605
|
+
constructor(message, meta = {}) {
|
|
1606
|
+
super(message, 'INVALID_TARGET', meta);
|
|
1607
|
+
this.name = 'InvalidTargetError';
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
class ContentValidationError extends AIRichError {
|
|
1612
|
+
constructor(message, meta = {}) {
|
|
1613
|
+
super(message, 'CONTENT_VALIDATION', meta);
|
|
1614
|
+
this.name = 'ContentValidationError';
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1571
1618
|
class AIRich extends BaseBuilder {
|
|
1572
1619
|
#client;
|
|
1573
1620
|
|
|
1574
|
-
constructor(client) {
|
|
1621
|
+
constructor(client, { dynamic = true, unsupportedTypeAlert = true } = {}) {
|
|
1575
1622
|
if (!client) {
|
|
1576
1623
|
throw new Error('Socket is required');
|
|
1577
1624
|
}
|
|
@@ -1579,148 +1626,125 @@ class AIRich extends BaseBuilder {
|
|
|
1579
1626
|
super();
|
|
1580
1627
|
this.#client = client;
|
|
1581
1628
|
this._contextInfo = {};
|
|
1582
|
-
this.
|
|
1583
|
-
this.
|
|
1584
|
-
this.
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
};
|
|
1629
|
+
this._nodes = [];
|
|
1630
|
+
this._idIndex = new Map();
|
|
1631
|
+
this._unsupportedTypeAlert = !!unsupportedTypeAlert;
|
|
1632
|
+
this._dynamic = !!dynamic;
|
|
1633
|
+
this._responseId = crypto.randomUUID();
|
|
1634
|
+
this._botResponseId = crypto.randomUUID();
|
|
1635
|
+
this._lastMessageKey = null;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
loadFrom(msg) {
|
|
1639
|
+
if (!msg) throw new Error('AI Rich message needed');
|
|
1640
|
+
|
|
1641
|
+
const message = msg.message ?? msg;
|
|
1642
|
+
|
|
1643
|
+
let richResponseMessage = message?.botForwardedMessage?.message?.richResponseMessage;
|
|
1644
|
+
|
|
1645
|
+
if (!richResponseMessage) {
|
|
1646
|
+
richResponseMessage = message?.botForwardedMessage?.richResponseMessage;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
if (!richResponseMessage) {
|
|
1650
|
+
richResponseMessage = message?.richResponseMessage;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
if (!richResponseMessage) {
|
|
1654
|
+
throw new Error('richResponseMessage not found');
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
const messageContextInfo = message?.messageContextInfo ?? {};
|
|
1658
|
+
const botMetadata = messageContextInfo?.botMetadata ?? {};
|
|
1659
|
+
|
|
1660
|
+
this._title = botMetadata?.messageDisclaimerText ?? '';
|
|
1661
|
+
|
|
1662
|
+
this._contextInfo = structuredClone(richResponseMessage?.contextInfo ?? {});
|
|
1663
|
+
|
|
1664
|
+
const loadedSubmessages = Array.isArray(richResponseMessage?.submessages) ? structuredClone(richResponseMessage.submessages) : [];
|
|
1665
|
+
|
|
1666
|
+
let loadedSections = [];
|
|
1667
|
+
|
|
1668
|
+
const unifiedData = richResponseMessage?.unifiedResponse?.data;
|
|
1669
|
+
|
|
1670
|
+
if (unifiedData) {
|
|
1671
|
+
try {
|
|
1672
|
+
const decoded = Buffer.from(unifiedData, 'base64').toString('utf8');
|
|
1673
|
+
const unifiedResponse = JSON.parse(decoded);
|
|
1674
|
+
|
|
1675
|
+
if (Array.isArray(unifiedResponse?.sections)) {
|
|
1676
|
+
loadedSections = structuredClone(unifiedResponse.sections);
|
|
1631
1677
|
}
|
|
1678
|
+
} catch {}
|
|
1679
|
+
}
|
|
1632
1680
|
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
const id = opts?.id;
|
|
1636
|
-
const insertAt = opts?.insertAt;
|
|
1637
|
-
|
|
1638
|
-
const subBefore = target._submessages.length;
|
|
1639
|
-
const secBefore = target._sections.length;
|
|
1640
|
-
|
|
1641
|
-
// Vanz@Fix 23-08-26 --- was orig.apply(receiver, args): calling the real method bound to
|
|
1642
|
-
// the Proxy itself (`receiver`) makes any `this.#client` access inside throw
|
|
1643
|
-
// "Cannot read private member #client from an object whose class did not declare it",
|
|
1644
|
-
// because a Proxy is never the branded instance a private field was declared on —
|
|
1645
|
-
// this hit every add*() that touches #client via Toolkit.resolveMedia(this.#client, ...)
|
|
1646
|
-
// (addProduct/addPost/addReels/addSource, and would eventually hit addImage/addVideo
|
|
1647
|
-
// too once JIT/engine specifics changed). Binding to `target` (the real instance) instead
|
|
1648
|
-
// fixes it for good; `target._submessages`/`target._sections` below are unaffected since
|
|
1649
|
-
// they're plain properties, and `result === target ? receiver : result` still converts a
|
|
1650
|
-
// `this`-return back to the Proxy so chaining (`.addX().addY()`) keeps working.
|
|
1651
|
-
const result = orig.apply(target, args);
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
const subItems = target._submessages.splice(subBefore);
|
|
1655
|
-
const secItems = target._sections.splice(secBefore);
|
|
1656
|
-
|
|
1657
|
-
if (insertAt) {
|
|
1658
|
-
const anchor = target._blocks.get(insertAt);
|
|
1659
|
-
if (!anchor) throw new Error(`insertAt: no block registered with id "${insertAt}" (register it by passing { id: "${insertAt}" } on an earlier add*() call)`);
|
|
1660
|
-
|
|
1661
|
-
const lastSub = anchor.subItems[anchor.subItems.length - 1];
|
|
1662
|
-
const subIdx = lastSub ? target._submessages.indexOf(lastSub) + 1 : target._submessages.length;
|
|
1663
|
-
target._submessages.splice(subIdx, 0, ...subItems);
|
|
1664
|
-
|
|
1665
|
-
const lastSec = anchor.secItems[anchor.secItems.length - 1];
|
|
1666
|
-
const secIdx = lastSec ? target._sections.indexOf(lastSec) + 1 : target._sections.length;
|
|
1667
|
-
target._sections.splice(secIdx, 0, ...secItems);
|
|
1668
|
-
} else {
|
|
1669
|
-
target._submessages.push(...subItems);
|
|
1670
|
-
target._sections.push(...secItems);
|
|
1671
|
-
}
|
|
1681
|
+
this._nodes = [];
|
|
1682
|
+
this._idIndex = new Map();
|
|
1672
1683
|
|
|
1673
|
-
|
|
1684
|
+
const maxLength = Math.max(loadedSections.length, loadedSubmessages.length);
|
|
1674
1685
|
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1686
|
+
for (let i = 0; i < maxLength; i++) {
|
|
1687
|
+
this._nodes.push({
|
|
1688
|
+
id: null,
|
|
1689
|
+
section: loadedSections[i] ?? null,
|
|
1690
|
+
submessage: loadedSubmessages[i] ?? null,
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
this._extraPayload = {};
|
|
1695
|
+
|
|
1696
|
+
for (const [key, value] of Object.entries(message)) {
|
|
1697
|
+
if (key !== 'messageContextInfo' && key !== 'botForwardedMessage' && key !== 'richResponseMessage') {
|
|
1698
|
+
this._extraPayload[key] = structuredClone(value);
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
return this;
|
|
1679
1703
|
}
|
|
1680
1704
|
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
});
|
|
1705
|
+
setResponseId(id) {
|
|
1706
|
+
if (typeof id !== 'string') {
|
|
1707
|
+
throw new TypeError('ID must be a string');
|
|
1708
|
+
}
|
|
1709
|
+
this._responseId = id;
|
|
1710
|
+
|
|
1711
|
+
return this;
|
|
1689
1712
|
}
|
|
1690
1713
|
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
const items = Array.isArray(submessage) ? submessage : [submessage];
|
|
1714
|
+
refreshResponseId() {
|
|
1715
|
+
this._responseId = crypto.randomUUID();
|
|
1694
1716
|
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
throw new TypeError('Submessage must be a plain object or array of plain objects');
|
|
1698
|
-
}
|
|
1717
|
+
return this;
|
|
1718
|
+
}
|
|
1699
1719
|
|
|
1700
|
-
|
|
1720
|
+
setBotResponseId(id) {
|
|
1721
|
+
if (typeof id !== 'string') {
|
|
1722
|
+
throw new TypeError('ID must be a string');
|
|
1701
1723
|
}
|
|
1724
|
+
this._botResponseId = id;
|
|
1702
1725
|
|
|
1703
1726
|
return this;
|
|
1704
1727
|
}
|
|
1705
1728
|
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
const items = Array.isArray(section) ? section : [section];
|
|
1729
|
+
refreshBotResponseId() {
|
|
1730
|
+
this._botResponseId = crypto.randomUUID();
|
|
1709
1731
|
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
throw new TypeError('Section must be a plain object or array of plain objects');
|
|
1713
|
-
}
|
|
1732
|
+
return this;
|
|
1733
|
+
}
|
|
1714
1734
|
|
|
1715
|
-
|
|
1735
|
+
createAlert(type) {
|
|
1736
|
+
if (this._unsupportedTypeAlert) {
|
|
1737
|
+
return {
|
|
1738
|
+
messageType: 2,
|
|
1739
|
+
messageText: `[ UNSUPPORTED_TYPE - ${type}]`,
|
|
1740
|
+
};
|
|
1716
1741
|
}
|
|
1717
1742
|
|
|
1718
|
-
return
|
|
1743
|
+
return undefined;
|
|
1719
1744
|
}
|
|
1720
1745
|
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
if (typeof text != 'string') {
|
|
1746
|
+
addText(text, { hyperlink = true, citation = true, latex = true, id, replace, insertAt } = {}) {
|
|
1747
|
+
if (typeof text !== 'string') {
|
|
1724
1748
|
throw new TypeError('Text must be a string');
|
|
1725
1749
|
}
|
|
1726
1750
|
|
|
@@ -1730,112 +1754,180 @@ class AIRich extends BaseBuilder {
|
|
|
1730
1754
|
latex,
|
|
1731
1755
|
});
|
|
1732
1756
|
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1757
|
+
const section = AIRich.newLayout('Single', {
|
|
1758
|
+
text: extractedText,
|
|
1759
|
+
...(inline_entities.length && { inline_entities }),
|
|
1760
|
+
__typename: 'GenAIMarkdownTextUXPrimitive',
|
|
1736
1761
|
});
|
|
1737
1762
|
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
__typename: 'GenAIMarkdownTextUXPrimitive',
|
|
1745
|
-
})
|
|
1746
|
-
);
|
|
1763
|
+
const submessages = [
|
|
1764
|
+
{
|
|
1765
|
+
messageType: 2,
|
|
1766
|
+
messageText: text,
|
|
1767
|
+
},
|
|
1768
|
+
].filter(Boolean);
|
|
1747
1769
|
|
|
1748
|
-
return this
|
|
1770
|
+
return this._addContent(section, submessages, {
|
|
1771
|
+
id,
|
|
1772
|
+
replace,
|
|
1773
|
+
insertAt,
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
addFOAText(text, { id, replace, insertAt } = {}) {
|
|
1778
|
+
if (typeof text !== 'string') {
|
|
1779
|
+
throw new TypeError('Text must be a string');
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
const section = AIRich.newLayout('Single', {
|
|
1783
|
+
text,
|
|
1784
|
+
__typename: 'FOATextPrimitive',
|
|
1785
|
+
});
|
|
1786
|
+
|
|
1787
|
+
const submessages = [
|
|
1788
|
+
{
|
|
1789
|
+
messageType: 2,
|
|
1790
|
+
messageText: text,
|
|
1791
|
+
},
|
|
1792
|
+
];
|
|
1793
|
+
|
|
1794
|
+
return this._addContent(section, submessages, {
|
|
1795
|
+
id,
|
|
1796
|
+
replace,
|
|
1797
|
+
insertAt,
|
|
1798
|
+
});
|
|
1749
1799
|
}
|
|
1750
1800
|
|
|
1751
|
-
|
|
1752
|
-
addCode(language, code) {
|
|
1801
|
+
addCode(language, code, { id, replace, insertAt } = {}) {
|
|
1753
1802
|
if (typeof language !== 'string' || typeof code !== 'string') {
|
|
1754
1803
|
throw new TypeError('Language and code must be a string');
|
|
1755
1804
|
}
|
|
1756
1805
|
|
|
1757
1806
|
const meta = AIRich.tokenizer(code, language);
|
|
1758
1807
|
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
codeBlocks: meta.codeBlock,
|
|
1764
|
-
},
|
|
1808
|
+
const section = AIRich.newLayout('Single', {
|
|
1809
|
+
language,
|
|
1810
|
+
code_blocks: meta.unified_codeBlock,
|
|
1811
|
+
__typename: 'GenAICodeUXPrimitive',
|
|
1765
1812
|
});
|
|
1766
1813
|
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1814
|
+
const submessages = [
|
|
1815
|
+
{
|
|
1816
|
+
messageType: 5,
|
|
1817
|
+
codeMetadata: {
|
|
1818
|
+
codeLanguage: language,
|
|
1819
|
+
codeBlocks: meta.codeBlock,
|
|
1820
|
+
},
|
|
1821
|
+
},
|
|
1822
|
+
];
|
|
1774
1823
|
|
|
1775
|
-
return this
|
|
1824
|
+
return this._addContent(section, submessages, {
|
|
1825
|
+
id,
|
|
1826
|
+
replace,
|
|
1827
|
+
insertAt,
|
|
1828
|
+
});
|
|
1776
1829
|
}
|
|
1777
1830
|
|
|
1778
|
-
|
|
1779
|
-
addTable(table, { hyperlink = true, citation = true, latex = true } = {}) {
|
|
1831
|
+
addTable(table, { hyperlink = true, citation = true, latex = true, id, replace, insertAt } = {}) {
|
|
1780
1832
|
if (!Array.isArray(table)) {
|
|
1781
1833
|
throw new TypeError('Table must be an array');
|
|
1782
1834
|
}
|
|
1783
1835
|
|
|
1784
|
-
const meta = AIRich.toTableMetadata(table, {
|
|
1836
|
+
const meta = AIRich.toTableMetadata(table, {
|
|
1837
|
+
hyperlink,
|
|
1838
|
+
citation,
|
|
1839
|
+
latex,
|
|
1840
|
+
});
|
|
1841
|
+
|
|
1842
|
+
const section = AIRich.newLayout('Single', {
|
|
1843
|
+
rows: meta.unified_rows,
|
|
1844
|
+
__typename: 'GenATableUXPrimitive',
|
|
1845
|
+
});
|
|
1785
1846
|
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1847
|
+
const submessages = [
|
|
1848
|
+
{
|
|
1849
|
+
messageType: 4,
|
|
1850
|
+
tableMetadata: {
|
|
1851
|
+
title: meta.title,
|
|
1852
|
+
rows: meta.rows,
|
|
1853
|
+
},
|
|
1791
1854
|
},
|
|
1855
|
+
];
|
|
1856
|
+
|
|
1857
|
+
return this._addContent(section, submessages, {
|
|
1858
|
+
id,
|
|
1859
|
+
replace,
|
|
1860
|
+
insertAt,
|
|
1792
1861
|
});
|
|
1862
|
+
}
|
|
1793
1863
|
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
})
|
|
1799
|
-
);
|
|
1864
|
+
addSource(sources = [], { id, replace, insertAt } = {}) {
|
|
1865
|
+
if (!Array.isArray(sources)) {
|
|
1866
|
+
throw new TypeError('Sources must be an array of strings, arrays, or objects');
|
|
1867
|
+
}
|
|
1800
1868
|
|
|
1801
|
-
|
|
1802
|
-
|
|
1869
|
+
const isStringArray = sources.every((item) => typeof item === 'string');
|
|
1870
|
+
|
|
1871
|
+
const isArrayFormat = sources.every((item) => Array.isArray(item) && item.every((value) => typeof value === 'string'));
|
|
1803
1872
|
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
if (!
|
|
1807
|
-
throw new TypeError('Sources must be a string array
|
|
1873
|
+
const isObjectFormat = sources.every((item) => item && typeof item === 'object' && !Array.isArray(item));
|
|
1874
|
+
|
|
1875
|
+
if (!isStringArray && !isArrayFormat && !isObjectFormat) {
|
|
1876
|
+
throw new TypeError('Sources must be a string array, array of string arrays, or array of objects');
|
|
1808
1877
|
}
|
|
1809
1878
|
|
|
1810
|
-
if (
|
|
1879
|
+
if (isStringArray) {
|
|
1811
1880
|
sources = [sources];
|
|
1812
1881
|
}
|
|
1813
1882
|
|
|
1814
|
-
const
|
|
1883
|
+
const normalizedSources = sources.map((source) => {
|
|
1884
|
+
if (Array.isArray(source)) {
|
|
1885
|
+
const [icon, url, title, subtitle] = source;
|
|
1886
|
+
|
|
1887
|
+
return {
|
|
1888
|
+
icon,
|
|
1889
|
+
url,
|
|
1890
|
+
title,
|
|
1891
|
+
subtitle,
|
|
1892
|
+
};
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
return {
|
|
1896
|
+
icon: source.favicon ?? source.icon ?? '',
|
|
1897
|
+
url: source.url ?? '',
|
|
1898
|
+
title: source.title ?? '',
|
|
1899
|
+
subtitle: source.subtitle ?? '',
|
|
1900
|
+
};
|
|
1901
|
+
});
|
|
1902
|
+
|
|
1903
|
+
const source = normalizedSources.map(({ icon, url, title, subtitle }) => ({
|
|
1815
1904
|
source_type: 'THIRD_PARTY',
|
|
1816
|
-
source_display_name:
|
|
1817
|
-
source_subtitle:
|
|
1818
|
-
source_url: url
|
|
1905
|
+
source_display_name: title,
|
|
1906
|
+
source_subtitle: subtitle,
|
|
1907
|
+
source_url: url,
|
|
1819
1908
|
favicon: {
|
|
1820
|
-
url: Toolkit.resolveMedia(this.#client, icon
|
|
1909
|
+
url: Toolkit.resolveMedia(this.#client, icon, 'image'),
|
|
1821
1910
|
mime_type: 'image/jpeg',
|
|
1822
1911
|
width: 16,
|
|
1823
1912
|
height: 16,
|
|
1824
1913
|
},
|
|
1825
1914
|
}));
|
|
1826
1915
|
|
|
1827
|
-
this.
|
|
1828
|
-
AIRich.newLayout('Single', {
|
|
1829
|
-
sources: source,
|
|
1830
|
-
__typename: 'GenAISearchResultPrimitive',
|
|
1831
|
-
})
|
|
1832
|
-
);
|
|
1916
|
+
const submessage = this.createAlert('GenAISearchResultPrimitive');
|
|
1833
1917
|
|
|
1834
|
-
|
|
1918
|
+
const section = AIRich.newLayout('Single', {
|
|
1919
|
+
sources: source,
|
|
1920
|
+
__typename: 'GenAISearchResultPrimitive',
|
|
1921
|
+
});
|
|
1922
|
+
|
|
1923
|
+
return this._addContent(section, submessage, {
|
|
1924
|
+
id,
|
|
1925
|
+
replace,
|
|
1926
|
+
insertAt,
|
|
1927
|
+
});
|
|
1835
1928
|
}
|
|
1836
1929
|
|
|
1837
|
-
|
|
1838
|
-
addReels(reelsItems = [], { resolveUrl = false } = {}) {
|
|
1930
|
+
addReels(reelsItems = [], { id, replace, insertAt } = {}) {
|
|
1839
1931
|
if (
|
|
1840
1932
|
!(
|
|
1841
1933
|
(reelsItems && typeof reelsItems === 'object' && !Array.isArray(reelsItems)) ||
|
|
@@ -1845,88 +1937,64 @@ class AIRich extends BaseBuilder {
|
|
|
1845
1937
|
throw new TypeError('Reels items must be an object or an array of objects');
|
|
1846
1938
|
}
|
|
1847
1939
|
|
|
1848
|
-
|
|
1849
|
-
reelsItems = [reelsItems];
|
|
1850
|
-
}
|
|
1940
|
+
const items = Array.isArray(reelsItems) ? reelsItems : [reelsItems];
|
|
1851
1941
|
|
|
1852
|
-
const reels =
|
|
1942
|
+
const reels = items.map((item) => ({
|
|
1853
1943
|
...item,
|
|
1854
|
-
_avatar: Toolkit.resolveMedia(this.#client, item.profileIconUrl ?? item.profile_url ?? item.profile ?? '', 'image'
|
|
1855
|
-
_thumbnail: Toolkit.resolveMedia(this.#client, item.thumbnailUrl ?? item.thumbnail ?? '', 'image'
|
|
1944
|
+
_avatar: Toolkit.resolveMedia(this.#client, item.profileIconUrl ?? item.profile_url ?? item.profile ?? '', 'image'),
|
|
1945
|
+
_thumbnail: Toolkit.resolveMedia(this.#client, item.thumbnailUrl ?? item.thumbnail ?? '', 'image'),
|
|
1856
1946
|
}));
|
|
1857
1947
|
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1948
|
+
const section = AIRich.newLayout(
|
|
1949
|
+
'HScroll',
|
|
1950
|
+
reels.map((item) => ({
|
|
1951
|
+
reels_url: item.videoUrl ?? item.url ?? '',
|
|
1952
|
+
thumbnail_url: item._thumbnail,
|
|
1953
|
+
creator: item.username ?? item.title ?? '',
|
|
1954
|
+
avatar_url: item._avatar,
|
|
1955
|
+
reels_title: item.reels_title ?? item.title ?? '',
|
|
1956
|
+
likes_count: item.likes_count ?? item.like ?? 0,
|
|
1957
|
+
shares_count: item.shares_count ?? item.share ?? 0,
|
|
1958
|
+
view_count: item.view_count ?? item.view ?? 0,
|
|
1959
|
+
reel_source: item.reel_source ?? item.source ?? 'IG',
|
|
1960
|
+
is_verified: !!(item.is_verified || item.verified),
|
|
1961
|
+
__typename: 'GenAIReelPrimitive',
|
|
1962
|
+
}))
|
|
1963
|
+
);
|
|
1964
|
+
|
|
1965
|
+
const submessages = [
|
|
1966
|
+
{
|
|
1967
|
+
messageType: 9,
|
|
1968
|
+
contentItemsMetadata: {
|
|
1969
|
+
contentType: 1,
|
|
1970
|
+
itemsMetadata: reels.map((item) => ({
|
|
1971
|
+
reelItem: {
|
|
1972
|
+
title: item.username ?? '',
|
|
1973
|
+
profileIconUrl: item._avatar,
|
|
1974
|
+
thumbnailUrl: item._thumbnail,
|
|
1975
|
+
videoUrl: item.videoUrl ?? item.url ?? '',
|
|
1976
|
+
},
|
|
1977
|
+
})),
|
|
1978
|
+
},
|
|
1870
1979
|
},
|
|
1871
|
-
|
|
1980
|
+
];
|
|
1872
1981
|
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
sourceProviderURL: item.videoUrl ?? item.url ?? '',
|
|
1878
|
-
sourceQuery: '',
|
|
1879
|
-
faviconCDNURL: item._avatar,
|
|
1880
|
-
citationNumber: idx + 1,
|
|
1881
|
-
sourceTitle: item.username ?? '',
|
|
1882
|
-
});
|
|
1982
|
+
return this._addContent(section, submessages, {
|
|
1983
|
+
id,
|
|
1984
|
+
replace,
|
|
1985
|
+
insertAt,
|
|
1883
1986
|
});
|
|
1884
|
-
|
|
1885
|
-
this._sections.push(
|
|
1886
|
-
AIRich.newLayout(
|
|
1887
|
-
'HScroll',
|
|
1888
|
-
reels.map((item) => ({
|
|
1889
|
-
reels_url: item.videoUrl ?? item.url ?? '',
|
|
1890
|
-
thumbnail_url: item._thumbnail,
|
|
1891
|
-
creator: item.username ?? item.title ?? '',
|
|
1892
|
-
avatar_url: item._avatar,
|
|
1893
|
-
reels_title: item.reels_title ?? item.title ?? '',
|
|
1894
|
-
likes_count: item.likes_count ?? item.like ?? 0,
|
|
1895
|
-
shares_count: item.shares_count ?? item.share ?? 0,
|
|
1896
|
-
view_count: item.view_count ?? item.view ?? 0,
|
|
1897
|
-
reel_source: item.reel_source ?? item.source ?? 'IG',
|
|
1898
|
-
is_verified: !!(item.is_verified || item.verified),
|
|
1899
|
-
__typename: 'GenAIReelPrimitive',
|
|
1900
|
-
}))
|
|
1901
|
-
)
|
|
1902
|
-
);
|
|
1903
|
-
|
|
1904
|
-
return this;
|
|
1905
1987
|
}
|
|
1906
1988
|
|
|
1907
|
-
|
|
1908
|
-
/**
|
|
1909
|
-
* @param {{ resolveUrl?: boolean, instant?: boolean|'only' }} [options]
|
|
1910
|
-
* `instant: true` — sends BOTH: the GRID_IMAGE card (still shows WA's "can't verify"
|
|
1911
|
-
* forwarded-download prompt, unavoidable per-design of botForwardedMessage) AND a plain
|
|
1912
|
-
* (non-forwarded) imageMessage via send()'s inline-image fallback queue (`_inlineImages`,
|
|
1913
|
-
* shared with addInlineImage()) that renders instantly with no prompt. Two images, by design.
|
|
1914
|
-
* `instant: 'only'` — Vanz@Add (v4.9.2): skips building the GRID_IMAGE card entirely (no
|
|
1915
|
-
* submessage, no GenAIImaginePrimitive section) and queues ONLY the plain imageMessage.
|
|
1916
|
-
* One image, no prompt, nothing to download — use this when you don't need the rich card,
|
|
1917
|
-
* just the picture to show up immediately.
|
|
1918
|
-
*/
|
|
1919
|
-
addImage(imageUrl, { resolveUrl = false, instant = false } = {}) {
|
|
1989
|
+
addImage(imageUrl, { width, height, status = 'READY', update_text, resolveUrl = false, id, replace, insertAt } = {}) {
|
|
1920
1990
|
if (!(typeof imageUrl === 'string' || Buffer.isBuffer(imageUrl) || (Array.isArray(imageUrl) && imageUrl.every((v) => typeof v === 'string' || Buffer.isBuffer(v))))) {
|
|
1921
1991
|
throw new TypeError('imageUrl must be string | buffer | array of string/buffer');
|
|
1922
1992
|
}
|
|
1923
|
-
if (instant !== false && instant !== true && instant !== 'only') {
|
|
1924
|
-
throw new TypeError(`instant must be false, true, or 'only' — got ${JSON.stringify(instant)}`);
|
|
1925
|
-
}
|
|
1926
1993
|
|
|
1927
1994
|
const list = Array.isArray(imageUrl)
|
|
1928
1995
|
? imageUrl.map((v) => {
|
|
1929
1996
|
const url = Toolkit.resolveMedia(this.#client, v, 'image', { resolveUrl });
|
|
1997
|
+
|
|
1930
1998
|
return {
|
|
1931
1999
|
imagePreviewUrl: url,
|
|
1932
2000
|
imageHighResUrl: url,
|
|
@@ -1935,6 +2003,7 @@ class AIRich extends BaseBuilder {
|
|
|
1935
2003
|
})
|
|
1936
2004
|
: (() => {
|
|
1937
2005
|
const url = Toolkit.resolveMedia(this.#client, imageUrl, 'image', { resolveUrl });
|
|
2006
|
+
|
|
1938
2007
|
return [
|
|
1939
2008
|
{
|
|
1940
2009
|
imagePreviewUrl: url,
|
|
@@ -1944,115 +2013,46 @@ class AIRich extends BaseBuilder {
|
|
|
1944
2013
|
];
|
|
1945
2014
|
})();
|
|
1946
2015
|
|
|
1947
|
-
const
|
|
1948
|
-
|
|
1949
|
-
if (buildCard) {
|
|
1950
|
-
this._submessages.push({
|
|
1951
|
-
messageType: 1,
|
|
1952
|
-
gridImageMetadata: {
|
|
1953
|
-
gridImageUrl: {
|
|
1954
|
-
imagePreviewUrl: list[0]?.imagePreviewUrl,
|
|
1955
|
-
},
|
|
1956
|
-
imageUrls: list,
|
|
1957
|
-
},
|
|
1958
|
-
});
|
|
1959
|
-
}
|
|
1960
|
-
|
|
1961
|
-
list.forEach(({ imagePreviewUrl }) => {
|
|
1962
|
-
if (buildCard) {
|
|
1963
|
-
this._sections.push(
|
|
1964
|
-
AIRich.newLayout('Single', {
|
|
1965
|
-
media: {
|
|
1966
|
-
url: imagePreviewUrl,
|
|
1967
|
-
mime_type: 'image/png',
|
|
1968
|
-
},
|
|
1969
|
-
imagine_type: 'IMAGE',
|
|
1970
|
-
status: { status: 'READY' },
|
|
1971
|
-
__typename: 'GenAIImaginePrimitive',
|
|
1972
|
-
})
|
|
1973
|
-
);
|
|
1974
|
-
}
|
|
1975
|
-
|
|
1976
|
-
if (instant) {
|
|
1977
|
-
this._inlineImages.push({ url: imagePreviewUrl, caption: undefined });
|
|
1978
|
-
}
|
|
1979
|
-
});
|
|
1980
|
-
|
|
1981
|
-
return this;
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
// Vanz@Fix 15-08-26 (bug 41) --- addImage() only builds GRID_IMAGE (messageType 1).
|
|
1985
|
-
// There was no helper for standalone INLINE_IMAGE (messageType 3): callers were manually
|
|
1986
|
-
// pushing addSubmessage() (correct proto shape) + addSection() (WRONG shape — reused the
|
|
1987
|
-
// GRID_IMAGE/GenAIImaginePrimitive section schema instead of GenAIInlineImageUXPrimitive),
|
|
1988
|
-
// which broke client-side unifiedResponse rendering even though the submessage itself was fine.
|
|
1989
|
-
// Mirrors RichSubMessageType.INLINE_IMAGE handling in rich-message-utils.js's toUnified().
|
|
1990
|
-
/** 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). */
|
|
1991
|
-
addInlineImage(imageUrl, { text = '', alignment = 'center', tapLinkUrl = '', resolveUrl = false } = {}) {
|
|
1992
|
-
if (!(typeof imageUrl === 'string' || Buffer.isBuffer(imageUrl) || (imageUrl && typeof imageUrl === 'object'))) {
|
|
1993
|
-
throw new TypeError('imageUrl must be string | buffer | { imagePreviewUrl, imageHighResUrl, sourceUrl }');
|
|
1994
|
-
}
|
|
1995
|
-
|
|
1996
|
-
const ALIGNMENT_ENUM = { leading: 0, trailing: 1, center: 2 };
|
|
1997
|
-
const ALIGNMENT_NAME = ['AI_RICH_RESPONSE_IMAGE_LAYOUT_LEADING_ALIGNED', 'AI_RICH_RESPONSE_IMAGE_LAYOUT_TRAILING_ALIGNED', 'AI_RICH_RESPONSE_IMAGE_LAYOUT_CENTER_ALIGNED'];
|
|
1998
|
-
const alignmentNum = typeof alignment === 'number' ? alignment : (ALIGNMENT_ENUM[String(alignment).toLowerCase()] ?? ALIGNMENT_ENUM.center);
|
|
1999
|
-
|
|
2000
|
-
const url =
|
|
2001
|
-
imageUrl && typeof imageUrl === 'object'
|
|
2002
|
-
? {
|
|
2003
|
-
imagePreviewUrl: imageUrl.imagePreviewUrl || imageUrl.url,
|
|
2004
|
-
imageHighResUrl: imageUrl.imageHighResUrl || imageUrl.url,
|
|
2005
|
-
sourceUrl: imageUrl.sourceUrl || imageUrl.url,
|
|
2006
|
-
}
|
|
2007
|
-
: (() => {
|
|
2008
|
-
const resolved = Toolkit.resolveMedia(this.#client, imageUrl, 'image', { resolveUrl });
|
|
2009
|
-
return { imagePreviewUrl: resolved, imageHighResUrl: resolved, sourceUrl: resolved };
|
|
2010
|
-
})();
|
|
2011
|
-
|
|
2012
|
-
this._submessages.push({
|
|
2013
|
-
messageType: 3,
|
|
2014
|
-
imageMetadata: {
|
|
2015
|
-
imageUrl: url,
|
|
2016
|
-
imageText: text,
|
|
2017
|
-
alignment: alignmentNum,
|
|
2018
|
-
tapLinkUrl,
|
|
2019
|
-
},
|
|
2020
|
-
});
|
|
2021
|
-
|
|
2022
|
-
this._sections.push(
|
|
2016
|
+
const sections = list.map(({ imagePreviewUrl }) =>
|
|
2023
2017
|
AIRich.newLayout('Single', {
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2018
|
+
media: {
|
|
2019
|
+
url: imagePreviewUrl,
|
|
2020
|
+
mime_type: 'image/png',
|
|
2021
|
+
width,
|
|
2022
|
+
height,
|
|
2028
2023
|
},
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2024
|
+
imagine_type: 'IMAGE',
|
|
2025
|
+
status: {
|
|
2026
|
+
status,
|
|
2027
|
+
update_text,
|
|
2028
|
+
},
|
|
2029
|
+
__typename: 'GenAIImaginePrimitive',
|
|
2033
2030
|
})
|
|
2034
2031
|
);
|
|
2035
2032
|
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2033
|
+
const submessage = {
|
|
2034
|
+
messageType: 1,
|
|
2035
|
+
gridImageMetadata: {
|
|
2036
|
+
gridImageUrl: {
|
|
2037
|
+
imagePreviewUrl: list[0]?.imagePreviewUrl,
|
|
2038
|
+
},
|
|
2039
|
+
imageUrls: list,
|
|
2040
|
+
},
|
|
2041
|
+
};
|
|
2041
2042
|
|
|
2042
|
-
|
|
2043
|
+
if (id && sections.length !== 1) {
|
|
2044
|
+
throw new Error('Cannot assign one id to multiple image sections');
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
return this._addContent(sections, submessage, {
|
|
2048
|
+
id,
|
|
2049
|
+
replace,
|
|
2050
|
+
insertAt,
|
|
2051
|
+
});
|
|
2043
2052
|
}
|
|
2044
2053
|
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
// was the main source of blurose's slower response time. Pass { autoFill: true } to opt
|
|
2048
|
-
// back into the complete/slow path (real thumbnail + duration + file_length).
|
|
2049
|
-
// Vanz@Fix 23-08-26 --- addVideo() had no resolveUrl option at all (unlike addImage()), so the
|
|
2050
|
-
// video url always stayed a raw external link, which stock WA clients show a "download" state
|
|
2051
|
-
// for before rendering. Mirrors addImage()'s { resolveUrl } — when true, the url is uploaded to
|
|
2052
|
-
// WA's own media server first via Toolkit.toUrl() so it renders instantly like WA-native media.
|
|
2053
|
-
/** Add a video block. */
|
|
2054
|
-
addVideo(videoUrl, { autoFill = false, resolveUrl = false } = {}) {
|
|
2055
|
-
const isObjectVideo = (v) => v && typeof v === 'object' && v.url;
|
|
2054
|
+
addVideo(videoUrl, { autoFill = true, status = 'READY', estimatedTime, id, replace, insertAt } = {}) {
|
|
2055
|
+
const isObjectVideo = (v) => v && typeof v === 'object' && !Array.isArray(v) && v.url;
|
|
2056
2056
|
|
|
2057
2057
|
const isValidPrimitive =
|
|
2058
2058
|
typeof videoUrl === 'string' ||
|
|
@@ -2066,17 +2066,15 @@ class AIRich extends BaseBuilder {
|
|
|
2066
2066
|
|
|
2067
2067
|
const items = Array.isArray(videoUrl) ? videoUrl : [videoUrl];
|
|
2068
2068
|
|
|
2069
|
-
this.
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2069
|
+
const alert = this.createAlert('GenAIImaginePrimitive (ANIMATE)');
|
|
2070
|
+
|
|
2071
|
+
const sections = [];
|
|
2072
|
+
const submessages = [];
|
|
2073
2073
|
|
|
2074
|
-
|
|
2074
|
+
for (const item of items) {
|
|
2075
2075
|
const isObject = isObjectVideo(item);
|
|
2076
2076
|
|
|
2077
|
-
const url = isObject
|
|
2078
|
-
? Toolkit.resolveMedia(this.#client, item.url ?? '', 'video', { resolveUrl })
|
|
2079
|
-
: Toolkit.resolveMedia(this.#client, item, 'video', { resolveUrl });
|
|
2077
|
+
const url = isObject ? Toolkit.resolveMedia(this.#client, item.url ?? '', 'video') : Toolkit.resolveMedia(this.#client, item, 'video');
|
|
2080
2078
|
|
|
2081
2079
|
const bufferPromise = autoFill ? Promise.resolve(url).then((u) => Toolkit.fetchBuffer(u)) : null;
|
|
2082
2080
|
|
|
@@ -2102,17 +2100,15 @@ class AIRich extends BaseBuilder {
|
|
|
2102
2100
|
height: 300,
|
|
2103
2101
|
})
|
|
2104
2102
|
: autoFill
|
|
2105
|
-
? bufferPromise
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
)
|
|
2112
|
-
: null
|
|
2103
|
+
? bufferPromise?.then((b) =>
|
|
2104
|
+
Toolkit.getMp4Preview(b, {
|
|
2105
|
+
time: 0,
|
|
2106
|
+
result: 'base64',
|
|
2107
|
+
})
|
|
2108
|
+
)
|
|
2113
2109
|
: null;
|
|
2114
2110
|
|
|
2115
|
-
|
|
2111
|
+
sections.push(
|
|
2116
2112
|
AIRich.newLayout('Single', {
|
|
2117
2113
|
media: {
|
|
2118
2114
|
url,
|
|
@@ -2121,34 +2117,37 @@ class AIRich extends BaseBuilder {
|
|
|
2121
2117
|
duration,
|
|
2122
2118
|
},
|
|
2123
2119
|
imagine_type: 'ANIMATE',
|
|
2124
|
-
status: {
|
|
2120
|
+
status: {
|
|
2121
|
+
status,
|
|
2122
|
+
estimated_completion_time: estimatedTime != null ? Math.floor((Date.now() + estimatedTime) / 1000) : undefined,
|
|
2123
|
+
},
|
|
2125
2124
|
thumbnail: {
|
|
2126
2125
|
raw_media: thumbnail,
|
|
2127
2126
|
},
|
|
2128
2127
|
__typename: 'GenAIImaginePrimitive',
|
|
2129
2128
|
})
|
|
2130
2129
|
);
|
|
2131
|
-
}
|
|
2132
|
-
|
|
2133
|
-
return this;
|
|
2134
|
-
}
|
|
2130
|
+
}
|
|
2135
2131
|
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
2139
|
-
throw new TypeError('Product items must be an object or an array of objects');
|
|
2132
|
+
if (alert !== undefined) {
|
|
2133
|
+
submessages.push(alert);
|
|
2140
2134
|
}
|
|
2141
2135
|
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
if (missingTitleAt !== -1) {
|
|
2145
|
-
throw new TypeError(`addProduct() item[${missingTitleAt}] is missing a required "title"`);
|
|
2136
|
+
if (submessages.length > 1) {
|
|
2137
|
+
throw new Error('Video content can only have one submessage');
|
|
2146
2138
|
}
|
|
2147
2139
|
|
|
2148
|
-
this.
|
|
2149
|
-
|
|
2150
|
-
|
|
2140
|
+
return this._addContent(sections, submessages[0], {
|
|
2141
|
+
id,
|
|
2142
|
+
replace,
|
|
2143
|
+
insertAt,
|
|
2151
2144
|
});
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
addProduct(data = {}, { id, replace, insertAt } = {}) {
|
|
2148
|
+
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
2149
|
+
throw new TypeError('Product items must be an object or an array of objects');
|
|
2150
|
+
}
|
|
2152
2151
|
|
|
2153
2152
|
const items = Array.isArray(data) ? data : [data];
|
|
2154
2153
|
|
|
@@ -2159,41 +2158,41 @@ class AIRich extends BaseBuilder {
|
|
|
2159
2158
|
sale_price: item.sale_price,
|
|
2160
2159
|
product_url: item.product_url ?? item.url,
|
|
2161
2160
|
image: {
|
|
2162
|
-
url: Toolkit.resolveMedia(this.#client, item.image_url ?? item.image, 'image'
|
|
2161
|
+
url: Toolkit.resolveMedia(this.#client, item.image_url ?? item.image, 'image'),
|
|
2163
2162
|
},
|
|
2164
2163
|
additional_images: [
|
|
2165
2164
|
{
|
|
2166
|
-
url: Toolkit.resolveMedia(this.#client, item.icon_url ?? item.icon, 'image'
|
|
2165
|
+
url: Toolkit.resolveMedia(this.#client, item.icon_url ?? item.icon, 'image'),
|
|
2167
2166
|
},
|
|
2168
2167
|
],
|
|
2169
2168
|
__typename: 'GenAIProductItemCardPrimitive',
|
|
2170
2169
|
}));
|
|
2171
2170
|
|
|
2172
|
-
|
|
2171
|
+
const section = AIRich.newLayout(Array.isArray(data) ? 'HScroll' : 'Single', Array.isArray(data) ? product : product[0]);
|
|
2173
2172
|
|
|
2174
|
-
|
|
2173
|
+
const submessage = this.createAlert('GenAIProductItemCardPrimitive');
|
|
2174
|
+
|
|
2175
|
+
return this._addContent(section, submessage, {
|
|
2176
|
+
id,
|
|
2177
|
+
replace,
|
|
2178
|
+
insertAt,
|
|
2179
|
+
});
|
|
2175
2180
|
}
|
|
2176
2181
|
|
|
2177
|
-
|
|
2178
|
-
addPost(data = {}, { resolveUrl = false } = {}) {
|
|
2182
|
+
addPost(data = {}, { id, replace, insertAt } = {}) {
|
|
2179
2183
|
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
2180
2184
|
throw new TypeError('Post items must be an object or an array of objects');
|
|
2181
2185
|
}
|
|
2182
2186
|
|
|
2183
2187
|
const posts = Array.isArray(data) ? data : [data];
|
|
2184
2188
|
|
|
2185
|
-
this._submessages.push({
|
|
2186
|
-
messageType: 2,
|
|
2187
|
-
messageText: '[ Postingan tidak dapat dimuat ]',
|
|
2188
|
-
});
|
|
2189
|
-
|
|
2190
2189
|
const primitives = posts.map((p) => ({
|
|
2191
2190
|
title: p.title ?? '',
|
|
2192
2191
|
subtitle: p.subtitle ?? '',
|
|
2193
2192
|
username: p.username ?? '',
|
|
2194
|
-
profile_picture_url: Toolkit.resolveMedia(this.#client, p.profile_picture_url ?? p.profile_url ?? p.profile ?? '', 'image'
|
|
2193
|
+
profile_picture_url: Toolkit.resolveMedia(this.#client, p.profile_picture_url ?? p.profile_url ?? p.profile ?? '', 'image'),
|
|
2195
2194
|
is_verified: !!(p.is_verified || p.verified),
|
|
2196
|
-
thumbnail_url: Toolkit.resolveMedia(this.#client, p.thumbnail_url ?? p.thumbnail ?? '', 'image'
|
|
2195
|
+
thumbnail_url: Toolkit.resolveMedia(this.#client, p.thumbnail_url ?? p.thumbnail ?? '', 'image'),
|
|
2197
2196
|
post_caption: p.post_caption ?? p.caption ?? '',
|
|
2198
2197
|
likes_count: p.likes_count ?? p.like ?? 0,
|
|
2199
2198
|
comments_count: p.comments_count ?? p.comment ?? 0,
|
|
@@ -2202,419 +2201,151 @@ class AIRich extends BaseBuilder {
|
|
|
2202
2201
|
post_deeplink: p.post_deeplink ?? p.deeplink ?? '',
|
|
2203
2202
|
source_app: p.source_app || p.source || 'INSTAGRAM',
|
|
2204
2203
|
footer_label: p.footer_label ?? p.footer ?? '',
|
|
2205
|
-
footer_icon: Toolkit.resolveMedia(this.#client, p.footer_icon ?? p.icon ?? '', 'image'
|
|
2204
|
+
footer_icon: Toolkit.resolveMedia(this.#client, p.footer_icon ?? p.icon ?? '', 'image'),
|
|
2206
2205
|
is_carousel: posts.length > 1,
|
|
2207
2206
|
orientation: p.orientation ?? 'LANDSCAPE',
|
|
2208
2207
|
post_type: p.post_type ?? 'VIDEO',
|
|
2209
2208
|
__typename: 'GenAIPostPrimitive',
|
|
2210
2209
|
}));
|
|
2211
2210
|
|
|
2212
|
-
|
|
2211
|
+
const section = AIRich.newLayout('HScroll', primitives);
|
|
2213
2212
|
|
|
2214
|
-
|
|
2213
|
+
const submessage = this.createAlert('GenAIPostPrimitive');
|
|
2214
|
+
|
|
2215
|
+
return this._addContent(section, submessage, {
|
|
2216
|
+
id,
|
|
2217
|
+
replace,
|
|
2218
|
+
insertAt,
|
|
2219
|
+
});
|
|
2215
2220
|
}
|
|
2216
2221
|
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2222
|
+
addMetadata(text, { id, replace, insertAt } = {}) {
|
|
2223
|
+
if (typeof text !== 'string') {
|
|
2224
|
+
throw new TypeError('Text must be a string');
|
|
2225
|
+
}
|
|
2221
2226
|
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
return this;
|
|
2227
|
-
}
|
|
2227
|
+
const section = AIRich.newLayout('Single', {
|
|
2228
|
+
text,
|
|
2229
|
+
__typename: 'GenAIMetadataTextPrimitive',
|
|
2230
|
+
});
|
|
2228
2231
|
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
}
|
|
2232
|
+
const submessage = {
|
|
2233
|
+
messageType: 2,
|
|
2234
|
+
messageText: text,
|
|
2235
|
+
};
|
|
2234
2236
|
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2237
|
+
return this._addContent(section, submessage, {
|
|
2238
|
+
id,
|
|
2239
|
+
replace,
|
|
2240
|
+
insertAt,
|
|
2241
|
+
});
|
|
2240
2242
|
}
|
|
2241
2243
|
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
}
|
|
2244
|
+
addTip(text, { id, replace, insertAt } = {}) {
|
|
2245
|
+
if (typeof text !== 'string') {
|
|
2246
|
+
throw new TypeError('Text must be a string');
|
|
2247
|
+
}
|
|
2247
2248
|
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2249
|
+
const section = AIRich.newLayout('Single', {
|
|
2250
|
+
text: 'ⓘ ' + text,
|
|
2251
|
+
__typename: 'GenAIMetadataTextPrimitive',
|
|
2252
|
+
});
|
|
2251
2253
|
|
|
2252
|
-
|
|
2254
|
+
const submessage = {
|
|
2253
2255
|
messageType: 2,
|
|
2254
2256
|
messageText: text,
|
|
2255
|
-
}
|
|
2256
|
-
|
|
2257
|
-
this._sections.push(
|
|
2258
|
-
AIRich.newLayout('Single', {
|
|
2259
|
-
text,
|
|
2260
|
-
__typename: 'GenAIMetadataTextPrimitive',
|
|
2261
|
-
})
|
|
2262
|
-
);
|
|
2263
|
-
|
|
2264
|
-
return this;
|
|
2265
|
-
}
|
|
2266
|
-
|
|
2267
|
-
/** Add a small "tip" callout banner. @param {string} text */
|
|
2268
|
-
addTip(text) {
|
|
2269
|
-
if (typeof text !== 'string' || !text) {
|
|
2270
|
-
throw new TypeError('addTip(text) requires a non-empty string');
|
|
2271
|
-
}
|
|
2257
|
+
};
|
|
2272
2258
|
|
|
2273
|
-
this.
|
|
2274
|
-
|
|
2275
|
-
|
|
2259
|
+
return this._addContent(section, submessage, {
|
|
2260
|
+
id,
|
|
2261
|
+
replace,
|
|
2262
|
+
insertAt,
|
|
2276
2263
|
});
|
|
2277
|
-
|
|
2278
|
-
this._sections.push(
|
|
2279
|
-
AIRich.newLayout('Single', {
|
|
2280
|
-
text,
|
|
2281
|
-
__typename: 'GenAIMetadataTextPrimitive',
|
|
2282
|
-
})
|
|
2283
|
-
);
|
|
2284
|
-
|
|
2285
|
-
return this;
|
|
2286
2264
|
}
|
|
2287
2265
|
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
// unknown enum values (kind/state on addWidget's ctas) are passed through as observed rather
|
|
2292
|
-
// than guessed at, and documented as experimental below.
|
|
2293
|
-
|
|
2294
|
-
/** Add a large heading-style text block (`FOATextPrimitive`) — visually distinct from `addText()`'s regular paragraph text. */
|
|
2295
|
-
addHeading(text) {
|
|
2296
|
-
if (typeof text !== 'string' || !text) {
|
|
2297
|
-
throw new TypeError('addHeading(text) requires a non-empty string');
|
|
2266
|
+
addWidget(data, { layout, id, replace, insertAt, ...options } = {}) {
|
|
2267
|
+
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
2268
|
+
throw new TypeError('Widget must be an object or an array of objects');
|
|
2298
2269
|
}
|
|
2299
2270
|
|
|
2300
|
-
|
|
2301
|
-
messageType: 2,
|
|
2302
|
-
messageText: text,
|
|
2303
|
-
});
|
|
2304
|
-
|
|
2305
|
-
this._sections.push(
|
|
2306
|
-
AIRich.newLayout('Single', {
|
|
2307
|
-
text,
|
|
2308
|
-
__typename: 'FOATextPrimitive',
|
|
2309
|
-
})
|
|
2310
|
-
);
|
|
2311
|
-
|
|
2312
|
-
return this;
|
|
2313
|
-
}
|
|
2271
|
+
const isArray = Array.isArray(data);
|
|
2314
2272
|
|
|
2315
|
-
|
|
2316
|
-
* Add a "ready" static image card (`GenAIImagePrimitive`: preview + full-res, no generating/status
|
|
2317
|
-
* state) — distinct from `addImage()`'s AI-generation-style `GenAIImaginePrimitive`.
|
|
2318
|
-
* @param {string|Buffer} previewUrl Preview/thumbnail image.
|
|
2319
|
-
* @param {string|Buffer} [fullUrl] Full-resolution image; defaults to `previewUrl`.
|
|
2320
|
-
*/
|
|
2321
|
-
addImageCard(previewUrl, fullUrl = previewUrl, { resolveUrl = false } = {}) {
|
|
2322
|
-
if (!(typeof previewUrl === 'string' || Buffer.isBuffer(previewUrl))) {
|
|
2323
|
-
throw new TypeError('addImageCard(previewUrl) requires a string url or buffer');
|
|
2324
|
-
}
|
|
2273
|
+
const items = isArray ? data : [data];
|
|
2325
2274
|
|
|
2326
|
-
const
|
|
2327
|
-
|
|
2275
|
+
const widgets = items.map((item) => ({
|
|
2276
|
+
__typename: 'GenAI3PExtWidgetPrimitive',
|
|
2328
2277
|
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
imageUrls: [{ imagePreviewUrl: preview, imageHighResUrl: full, sourceUrl: full }],
|
|
2278
|
+
header: {
|
|
2279
|
+
__typename: 'GenAI3PExtWidgetStandardHeader',
|
|
2280
|
+
title: item.title ?? '',
|
|
2281
|
+
...(item.header ?? {}),
|
|
2334
2282
|
},
|
|
2335
|
-
});
|
|
2336
|
-
|
|
2337
|
-
this._sections.push(
|
|
2338
|
-
AIRich.newLayout('Single', {
|
|
2339
|
-
preview_image: { url: preview, mime_type: 'image/jpeg', __typename: 'GenAIMediaItem' },
|
|
2340
|
-
full_image: { url: full, mime_type: 'image/jpeg', __typename: 'GenAIMediaItem' },
|
|
2341
|
-
__typename: 'GenAIImagePrimitive',
|
|
2342
|
-
})
|
|
2343
|
-
);
|
|
2344
2283
|
|
|
2345
|
-
|
|
2346
|
-
|
|
2284
|
+
body: {
|
|
2285
|
+
__typename: 'GenAI3PExtCalendarEventList',
|
|
2286
|
+
sections: item.sections ?? [],
|
|
2287
|
+
|
|
2288
|
+
ctas: (item.actions ?? []).map((action) => ({
|
|
2289
|
+
__typename: 'GenAI3PExtWidgetCTA',
|
|
2290
|
+
label: action.label ?? '',
|
|
2291
|
+
state: action.state ?? 'PENDING',
|
|
2292
|
+
kind: action.kind ?? 'OTHER',
|
|
2293
|
+
tool_call_id: action.tool_call_id ?? action.id ?? '',
|
|
2294
|
+
|
|
2295
|
+
...(action.toast && {
|
|
2296
|
+
toast: {
|
|
2297
|
+
__typename: 'GenAI3PExtWidgetToast',
|
|
2298
|
+
label: action.toast.label ?? action.label ?? '',
|
|
2299
|
+
},
|
|
2300
|
+
}),
|
|
2301
|
+
})),
|
|
2347
2302
|
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
* rather than opening a url; `kind`/`state` semantics beyond the observed `'OTHER'`/`'PENDING'`
|
|
2352
|
-
* defaults aren't publicly documented, so treat this as experimental.
|
|
2353
|
-
*
|
|
2354
|
-
* Vanz@Add (v4.8) --- accepts an `{ layout }` override so consecutive `addWidget()` calls can
|
|
2355
|
-
* pick different renderings (e.g. one `HScroll` row, one `ActionRow` stack) instead of always
|
|
2356
|
-
* inferring HScroll-for-array/Single-for-object from the shape of `data`. Also accepts either
|
|
2357
|
-
* `ctas` (original key, matches the wire field) or `actions` (alias) on each item — whichever
|
|
2358
|
-
* is present is used; `ctas` wins if both are somehow given.
|
|
2359
|
-
* @param {Record<string, any>|Record<string, any>[]} data `{ title, ctas|actions: [{ label, tool_call_id?, kind?, state?, toast? }] }` (single or array).
|
|
2360
|
-
* @param {{layout?: 'Single'|'HScroll'|'ActionRow'|string}} [options] `layout` overrides the default single/array inference.
|
|
2361
|
-
*/
|
|
2362
|
-
addWidget(data = {}, { layout } = {}) {
|
|
2363
|
-
const items = Array.isArray(data) ? data : [data];
|
|
2303
|
+
...(item.body ?? {}),
|
|
2304
|
+
},
|
|
2305
|
+
}));
|
|
2364
2306
|
|
|
2365
|
-
|
|
2366
|
-
if (!item?.title) {
|
|
2367
|
-
throw new TypeError(`addWidget() item[${i}] is missing a required "title"`);
|
|
2368
|
-
}
|
|
2369
|
-
const ctas = item.ctas ?? item.actions;
|
|
2370
|
-
if (!Array.isArray(ctas) || !ctas.length) {
|
|
2371
|
-
throw new TypeError(`addWidget() item[${i}] requires a non-empty "ctas" (or "actions") array`);
|
|
2372
|
-
}
|
|
2373
|
-
});
|
|
2307
|
+
const section = AIRich.newLayout(layout ?? (isArray ? 'HScroll' : 'Single'), isArray ? widgets : widgets[0], options);
|
|
2374
2308
|
|
|
2375
|
-
this.
|
|
2376
|
-
messageType: 2,
|
|
2377
|
-
messageText: items.map((item) => item.title).join(', '),
|
|
2378
|
-
});
|
|
2309
|
+
const submessage = this.createAlert('GenAI3PExtWidgetStandardHeader');
|
|
2379
2310
|
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
body: {
|
|
2385
|
-
sections: item.sections ?? [],
|
|
2386
|
-
ctas: ctas.map((cta, idx) => ({
|
|
2387
|
-
label: cta.label ?? '',
|
|
2388
|
-
state: cta.state ?? 'PENDING',
|
|
2389
|
-
kind: cta.kind ?? 'OTHER',
|
|
2390
|
-
tool_call_id: cta.tool_call_id ?? String(idx).padStart(2, '0'),
|
|
2391
|
-
...(cta.toast !== false && {
|
|
2392
|
-
toast: { label: typeof cta.toast === 'string' ? cta.toast : item.title, __typename: 'GenAI3PExtWidgetToast' },
|
|
2393
|
-
}),
|
|
2394
|
-
__typename: 'GenAI3PExtWidgetCTA',
|
|
2395
|
-
})),
|
|
2396
|
-
__typename: item.body_typename ?? 'GenAI3PExtCalendarEventList',
|
|
2397
|
-
},
|
|
2398
|
-
__typename: 'GenAI3PExtWidgetPrimitive',
|
|
2399
|
-
};
|
|
2311
|
+
return this._addContent(section, submessage, {
|
|
2312
|
+
id,
|
|
2313
|
+
replace,
|
|
2314
|
+
insertAt,
|
|
2400
2315
|
});
|
|
2401
|
-
|
|
2402
|
-
const resolvedLayout = layout ?? (Array.isArray(data) ? 'HScroll' : 'Single');
|
|
2403
|
-
const asArray = resolvedLayout !== 'Single';
|
|
2404
|
-
|
|
2405
|
-
this._sections.push(AIRich.newLayout(resolvedLayout, asArray ? widgets : widgets[0]));
|
|
2406
|
-
|
|
2407
|
-
return this;
|
|
2408
2316
|
}
|
|
2409
2317
|
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
*/
|
|
2415
|
-
addFooterAction(actions) {
|
|
2416
|
-
const items = Array.isArray(actions) ? actions : [actions];
|
|
2318
|
+
addFooterAction(data, { layout, id, replace, insertAt, ...options } = {}) {
|
|
2319
|
+
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
2320
|
+
throw new TypeError('Footer action must be an object or an array of objects');
|
|
2321
|
+
}
|
|
2417
2322
|
|
|
2418
|
-
|
|
2419
|
-
if (!item?.text || !item?.url) {
|
|
2420
|
-
throw new TypeError(`addFooterAction() item[${i}] requires both "text" and "url"`);
|
|
2421
|
-
}
|
|
2422
|
-
});
|
|
2323
|
+
const isArray = Array.isArray(data);
|
|
2423
2324
|
|
|
2424
|
-
const
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
cta_url: item.url,
|
|
2325
|
+
const items = isArray ? data : [data];
|
|
2326
|
+
|
|
2327
|
+
const actions = items.map((item) => ({
|
|
2428
2328
|
__typename: 'GenAIFooterActionPrimitive',
|
|
2429
|
-
}));
|
|
2430
2329
|
|
|
2431
|
-
|
|
2330
|
+
cta_text: item.text ?? item.cta_text ?? '',
|
|
2432
2331
|
|
|
2433
|
-
|
|
2434
|
-
}
|
|
2332
|
+
cta_type: item.type ?? item.cta_type ?? 'OPEN_URL',
|
|
2435
2333
|
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
// have no dedicated AIRichResponseSubMessageType — WA carries them purely in the
|
|
2439
|
-
// unifiedResponse view-model JSON, so their submessage falls back to plain AI_RICH_RESPONSE_TEXT
|
|
2440
|
-
// like addTip/addHeading already do. Latex is the one exception: it has a real proto type
|
|
2441
|
-
// (AI_RICH_RESPONSE_LATEX = 8, confirmed in WAProto) with its own latexMetadata, so that one
|
|
2442
|
-
// gets a proper submessage instead of the text fallback.
|
|
2334
|
+
cta_url: item.url ?? item.cta_url ?? '',
|
|
2335
|
+
}));
|
|
2443
2336
|
|
|
2444
|
-
|
|
2445
|
-
addDivider() {
|
|
2446
|
-
this._submessages.push({ messageType: 2, messageText: '---' });
|
|
2447
|
-
this._sections.push(AIRich.newLayout('Single', { __typename: 'GenAIDividerPrimitive' }));
|
|
2448
|
-
return this;
|
|
2449
|
-
}
|
|
2337
|
+
const section = AIRich.newLayout(layout ?? (isArray ? 'HScroll' : 'Single'), isArray ? actions : actions[0], options);
|
|
2450
2338
|
|
|
2451
|
-
|
|
2452
|
-
addSpacer(spacing = 1) {
|
|
2453
|
-
if (typeof spacing !== 'number' || spacing < 0) {
|
|
2454
|
-
throw new TypeError('addSpacer(spacing) requires a non-negative number');
|
|
2455
|
-
}
|
|
2456
|
-
this._submessages.push({ messageType: 2, messageText: `spasi ${spacing}` });
|
|
2457
|
-
this._sections.push(AIRich.newLayout('Single', { spacing, __typename: 'GenAISpacerPrimitive' }));
|
|
2458
|
-
return this;
|
|
2459
|
-
}
|
|
2339
|
+
const submessage = this.createAlert('GenAIFooterActionPrimitive');
|
|
2460
2340
|
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
*/
|
|
2466
|
-
addLatex(expression) {
|
|
2467
|
-
if (typeof expression !== 'string' || !expression) {
|
|
2468
|
-
throw new TypeError('addLatex(expression) requires a non-empty string');
|
|
2469
|
-
}
|
|
2470
|
-
this._submessages.push({
|
|
2471
|
-
messageType: 8,
|
|
2472
|
-
latexMetadata: { text: expression, expressions: [{ latexExpression: expression }] },
|
|
2341
|
+
return this._addContent(section, submessage, {
|
|
2342
|
+
id,
|
|
2343
|
+
replace,
|
|
2344
|
+
insertAt,
|
|
2473
2345
|
});
|
|
2474
|
-
this._sections.push(AIRich.newLayout('Single', { latex_expression: expression, __typename: 'GenAILatexUXPrimitive' }));
|
|
2475
|
-
return this;
|
|
2476
|
-
}
|
|
2477
|
-
|
|
2478
|
-
/**
|
|
2479
|
-
* Add a task/checklist card (`GenAITaskPrimitive`).
|
|
2480
|
-
* @param {{task_id?: string, title: string, subtitle?: string, status?: string}} data
|
|
2481
|
-
*/
|
|
2482
|
-
addTask(data = {}) {
|
|
2483
|
-
if (!data?.title) {
|
|
2484
|
-
throw new TypeError('addTask() requires a "title"');
|
|
2485
|
-
}
|
|
2486
|
-
this._submessages.push({ messageType: 2, messageText: `Tugas: ${data.title}` });
|
|
2487
|
-
this._sections.push(
|
|
2488
|
-
AIRich.newLayout('Single', {
|
|
2489
|
-
task_id: data.task_id ?? '',
|
|
2490
|
-
title: data.title,
|
|
2491
|
-
subtitle: data.subtitle ?? '',
|
|
2492
|
-
status: data.status ?? 'IN_PROGRESS',
|
|
2493
|
-
__typename: 'GenAITaskPrimitive',
|
|
2494
|
-
})
|
|
2495
|
-
);
|
|
2496
|
-
// Safety net: GenAITaskPrimitive is a custom AI-only component the stock WA client
|
|
2497
|
-
// doesn't render visibly. Append a plain text section so the task is still visible.
|
|
2498
|
-
// Set data.textFallback = false to skip.
|
|
2499
|
-
if (data.textFallback !== false) {
|
|
2500
|
-
const fallbackText = data.subtitle ? `${data.title} — ${data.subtitle}` : data.title;
|
|
2501
|
-
this._sections.push(AIRich.newLayout('Single', { text: `Tugas: ${fallbackText}`, __typename: 'FOATextPrimitive' }));
|
|
2502
|
-
}
|
|
2503
|
-
return this;
|
|
2504
2346
|
}
|
|
2505
2347
|
|
|
2506
|
-
|
|
2507
|
-
* Add a "searching/working" progress banner (`GenAIBotProgressStatusPrimitive`) — a one-shot
|
|
2508
|
-
* status chip (unlike `addSuggest`, this isn't tappable). Distinct from `addThinkingStatus()`'s
|
|
2509
|
-
* icon/typename.
|
|
2510
|
-
* @param {string} title
|
|
2511
|
-
* @param {{icon?: string, is_in_progress?: boolean}} [options]
|
|
2512
|
-
*/
|
|
2513
|
-
addProgressStatus(title, { icon = 'SEARCH', is_in_progress = true, target_secondary_screen_id, target_secondary_screen_tab_id } = {}) {
|
|
2514
|
-
if (typeof title !== 'string' || !title) {
|
|
2515
|
-
throw new TypeError('addProgressStatus(title) requires a non-empty string');
|
|
2516
|
-
}
|
|
2517
|
-
this._submessages.push({ messageType: 2, messageText: title });
|
|
2518
|
-
const primitive = {
|
|
2519
|
-
title,
|
|
2520
|
-
icon,
|
|
2521
|
-
is_in_progress,
|
|
2522
|
-
meta_search_apps: [],
|
|
2523
|
-
__typename: 'GenAIBotProgressStatusPrimitive',
|
|
2524
|
-
};
|
|
2525
|
-
// NOTE: these two fields must be OMITTED when unset, not sent as `null` —
|
|
2526
|
-
// an explicit null here was reproducibly crashing the WA client renderer
|
|
2527
|
-
// on group-open/media-download. Only include when the caller actually passes one.
|
|
2528
|
-
if (target_secondary_screen_id != null) primitive.target_secondary_screen_id = target_secondary_screen_id;
|
|
2529
|
-
if (target_secondary_screen_tab_id != null) primitive.target_secondary_screen_tab_id = target_secondary_screen_tab_id;
|
|
2530
|
-
this._sections.push(AIRich.newLayout('Single', primitive));
|
|
2531
|
-
return this;
|
|
2532
|
-
}
|
|
2533
|
-
|
|
2534
|
-
/** Add a "thinking" status banner (`GenAIBotThinkingStatusPrimitive`). See `addProgressStatus()`. */
|
|
2535
|
-
addThinkingStatus(title, { icon = 'THINKING', is_in_progress = true, target_secondary_screen_id, target_secondary_screen_tab_id, textFallback = true } = {}) {
|
|
2536
|
-
if (typeof title !== 'string' || !title) {
|
|
2537
|
-
throw new TypeError('addThinkingStatus(title) requires a non-empty string');
|
|
2538
|
-
}
|
|
2539
|
-
this._submessages.push({ messageType: 2, messageText: title });
|
|
2540
|
-
const primitive = {
|
|
2541
|
-
title,
|
|
2542
|
-
icon,
|
|
2543
|
-
is_in_progress,
|
|
2544
|
-
meta_search_apps: [],
|
|
2545
|
-
__typename: 'GenAIBotThinkingStatusPrimitive',
|
|
2546
|
-
};
|
|
2547
|
-
// Same crash-avoidance rule as addProgressStatus(): omit, never null.
|
|
2548
|
-
if (target_secondary_screen_id != null) primitive.target_secondary_screen_id = target_secondary_screen_id;
|
|
2549
|
-
if (target_secondary_screen_tab_id != null) primitive.target_secondary_screen_tab_id = target_secondary_screen_tab_id;
|
|
2550
|
-
this._sections.push(AIRich.newLayout('Single', primitive));
|
|
2551
|
-
// Safety net: stock WA client doesn't render this primitive's own view (it's meant
|
|
2552
|
-
// as a transient spinner in the official app), so the card shows blank when forwarded.
|
|
2553
|
-
// Append a plain text section so the title is still visible. Set { textFallback: false } to skip.
|
|
2554
|
-
if (textFallback) {
|
|
2555
|
-
this._sections.push(AIRich.newLayout('Single', { text: title, __typename: 'FOATextPrimitive' }));
|
|
2556
|
-
}
|
|
2557
|
-
return this;
|
|
2558
|
-
}
|
|
2559
|
-
|
|
2560
|
-
/**
|
|
2561
|
-
* Add a subscription-quota-limit upsell card (`GenAIMetaSubsQuotaUpsellPrimitive`).
|
|
2562
|
-
* @param {{title: string, body?: string, body_line1?: string, body_line2?: string, buttons?: {label: string, action?: string, deeplink?: string}[]}} data
|
|
2563
|
-
*/
|
|
2564
|
-
addQuotaUpsell(data = {}) {
|
|
2565
|
-
if (!data?.title) {
|
|
2566
|
-
throw new TypeError('addQuotaUpsell() requires a "title"');
|
|
2567
|
-
}
|
|
2568
|
-
this._submessages.push({ messageType: 2, messageText: data.title });
|
|
2569
|
-
this._sections.push(
|
|
2570
|
-
AIRich.newLayout('Single', {
|
|
2571
|
-
title: data.title,
|
|
2572
|
-
body: data.body ?? '',
|
|
2573
|
-
body_line1: data.body_line1 ?? '',
|
|
2574
|
-
body_line2: data.body_line2 ?? '',
|
|
2575
|
-
buttons: (data.buttons ?? []).map((b) => ({
|
|
2576
|
-
label: b.label ?? '',
|
|
2577
|
-
action: b.action ?? 'OPEN_DEEPLINK',
|
|
2578
|
-
deeplink: b.deeplink ?? '',
|
|
2579
|
-
})),
|
|
2580
|
-
__typename: 'GenAIMetaSubsQuotaUpsellPrimitive',
|
|
2581
|
-
})
|
|
2582
|
-
);
|
|
2583
|
-
return this;
|
|
2584
|
-
}
|
|
2585
|
-
|
|
2586
|
-
/**
|
|
2587
|
-
* Add a raw Bloks payload (`FOABloksPrimitive`) — Meta's internal UI-description format.
|
|
2588
|
-
* Escape hatch: field meaning beyond what's passed through is undocumented, so this is the
|
|
2589
|
-
* most experimental primitive in this block; pass whatever your captured traffic shows.
|
|
2590
|
-
* @param {{type: string, data: string, uuid?: string, initial_response?: any, versioning_id?: string}} data
|
|
2591
|
-
*/
|
|
2592
|
-
addBloks(data = {}) {
|
|
2593
|
-
if (!data?.type) {
|
|
2594
|
-
throw new TypeError('addBloks() requires a "type"');
|
|
2595
|
-
}
|
|
2596
|
-
this._submessages.push({ messageType: 2, messageText: 'Bloks' });
|
|
2597
|
-
const primitive = {
|
|
2598
|
-
type: data.type,
|
|
2599
|
-
data: data.data ?? '{}',
|
|
2600
|
-
uuid: data.uuid ?? '',
|
|
2601
|
-
versioning_id: data.versioning_id ?? '',
|
|
2602
|
-
__typename: 'FOABloksPrimitive',
|
|
2603
|
-
};
|
|
2604
|
-
// Omit initial_response entirely when unset — same null-field crash as addProgressStatus/addThinkingStatus.
|
|
2605
|
-
if (data.initial_response != null) primitive.initial_response = data.initial_response;
|
|
2606
|
-
this._sections.push(AIRich.newLayout('Single', primitive));
|
|
2607
|
-
// Safety net: FOABloksPrimitive needs a real, client-registered Bloks screen to render
|
|
2608
|
-
// anything — arbitrary/placeholder payloads show up blank. Append a plain text section
|
|
2609
|
-
// so the card isn't empty. Set data.textFallback = false to skip.
|
|
2610
|
-
if (data.textFallback !== false) {
|
|
2611
|
-
this._sections.push(AIRich.newLayout('Single', { text: `Bloks: ${data.type}`, __typename: 'FOATextPrimitive' }));
|
|
2612
|
-
}
|
|
2613
|
-
return this;
|
|
2614
|
-
}
|
|
2615
|
-
|
|
2616
|
-
/** Add tappable follow-up suggestion chips below the message. @param {string|string[]} suggestion */
|
|
2617
|
-
addSuggest(suggestion, { scroll = true, layout } = {}) {
|
|
2348
|
+
addSuggest(suggestion, { scroll = true, layout, id, replace, insertAt } = {}) {
|
|
2618
2349
|
if (!(typeof suggestion === 'string' || (Array.isArray(suggestion) && suggestion.every((v) => typeof v === 'string')))) {
|
|
2619
2350
|
throw new TypeError('Suggestion must be a string or array of strings');
|
|
2620
2351
|
}
|
|
@@ -2635,18 +2366,28 @@ class AIRich extends BaseBuilder {
|
|
|
2635
2366
|
|
|
2636
2367
|
const type = layout ?? (suggest.length === 1 ? 'Single' : scroll ? 'HScroll' : 'ActionRow');
|
|
2637
2368
|
|
|
2638
|
-
|
|
2369
|
+
const section = AIRich.newLayout(type, type === 'Single' ? suggest[0] : suggest, {
|
|
2370
|
+
__typename: 'GenAIUnifiedResponseSection',
|
|
2371
|
+
});
|
|
2372
|
+
|
|
2373
|
+
const submessage = this.createAlert('GenAIFollowUpSuggestionPillPrimitive');
|
|
2639
2374
|
|
|
2640
|
-
return this
|
|
2375
|
+
return this._addContent(section, submessage, {
|
|
2376
|
+
id,
|
|
2377
|
+
replace,
|
|
2378
|
+
insertAt,
|
|
2379
|
+
});
|
|
2641
2380
|
}
|
|
2642
2381
|
|
|
2643
|
-
|
|
2644
|
-
|
|
2382
|
+
async build(
|
|
2383
|
+
jid,
|
|
2384
|
+
{ bypassDownload = true, forwarded = true, notification = false, includesUnifiedResponse = true, includesSubmessages = true, quoted, quotedParticipant, messageId, ...options } = {}
|
|
2385
|
+
) {
|
|
2645
2386
|
const forward = forwarded
|
|
2646
2387
|
? {
|
|
2647
2388
|
forwardingScore: 1,
|
|
2648
2389
|
isForwarded: true,
|
|
2649
|
-
forwardedAiBotMessageInfo: { botJid: '
|
|
2390
|
+
forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' },
|
|
2650
2391
|
forwardOrigin: 4,
|
|
2651
2392
|
}
|
|
2652
2393
|
: {};
|
|
@@ -2664,7 +2405,7 @@ class AIRich extends BaseBuilder {
|
|
|
2664
2405
|
const qObj = quoted
|
|
2665
2406
|
? {
|
|
2666
2407
|
stanzaId: quoted?.key?.id || quoted?.id,
|
|
2667
|
-
participant: quotedParticipant || quoted?.key?.participant || quoted?.key?.remoteJid,
|
|
2408
|
+
participant: quotedParticipant || quoted?.key?.participant || quoted?.participant || quoted?.key?.remoteJid,
|
|
2668
2409
|
quotedType: 0,
|
|
2669
2410
|
quotedMessage: typeof quoted === 'object' && quoted !== null ? (quoted.message ?? quoted) : undefined,
|
|
2670
2411
|
}
|
|
@@ -2680,85 +2421,131 @@ class AIRich extends BaseBuilder {
|
|
|
2680
2421
|
]
|
|
2681
2422
|
: [...(await waitAllPromises(this._sections))];
|
|
2682
2423
|
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
// Vanz@Fix 24-08-26 --- was `const responseId = crypto.randomUUID()` shared for BOTH
|
|
2688
|
-
// unifiedResponse.response_id and botMetadata.botResponseId, generated fresh every build()
|
|
2689
|
-
// with no override. Now each has its own id, pinned via setResponseId()/setBotResponseId()
|
|
2690
|
-
// if the caller set one (for sendEdit()-style in-place message updates), otherwise still
|
|
2691
|
-
// defaults to a fresh randomUUID() per build() exactly like before.
|
|
2692
|
-
const responseId = this._responseId ?? crypto.randomUUID();
|
|
2693
|
-
const botResponseId = this._botResponseId ?? crypto.randomUUID();
|
|
2424
|
+
if (this._dynamic) {
|
|
2425
|
+
this.refreshResponseId();
|
|
2426
|
+
this.refreshBotResponseId();
|
|
2427
|
+
}
|
|
2694
2428
|
|
|
2695
|
-
return
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2429
|
+
return generateWAMessageFromContent(
|
|
2430
|
+
jid,
|
|
2431
|
+
{
|
|
2432
|
+
messageContextInfo: {
|
|
2433
|
+
deviceListMetadata: {},
|
|
2434
|
+
deviceListMetadataVersion: 2,
|
|
2435
|
+
botMetadata: {
|
|
2436
|
+
messageDisclaimerText: this._title,
|
|
2437
|
+
...notif,
|
|
2438
|
+
verificationMetadata: AIRich.generateVerificationMetadata(),
|
|
2439
|
+
botResponseId: this._botResponseId,
|
|
2440
|
+
},
|
|
2441
|
+
},
|
|
2442
|
+
...this._extraPayload,
|
|
2443
|
+
botForwardedMessage: {
|
|
2444
|
+
message: {
|
|
2445
|
+
richResponseMessage: {
|
|
2446
|
+
messageType: 1,
|
|
2447
|
+
submessages: includesSubmessages ? await waitAllPromises(this._submessages) : [],
|
|
2448
|
+
unifiedResponse: {
|
|
2449
|
+
data: includesUnifiedResponse ? Buffer.from(Toolkit.stringifyEscaped({ response_id: this._responseId, sections })).toString('base64') : '',
|
|
2710
2450
|
},
|
|
2711
|
-
|
|
2451
|
+
contextInfo: {
|
|
2452
|
+
...forward,
|
|
2453
|
+
...qObj,
|
|
2454
|
+
...this._contextInfo,
|
|
2455
|
+
},
|
|
2456
|
+
},
|
|
2712
2457
|
},
|
|
2713
|
-
...notif,
|
|
2714
2458
|
},
|
|
2715
2459
|
},
|
|
2716
|
-
...
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2460
|
+
{ messageId: messageId || generateMessageIDV2(), ...options }
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
async buildEdit(targetJid, targetId, { msg, messageId, ...options } = {}) {
|
|
2465
|
+
if (!msg) {
|
|
2466
|
+
msg = (await this.build(targetJid, options)).message;
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
const editedMessage = msg;
|
|
2470
|
+
|
|
2471
|
+
if (!editedMessage) {
|
|
2472
|
+
throw new Error('buildEdit: msg does not contain botForwardedMessage');
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
return generateWAMessageFromContent(
|
|
2476
|
+
targetJid,
|
|
2477
|
+
{
|
|
2478
|
+
botForwardedMessage: {
|
|
2479
|
+
message: {
|
|
2480
|
+
protocolMessage: {
|
|
2481
|
+
key: {
|
|
2482
|
+
remoteJid: targetJid,
|
|
2483
|
+
fromMe: true,
|
|
2484
|
+
id: targetId,
|
|
2485
|
+
},
|
|
2486
|
+
type: 14,
|
|
2487
|
+
editedMessage,
|
|
2729
2488
|
},
|
|
2730
2489
|
},
|
|
2731
2490
|
},
|
|
2732
2491
|
},
|
|
2733
|
-
|
|
2492
|
+
{ messageId: messageId || generateMessageIDV2(), ...options }
|
|
2493
|
+
);
|
|
2734
2494
|
}
|
|
2735
2495
|
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2496
|
+
async sendEdit(jid, id, { msg, messageId, additionalNodes = [], ...options } = {}) {
|
|
2497
|
+
jid = jid ?? this._lastMessageKey?.remoteJid;
|
|
2498
|
+
id = id ?? this._lastMessageKey?.id;
|
|
2499
|
+
|
|
2500
|
+
if (!jid) {
|
|
2501
|
+
throw new Error('JID is required');
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
if (!id) {
|
|
2505
|
+
throw new Error('Message id is required');
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
const msgEdit = await this.buildEdit(jid, id, {
|
|
2509
|
+
msg,
|
|
2510
|
+
messageId: messageId || generateMessageIDV2(),
|
|
2511
|
+
...options,
|
|
2512
|
+
});
|
|
2513
|
+
|
|
2514
|
+
await this.#client.relayMessage(jid, msgEdit.message, {
|
|
2515
|
+
messageId: msgEdit.key.id,
|
|
2516
|
+
additionalNodes,
|
|
2517
|
+
});
|
|
2518
|
+
|
|
2519
|
+
return msgEdit;
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
async send(jid, { bypassDownload = true, forwarded = true, notification = false, includesUnifiedResponse = true, includesSubmessages = true, messageId, additionalNodes = [], ...options } = {}) {
|
|
2523
|
+
const msg = await this.build(jid, {
|
|
2524
|
+
forwarded,
|
|
2525
|
+
notification,
|
|
2526
|
+
includesUnifiedResponse,
|
|
2527
|
+
includesSubmessages,
|
|
2528
|
+
messageId,
|
|
2529
|
+
...options,
|
|
2530
|
+
});
|
|
2531
|
+
|
|
2532
|
+
await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
|
|
2533
|
+
messageId: msg.key.id,
|
|
2534
|
+
additionalNodes,
|
|
2535
|
+
...options,
|
|
2536
|
+
});
|
|
2537
|
+
|
|
2538
|
+
if (includesUnifiedResponse && bypassDownload) {
|
|
2539
|
+
await this.sendEdit(jid, msg.key.id, {
|
|
2540
|
+
msg: msg.message,
|
|
2541
|
+
});
|
|
2756
2542
|
}
|
|
2757
2543
|
|
|
2758
|
-
|
|
2544
|
+
this._lastMessageKey = msg.key;
|
|
2545
|
+
|
|
2546
|
+
return msg;
|
|
2759
2547
|
}
|
|
2760
2548
|
|
|
2761
|
-
/** Tokenize `code` into `{ type, value }` spans for syntax highlighting. Covers JS/TS/Python/Java and more; unsupported languages fall back to a single plain-text token. */
|
|
2762
2549
|
static tokenizer(code, lang = 'javascript') {
|
|
2763
2550
|
const keywordsMap = {
|
|
2764
2551
|
javascript: new Set([
|
|
@@ -3424,7 +3211,6 @@ class AIRich extends BaseBuilder {
|
|
|
3424
3211
|
};
|
|
3425
3212
|
}
|
|
3426
3213
|
|
|
3427
|
-
/** Convert a raw `string[][]` grid into the table metadata shape addTable()/addText() produce internally. */
|
|
3428
3214
|
static toTableMetadata(arr, { hyperlink = true, citation = true, latex = true } = {}) {
|
|
3429
3215
|
if (!Array.isArray(arr) || !arr.every((row) => Array.isArray(row) && row.every((cell) => typeof cell === 'string'))) {
|
|
3430
3216
|
throw new TypeError('Table must be a nested array of strings');
|
|
@@ -3473,120 +3259,35 @@ class AIRich extends BaseBuilder {
|
|
|
3473
3259
|
};
|
|
3474
3260
|
}
|
|
3475
3261
|
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
* Use this to show a pending-generation state before the real media is ready.
|
|
3480
|
-
* @param {{ imagine_type?: 'IMAGE'|'ANIMATE', estimated_completion_time?: number }} [options]
|
|
3481
|
-
*/
|
|
3482
|
-
addGenerating({ imagine_type = 'IMAGE', estimated_completion_time, textFallback = true } = {}) {
|
|
3483
|
-
this._submessages.push({ messageType: 2, messageText: '[ Sedang diproses... ]' });
|
|
3484
|
-
this._sections.push(
|
|
3485
|
-
AIRich.newLayout('Single', {
|
|
3486
|
-
media: { url: '', mime_type: imagine_type === 'ANIMATE' ? 'video/mp4' : 'image/png' },
|
|
3487
|
-
imagine_type,
|
|
3488
|
-
status: {
|
|
3489
|
-
status: 'GENERATING',
|
|
3490
|
-
estimated_completion_time: estimated_completion_time ?? Math.floor(Date.now() / 1000) + 30,
|
|
3491
|
-
},
|
|
3492
|
-
__typename: 'GenAIImaginePrimitive',
|
|
3493
|
-
})
|
|
3262
|
+
static generateVerificationMetadata() {
|
|
3263
|
+
const signatureMaterial = Buffer.from(
|
|
3264
|
+
`\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`
|
|
3494
3265
|
);
|
|
3495
|
-
// Vanz@Fix 23-08-26 (v4.8) --- media.url kosong + status GENERATING gak punya renderer
|
|
3496
|
-
// visual instan di stock WA client; sebelumnya cuma diem sampe WA nge-timeout sendiri
|
|
3497
|
-
// dan nampilin fallback bawaannya ("Saat ini, saya tidak bisa membuat gambar itu...").
|
|
3498
|
-
// Same fix class kayak addTask/addBloks: append FOATextPrimitive biar ada fallback
|
|
3499
|
-
// instan, gak perlu nunggu timeout WA. Set { textFallback: false } buat skip.
|
|
3500
|
-
if (textFallback) {
|
|
3501
|
-
this._sections.push(AIRich.newLayout('Single', { text: '[ Sedang diproses... ]', __typename: 'FOATextPrimitive' }));
|
|
3502
|
-
}
|
|
3503
|
-
return this;
|
|
3504
|
-
}
|
|
3505
3266
|
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
* conversation message tagged as an AI/support-bot ticket, distinct from richResponseMessage.
|
|
3509
|
-
* @param {import('../../WAProto/index.js').WASocket} client
|
|
3510
|
-
* @param {string} jid
|
|
3511
|
-
* @param {string} text
|
|
3512
|
-
* @param {{ ticketId?: string, isAiMessage?: boolean, shouldShowSystemMessage?: boolean, version?: number }} [options]
|
|
3513
|
-
*/
|
|
3514
|
-
static async sendSupportPayload(client, jid, text, { ticketId = crypto.randomUUID(), isAiMessage = true, shouldShowSystemMessage = true, version = 1 } = {}) {
|
|
3515
|
-
if (!client) throw new Error('Socket is required');
|
|
3516
|
-
if (typeof text !== 'string' || !text) throw new TypeError('sendSupportPayload(client, jid, text) requires a non-empty string text');
|
|
3517
|
-
|
|
3518
|
-
const msg = {
|
|
3519
|
-
conversation: text,
|
|
3520
|
-
messageContextInfo: {
|
|
3521
|
-
messageSecret: crypto.randomBytes(32),
|
|
3522
|
-
supportPayload: JSON.stringify({
|
|
3523
|
-
version,
|
|
3524
|
-
is_ai_message: isAiMessage,
|
|
3525
|
-
should_show_system_message: shouldShowSystemMessage,
|
|
3526
|
-
ticket_id: ticketId,
|
|
3527
|
-
}),
|
|
3528
|
-
},
|
|
3529
|
-
};
|
|
3530
|
-
|
|
3531
|
-
return client.relayMessage(jid, msg, {
|
|
3532
|
-
additionalNodes: [
|
|
3533
|
-
{ tag: 'bot', attrs: { biz_bot: '1' } },
|
|
3534
|
-
{ tag: 'biz', attrs: {} },
|
|
3535
|
-
],
|
|
3536
|
-
});
|
|
3537
|
-
}
|
|
3538
|
-
|
|
3539
|
-
/**
|
|
3540
|
-
* Send an image and video as one paired-media unit (image sent first, video linked to it via
|
|
3541
|
-
* `messageAssociation`). Distinct from a plain album — the client treats them as a single group.
|
|
3542
|
-
* @param {import('../../WAProto/index.js').WASocket} client
|
|
3543
|
-
* @param {string} jid
|
|
3544
|
-
* @param {{ image: string|Buffer, video: string|Buffer }} media
|
|
3545
|
-
*/
|
|
3546
|
-
static async sendPairedMedia(client, jid, { image, video } = {}) {
|
|
3547
|
-
if (!client) throw new Error('Socket is required');
|
|
3548
|
-
if (!image || !video) throw new TypeError('sendPairedMedia() requires both "image" and "video"');
|
|
3549
|
-
|
|
3550
|
-
const imagePrepared = await prepareWAMessageMedia(
|
|
3551
|
-
{ image: typeof image === 'string' ? { url: image } : image },
|
|
3552
|
-
{ upload: client.waUploadToServer }
|
|
3553
|
-
);
|
|
3554
|
-
const videoPrepared = await prepareWAMessageMedia(
|
|
3555
|
-
{ video: typeof video === 'string' ? { url: video } : video },
|
|
3556
|
-
{ upload: client.waUploadToServer }
|
|
3267
|
+
const certificateMaterial = Buffer.from(
|
|
3268
|
+
`\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`
|
|
3557
3269
|
);
|
|
3558
3270
|
|
|
3559
|
-
const
|
|
3560
|
-
jid,
|
|
3561
|
-
{
|
|
3562
|
-
imageMessage: {
|
|
3563
|
-
...imagePrepared.imageMessage,
|
|
3564
|
-
contextInfo: { pairedMediaType: 5, statusSourceType: 0 },
|
|
3565
|
-
},
|
|
3566
|
-
},
|
|
3567
|
-
{}
|
|
3568
|
-
);
|
|
3271
|
+
const signature = Buffer.concat([signatureMaterial, crypto.randomBytes(64 - signatureMaterial.length)]).toString('base64');
|
|
3569
3272
|
|
|
3570
|
-
|
|
3273
|
+
const certificateChain = [
|
|
3274
|
+
Buffer.concat([certificateMaterial, crypto.randomBytes(684 - certificateMaterial.length)]).toString('base64'),
|
|
3571
3275
|
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
{
|
|
3575
|
-
videoMessage: {
|
|
3576
|
-
...videoPrepared.videoMessage,
|
|
3577
|
-
contextInfo: { pairedMediaType: 6, statusSourceType: 0 },
|
|
3578
|
-
},
|
|
3579
|
-
messageContextInfo: {
|
|
3580
|
-
messageAssociation: { associationType: 12, parentMessageKey: imageMsg.key },
|
|
3581
|
-
},
|
|
3582
|
-
},
|
|
3583
|
-
{}
|
|
3584
|
-
);
|
|
3276
|
+
Buffer.concat([certificateMaterial, crypto.randomBytes(892 - certificateMaterial.length)]).toString('base64'),
|
|
3277
|
+
];
|
|
3585
3278
|
|
|
3586
|
-
return
|
|
3279
|
+
return {
|
|
3280
|
+
proofs: [
|
|
3281
|
+
{
|
|
3282
|
+
version: 1,
|
|
3283
|
+
useCase: 1,
|
|
3284
|
+
signature,
|
|
3285
|
+
certificateChain,
|
|
3286
|
+
},
|
|
3287
|
+
],
|
|
3288
|
+
};
|
|
3587
3289
|
}
|
|
3588
3290
|
|
|
3589
|
-
/** Build a raw submessage layout block by name — escape hatch for layouts not covered by the add*() helpers. */
|
|
3590
3291
|
static newLayout(name, data, extra = {}) {
|
|
3591
3292
|
return {
|
|
3592
3293
|
...extra,
|
|
@@ -3596,6 +3297,320 @@ class AIRich extends BaseBuilder {
|
|
|
3596
3297
|
},
|
|
3597
3298
|
};
|
|
3598
3299
|
}
|
|
3300
|
+
|
|
3301
|
+
_makeNode(id, section, submessage) {
|
|
3302
|
+
return { id: id ?? null, section: section ?? null, submessage: submessage ?? null };
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
_registerId(node, id) {
|
|
3306
|
+
if (id === undefined || id === null || id === '') return;
|
|
3307
|
+
|
|
3308
|
+
if (typeof id !== 'string') {
|
|
3309
|
+
throw new ContentValidationError('Item id must be a string', { id });
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
if (this._idIndex.has(id)) {
|
|
3313
|
+
throw new DuplicateIdError(id);
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
node.id = id;
|
|
3317
|
+
this._idIndex.set(id, node);
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
_unregisterId(node) {
|
|
3321
|
+
if (node.id && this._idIndex.get(node.id) === node) {
|
|
3322
|
+
this._idIndex.delete(node.id);
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
hasId(id) {
|
|
3327
|
+
return typeof id === 'string' && this._idIndex.has(id);
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
getIds() {
|
|
3331
|
+
return [...this._idIndex.keys()];
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3334
|
+
peek(id) {
|
|
3335
|
+
const node = this._idIndex.get(id);
|
|
3336
|
+
|
|
3337
|
+
if (!node) return null;
|
|
3338
|
+
|
|
3339
|
+
return {
|
|
3340
|
+
id: node.id,
|
|
3341
|
+
section: node.section,
|
|
3342
|
+
submessage: node.submessage,
|
|
3343
|
+
};
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
assignId(index, id) {
|
|
3347
|
+
if (!Number.isInteger(index) || index < 0 || index >= this._nodes.length) {
|
|
3348
|
+
throw new InvalidTargetError(`Node index ${index} is out of range (0-${this._nodes.length - 1})`, { index });
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
const node = this._nodes[index];
|
|
3352
|
+
|
|
3353
|
+
if (node.id) {
|
|
3354
|
+
throw new AIRichError(`Node at index ${index} already has id "${node.id}"`, 'ALREADY_HAS_ID', { index, id: node.id });
|
|
3355
|
+
}
|
|
3356
|
+
|
|
3357
|
+
this._registerId(node, id);
|
|
3358
|
+
|
|
3359
|
+
return this;
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
_getNode(id) {
|
|
3363
|
+
if (typeof id !== 'string' || !id) {
|
|
3364
|
+
throw new ContentValidationError('Item id must be a non-empty string', { id });
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
const node = this._idIndex.get(id);
|
|
3368
|
+
|
|
3369
|
+
if (!node) {
|
|
3370
|
+
throw new ItemNotFoundError(id, this.getIds());
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
return node;
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
_resolveTarget(target) {
|
|
3377
|
+
if (Array.isArray(target)) {
|
|
3378
|
+
if (target.length < 1 || target.length > 2) {
|
|
3379
|
+
throw new ContentValidationError('Target must be id or [id, offset]', { target });
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
const [id, offset = 0] = target;
|
|
3383
|
+
|
|
3384
|
+
if (typeof id !== 'string' || !id) {
|
|
3385
|
+
throw new ContentValidationError('Target id must be a non-empty string', { target });
|
|
3386
|
+
}
|
|
3387
|
+
|
|
3388
|
+
if (!Number.isInteger(offset)) {
|
|
3389
|
+
throw new ContentValidationError('Offset must be an integer', { target });
|
|
3390
|
+
}
|
|
3391
|
+
|
|
3392
|
+
return { id, offset };
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
if (typeof target !== 'string' || !target) {
|
|
3396
|
+
throw new ContentValidationError('Target must be a non-empty id or [id, offset]', { target });
|
|
3397
|
+
}
|
|
3398
|
+
|
|
3399
|
+
return { id: target, offset: 0 };
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3402
|
+
_resolveNodeIndex(target) {
|
|
3403
|
+
const { id, offset } = this._resolveTarget(target);
|
|
3404
|
+
const node = this._getNode(id);
|
|
3405
|
+
const baseIndex = this._nodes.indexOf(node);
|
|
3406
|
+
|
|
3407
|
+
if (baseIndex === -1) {
|
|
3408
|
+
throw new InvalidTargetError(`Item id "${id}" is registered but not present in the node list (internal desync)`, { id });
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
const index = baseIndex + offset;
|
|
3412
|
+
|
|
3413
|
+
if (index < 0 || index >= this._nodes.length) {
|
|
3414
|
+
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 });
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3417
|
+
return { id, offset, baseIndex, index };
|
|
3418
|
+
}
|
|
3419
|
+
|
|
3420
|
+
_validateSections(section) {
|
|
3421
|
+
const items = Array.isArray(section) ? section : [section];
|
|
3422
|
+
|
|
3423
|
+
if (!items.length) {
|
|
3424
|
+
throw new ContentValidationError('At least one section is required');
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3427
|
+
for (const item of items) {
|
|
3428
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
3429
|
+
throw new ContentValidationError('Sections must be plain objects');
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
|
|
3433
|
+
return items;
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
_validateSubmessages(submessage) {
|
|
3437
|
+
if (submessage === undefined || submessage === null) {
|
|
3438
|
+
return [];
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
const items = Array.isArray(submessage) ? submessage : [submessage];
|
|
3442
|
+
|
|
3443
|
+
for (const item of items) {
|
|
3444
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
3445
|
+
throw new ContentValidationError('Submessages must be plain objects');
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
return items;
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
_pairSubmessages(sections, submessages) {
|
|
3453
|
+
const n = sections.length;
|
|
3454
|
+
const m = submessages.length;
|
|
3455
|
+
|
|
3456
|
+
if (m === 0) return sections.map(() => null);
|
|
3457
|
+
if (m === 1) return sections.map((_, i) => (i === 0 ? submessages[0] : null));
|
|
3458
|
+
if (m === n) return submessages;
|
|
3459
|
+
|
|
3460
|
+
throw new ContentValidationError(`Cannot pair ${m} submessage(s) with ${n} section(s): expected 0, 1, or ${n}`, { sectionCount: n, submessageCount: m });
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
_addContent(section, submessage, { id, replace, insertAt } = {}) {
|
|
3464
|
+
const hasReplace = replace !== undefined && replace !== null && replace !== '';
|
|
3465
|
+
|
|
3466
|
+
const hasInsertAt = insertAt !== undefined && insertAt !== null && insertAt !== '';
|
|
3467
|
+
|
|
3468
|
+
if (hasReplace && hasInsertAt) {
|
|
3469
|
+
throw new ContentValidationError('replace and insertAt cannot be used together');
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
const sections = this._validateSections(section);
|
|
3473
|
+
const submessages = this._validateSubmessages(submessage);
|
|
3474
|
+
|
|
3475
|
+
if (!sections.length) {
|
|
3476
|
+
throw new ContentValidationError('At least one section is required');
|
|
3477
|
+
}
|
|
3478
|
+
|
|
3479
|
+
if (id !== undefined && id !== null && id !== '' && sections.length !== 1) {
|
|
3480
|
+
throw new ContentValidationError('One id can only be assigned to one node', {
|
|
3481
|
+
id,
|
|
3482
|
+
sectionCount: sections.length,
|
|
3483
|
+
});
|
|
3484
|
+
}
|
|
3485
|
+
|
|
3486
|
+
if (submessages.length && submessages.length !== sections.length && submessages.length !== 1) {
|
|
3487
|
+
throw new ContentValidationError('Section and submessage count must match');
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
const pairedSubmessages = sections.map((_, index) => {
|
|
3491
|
+
if (!submessages.length) return undefined;
|
|
3492
|
+
|
|
3493
|
+
return submessages.length === 1 ? submessages[0] : submessages[index];
|
|
3494
|
+
});
|
|
3495
|
+
|
|
3496
|
+
if (id && this._idIndex.has(id) && !(hasReplace && this._resolveTarget(replace)?.id === id)) {
|
|
3497
|
+
throw new DuplicateIdError(id);
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3500
|
+
const newNodes = sections.map((currentSection, index) => {
|
|
3501
|
+
return this._makeNode(index === 0 ? id : null, currentSection, pairedSubmessages[index]);
|
|
3502
|
+
});
|
|
3503
|
+
|
|
3504
|
+
if (hasReplace) {
|
|
3505
|
+
if (newNodes.length !== 1) {
|
|
3506
|
+
throw new ContentValidationError('replace only supports adding exactly one node');
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3509
|
+
const target = this._resolveNodeIndex(replace);
|
|
3510
|
+
|
|
3511
|
+
if (!target) {
|
|
3512
|
+
throw new ContentValidationError('Target node could not be resolved');
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3515
|
+
const oldNode = this._nodes[target.index];
|
|
3516
|
+
const newNode = newNodes[0];
|
|
3517
|
+
|
|
3518
|
+
if (!newNode.id && oldNode?.id) {
|
|
3519
|
+
newNode.id = oldNode.id;
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3522
|
+
this._unregisterId(oldNode);
|
|
3523
|
+
|
|
3524
|
+
this._nodes.splice(target.index, 1, newNode);
|
|
3525
|
+
|
|
3526
|
+
if (newNode.id) {
|
|
3527
|
+
this._idIndex.set(newNode.id, newNode);
|
|
3528
|
+
}
|
|
3529
|
+
|
|
3530
|
+
return this;
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
if (hasInsertAt) {
|
|
3534
|
+
const target = this._resolveNodeIndex(insertAt);
|
|
3535
|
+
|
|
3536
|
+
if (!target) {
|
|
3537
|
+
throw new ContentValidationError('Target node could not be resolved');
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
const insertIndex = target.offset < 0 ? target.index : target.index + 1;
|
|
3541
|
+
|
|
3542
|
+
this._nodes.splice(insertIndex, 0, ...newNodes);
|
|
3543
|
+
|
|
3544
|
+
for (const node of newNodes) {
|
|
3545
|
+
if (node.id) {
|
|
3546
|
+
this._idIndex.set(node.id, node);
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3550
|
+
return this;
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
this._nodes.push(...newNodes);
|
|
3554
|
+
|
|
3555
|
+
for (const node of newNodes) {
|
|
3556
|
+
if (node.id) {
|
|
3557
|
+
this._idIndex.set(node.id, node);
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
return this;
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
addSection(section, options = {}) {
|
|
3565
|
+
return this._addContent(section, undefined, options);
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
addSubmessage(submessage, options = {}) {
|
|
3569
|
+
const items = this._validateSubmessages(submessage);
|
|
3570
|
+
|
|
3571
|
+
if (!items.length) {
|
|
3572
|
+
throw new ContentValidationError('At least one submessage is required');
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
return this._addContent(undefined, items, options);
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
delete(target) {
|
|
3579
|
+
const { index } = this._resolveNodeIndex(target);
|
|
3580
|
+
const [oldNode] = this._nodes.splice(index, 1);
|
|
3581
|
+
|
|
3582
|
+
this._unregisterId(oldNode);
|
|
3583
|
+
|
|
3584
|
+
return this;
|
|
3585
|
+
}
|
|
3586
|
+
|
|
3587
|
+
get _sections() {
|
|
3588
|
+
return this._nodes.filter((n) => n.section !== null).map((n) => n.section);
|
|
3589
|
+
}
|
|
3590
|
+
|
|
3591
|
+
get _submessages() {
|
|
3592
|
+
return this._nodes.filter((n) => n.submessage !== null).map((n) => n.submessage);
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
get sections() {
|
|
3596
|
+
return this._sections;
|
|
3597
|
+
}
|
|
3598
|
+
|
|
3599
|
+
get items() {
|
|
3600
|
+
return this._sections.flatMap((section) => {
|
|
3601
|
+
const vm = section?.view_model;
|
|
3602
|
+
|
|
3603
|
+
if (Array.isArray(vm?.primitives)) {
|
|
3604
|
+
return vm.primitives;
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3607
|
+
if (vm?.primitive) {
|
|
3608
|
+
return [vm.primitive];
|
|
3609
|
+
}
|
|
3610
|
+
|
|
3611
|
+
return [];
|
|
3612
|
+
});
|
|
3613
|
+
}
|
|
3599
3614
|
}
|
|
3600
3615
|
|
|
3601
3616
|
// Vanz@Alias --- AIRich diekspos ulang pake nama sendiri. Implementasi & referensi
|