@vanzxy/baileys 1.5.4 → 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 +914 -983
- package/package.json +1 -1
|
@@ -532,6 +532,10 @@ class Toolkit {
|
|
|
532
532
|
}
|
|
533
533
|
});
|
|
534
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
|
+
}
|
|
535
539
|
}
|
|
536
540
|
|
|
537
541
|
/**
|
|
@@ -1573,10 +1577,48 @@ class Poll extends BaseBuilder {
|
|
|
1573
1577
|
* everything ChatGPT/Gemini-in-WhatsApp-style bots typically render.
|
|
1574
1578
|
* Also exported as `AIVanzxy` / `LeafRich` / `VanzxyAI` / `VanzxyRich` (identical class, alternate names).
|
|
1575
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
|
+
|
|
1576
1618
|
class AIRich extends BaseBuilder {
|
|
1577
1619
|
#client;
|
|
1578
1620
|
|
|
1579
|
-
constructor(client) {
|
|
1621
|
+
constructor(client, { dynamic = true, unsupportedTypeAlert = true } = {}) {
|
|
1580
1622
|
if (!client) {
|
|
1581
1623
|
throw new Error('Socket is required');
|
|
1582
1624
|
}
|
|
@@ -1584,152 +1626,125 @@ class AIRich extends BaseBuilder {
|
|
|
1584
1626
|
super();
|
|
1585
1627
|
this.#client = client;
|
|
1586
1628
|
this._contextInfo = {};
|
|
1587
|
-
this.
|
|
1588
|
-
this.
|
|
1589
|
-
this.
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
// Track every addInlineImage() call here so send() can fall back to a normal imageMessage.
|
|
1594
|
-
this._inlineImages = [];
|
|
1595
|
-
|
|
1596
|
-
// Vanz@Add 24-08-26 --- ported from temen's MessageBuilderV4.7 (setResponseId/setBotResponseId
|
|
1597
|
-
// below), rewritten to fit this fork's conventions. build() used to always mint a fresh
|
|
1598
|
-
// crypto.randomUUID() for both unifiedResponse.response_id and botMetadata.botResponseId on
|
|
1599
|
-
// every call, with no way to reuse one — so a `sendEdit()`-style flow (rebuild the same
|
|
1600
|
-
// message with updated content, same response_id, so WA patches it in place instead of
|
|
1601
|
-
// showing a new message) was never actually possible despite being in the gist example.
|
|
1602
|
-
// null here means "not pinned yet" — build() falls back to a fresh randomUUID() same as before
|
|
1603
|
-
// when neither setResponseId() nor setBotResponseId() has been called.
|
|
1604
|
-
this._responseId = null;
|
|
1605
|
-
this._botResponseId = null;
|
|
1606
|
-
|
|
1607
|
-
// Vanz@Add --- set by send()/sendEdit() after every relay so a follow-up sendEdit(), called
|
|
1608
|
-
// with no args, knows which jid/message id to patch in place (matches temen's v4.7 API).
|
|
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();
|
|
1609
1635
|
this._lastMessageKey = null;
|
|
1636
|
+
}
|
|
1610
1637
|
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
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);
|
|
1640
1677
|
}
|
|
1678
|
+
} catch {}
|
|
1679
|
+
}
|
|
1641
1680
|
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
const id = opts?.id;
|
|
1645
|
-
const insertAt = opts?.insertAt;
|
|
1646
|
-
|
|
1647
|
-
const subBefore = target._submessages.length;
|
|
1648
|
-
const secBefore = target._sections.length;
|
|
1649
|
-
|
|
1650
|
-
// Vanz@Fix 23-08-26 --- was orig.apply(receiver, args): calling the real method bound to
|
|
1651
|
-
// the Proxy itself (`receiver`) makes any `this.#client` access inside throw
|
|
1652
|
-
// "Cannot read private member #client from an object whose class did not declare it",
|
|
1653
|
-
// because a Proxy is never the branded instance a private field was declared on —
|
|
1654
|
-
// this hit every add*() that touches #client via Toolkit.resolveMedia(this.#client, ...)
|
|
1655
|
-
// (addProduct/addPost/addReels/addSource, and would eventually hit addImage/addVideo
|
|
1656
|
-
// too once JIT/engine specifics changed). Binding to `target` (the real instance) instead
|
|
1657
|
-
// fixes it for good; `target._submessages`/`target._sections` below are unaffected since
|
|
1658
|
-
// they're plain properties, and `result === target ? receiver : result` still converts a
|
|
1659
|
-
// `this`-return back to the Proxy so chaining (`.addX().addY()`) keeps working.
|
|
1660
|
-
const result = orig.apply(target, args);
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
const subItems = target._submessages.splice(subBefore);
|
|
1664
|
-
const secItems = target._sections.splice(secBefore);
|
|
1665
|
-
|
|
1666
|
-
if (insertAt) {
|
|
1667
|
-
const anchor = target._blocks.get(insertAt);
|
|
1668
|
-
if (!anchor) throw new Error(`insertAt: no block registered with id "${insertAt}" (register it by passing { id: "${insertAt}" } on an earlier add*() call)`);
|
|
1669
|
-
|
|
1670
|
-
const lastSub = anchor.subItems[anchor.subItems.length - 1];
|
|
1671
|
-
const subIdx = lastSub ? target._submessages.indexOf(lastSub) + 1 : target._submessages.length;
|
|
1672
|
-
target._submessages.splice(subIdx, 0, ...subItems);
|
|
1673
|
-
|
|
1674
|
-
const lastSec = anchor.secItems[anchor.secItems.length - 1];
|
|
1675
|
-
const secIdx = lastSec ? target._sections.indexOf(lastSec) + 1 : target._sections.length;
|
|
1676
|
-
target._sections.splice(secIdx, 0, ...secItems);
|
|
1677
|
-
} else {
|
|
1678
|
-
target._submessages.push(...subItems);
|
|
1679
|
-
target._sections.push(...secItems);
|
|
1680
|
-
}
|
|
1681
|
+
this._nodes = [];
|
|
1682
|
+
this._idIndex = new Map();
|
|
1681
1683
|
|
|
1682
|
-
|
|
1684
|
+
const maxLength = Math.max(loadedSections.length, loadedSubmessages.length);
|
|
1683
1685
|
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
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;
|
|
1688
1703
|
}
|
|
1689
1704
|
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
});
|
|
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;
|
|
1698
1712
|
}
|
|
1699
1713
|
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
const items = Array.isArray(submessage) ? submessage : [submessage];
|
|
1714
|
+
refreshResponseId() {
|
|
1715
|
+
this._responseId = crypto.randomUUID();
|
|
1703
1716
|
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
throw new TypeError('Submessage must be a plain object or array of plain objects');
|
|
1707
|
-
}
|
|
1717
|
+
return this;
|
|
1718
|
+
}
|
|
1708
1719
|
|
|
1709
|
-
|
|
1720
|
+
setBotResponseId(id) {
|
|
1721
|
+
if (typeof id !== 'string') {
|
|
1722
|
+
throw new TypeError('ID must be a string');
|
|
1710
1723
|
}
|
|
1724
|
+
this._botResponseId = id;
|
|
1711
1725
|
|
|
1712
1726
|
return this;
|
|
1713
1727
|
}
|
|
1714
1728
|
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
const items = Array.isArray(section) ? section : [section];
|
|
1729
|
+
refreshBotResponseId() {
|
|
1730
|
+
this._botResponseId = crypto.randomUUID();
|
|
1718
1731
|
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
throw new TypeError('Section must be a plain object or array of plain objects');
|
|
1722
|
-
}
|
|
1732
|
+
return this;
|
|
1733
|
+
}
|
|
1723
1734
|
|
|
1724
|
-
|
|
1735
|
+
createAlert(type) {
|
|
1736
|
+
if (this._unsupportedTypeAlert) {
|
|
1737
|
+
return {
|
|
1738
|
+
messageType: 2,
|
|
1739
|
+
messageText: `[ UNSUPPORTED_TYPE - ${type}]`,
|
|
1740
|
+
};
|
|
1725
1741
|
}
|
|
1726
1742
|
|
|
1727
|
-
return
|
|
1743
|
+
return undefined;
|
|
1728
1744
|
}
|
|
1729
1745
|
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
if (typeof text != 'string') {
|
|
1746
|
+
addText(text, { hyperlink = true, citation = true, latex = true, id, replace, insertAt } = {}) {
|
|
1747
|
+
if (typeof text !== 'string') {
|
|
1733
1748
|
throw new TypeError('Text must be a string');
|
|
1734
1749
|
}
|
|
1735
1750
|
|
|
@@ -1739,112 +1754,180 @@ class AIRich extends BaseBuilder {
|
|
|
1739
1754
|
latex,
|
|
1740
1755
|
});
|
|
1741
1756
|
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1757
|
+
const section = AIRich.newLayout('Single', {
|
|
1758
|
+
text: extractedText,
|
|
1759
|
+
...(inline_entities.length && { inline_entities }),
|
|
1760
|
+
__typename: 'GenAIMarkdownTextUXPrimitive',
|
|
1745
1761
|
});
|
|
1746
1762
|
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
__typename: 'GenAIMarkdownTextUXPrimitive',
|
|
1754
|
-
})
|
|
1755
|
-
);
|
|
1763
|
+
const submessages = [
|
|
1764
|
+
{
|
|
1765
|
+
messageType: 2,
|
|
1766
|
+
messageText: text,
|
|
1767
|
+
},
|
|
1768
|
+
].filter(Boolean);
|
|
1756
1769
|
|
|
1757
|
-
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
|
+
});
|
|
1758
1799
|
}
|
|
1759
1800
|
|
|
1760
|
-
|
|
1761
|
-
addCode(language, code) {
|
|
1801
|
+
addCode(language, code, { id, replace, insertAt } = {}) {
|
|
1762
1802
|
if (typeof language !== 'string' || typeof code !== 'string') {
|
|
1763
1803
|
throw new TypeError('Language and code must be a string');
|
|
1764
1804
|
}
|
|
1765
1805
|
|
|
1766
1806
|
const meta = AIRich.tokenizer(code, language);
|
|
1767
1807
|
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
codeBlocks: meta.codeBlock,
|
|
1773
|
-
},
|
|
1808
|
+
const section = AIRich.newLayout('Single', {
|
|
1809
|
+
language,
|
|
1810
|
+
code_blocks: meta.unified_codeBlock,
|
|
1811
|
+
__typename: 'GenAICodeUXPrimitive',
|
|
1774
1812
|
});
|
|
1775
1813
|
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1814
|
+
const submessages = [
|
|
1815
|
+
{
|
|
1816
|
+
messageType: 5,
|
|
1817
|
+
codeMetadata: {
|
|
1818
|
+
codeLanguage: language,
|
|
1819
|
+
codeBlocks: meta.codeBlock,
|
|
1820
|
+
},
|
|
1821
|
+
},
|
|
1822
|
+
];
|
|
1783
1823
|
|
|
1784
|
-
return this
|
|
1824
|
+
return this._addContent(section, submessages, {
|
|
1825
|
+
id,
|
|
1826
|
+
replace,
|
|
1827
|
+
insertAt,
|
|
1828
|
+
});
|
|
1785
1829
|
}
|
|
1786
1830
|
|
|
1787
|
-
|
|
1788
|
-
addTable(table, { hyperlink = true, citation = true, latex = true } = {}) {
|
|
1831
|
+
addTable(table, { hyperlink = true, citation = true, latex = true, id, replace, insertAt } = {}) {
|
|
1789
1832
|
if (!Array.isArray(table)) {
|
|
1790
1833
|
throw new TypeError('Table must be an array');
|
|
1791
1834
|
}
|
|
1792
1835
|
|
|
1793
|
-
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
|
+
});
|
|
1794
1846
|
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1847
|
+
const submessages = [
|
|
1848
|
+
{
|
|
1849
|
+
messageType: 4,
|
|
1850
|
+
tableMetadata: {
|
|
1851
|
+
title: meta.title,
|
|
1852
|
+
rows: meta.rows,
|
|
1853
|
+
},
|
|
1800
1854
|
},
|
|
1855
|
+
];
|
|
1856
|
+
|
|
1857
|
+
return this._addContent(section, submessages, {
|
|
1858
|
+
id,
|
|
1859
|
+
replace,
|
|
1860
|
+
insertAt,
|
|
1801
1861
|
});
|
|
1862
|
+
}
|
|
1802
1863
|
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
})
|
|
1808
|
-
);
|
|
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
|
+
}
|
|
1809
1868
|
|
|
1810
|
-
|
|
1811
|
-
|
|
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'));
|
|
1872
|
+
|
|
1873
|
+
const isObjectFormat = sources.every((item) => item && typeof item === 'object' && !Array.isArray(item));
|
|
1812
1874
|
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
if (!(Array.isArray(sources) && (sources.every((item) => typeof item === 'string') || sources.every((item) => Array.isArray(item) && item.every((v) => typeof v === 'string'))))) {
|
|
1816
|
-
throw new TypeError('Sources must be a string array or an array of string arrays');
|
|
1875
|
+
if (!isStringArray && !isArrayFormat && !isObjectFormat) {
|
|
1876
|
+
throw new TypeError('Sources must be a string array, array of string arrays, or array of objects');
|
|
1817
1877
|
}
|
|
1818
1878
|
|
|
1819
|
-
if (
|
|
1879
|
+
if (isStringArray) {
|
|
1820
1880
|
sources = [sources];
|
|
1821
1881
|
}
|
|
1822
1882
|
|
|
1823
|
-
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 }) => ({
|
|
1824
1904
|
source_type: 'THIRD_PARTY',
|
|
1825
|
-
source_display_name:
|
|
1826
|
-
source_subtitle:
|
|
1827
|
-
source_url: url
|
|
1905
|
+
source_display_name: title,
|
|
1906
|
+
source_subtitle: subtitle,
|
|
1907
|
+
source_url: url,
|
|
1828
1908
|
favicon: {
|
|
1829
|
-
url: Toolkit.resolveMedia(this.#client, icon
|
|
1909
|
+
url: Toolkit.resolveMedia(this.#client, icon, 'image'),
|
|
1830
1910
|
mime_type: 'image/jpeg',
|
|
1831
1911
|
width: 16,
|
|
1832
1912
|
height: 16,
|
|
1833
1913
|
},
|
|
1834
1914
|
}));
|
|
1835
1915
|
|
|
1836
|
-
this.
|
|
1837
|
-
AIRich.newLayout('Single', {
|
|
1838
|
-
sources: source,
|
|
1839
|
-
__typename: 'GenAISearchResultPrimitive',
|
|
1840
|
-
})
|
|
1841
|
-
);
|
|
1916
|
+
const submessage = this.createAlert('GenAISearchResultPrimitive');
|
|
1842
1917
|
|
|
1843
|
-
|
|
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
|
+
});
|
|
1844
1928
|
}
|
|
1845
1929
|
|
|
1846
|
-
|
|
1847
|
-
addReels(reelsItems = [], { resolveUrl = false } = {}) {
|
|
1930
|
+
addReels(reelsItems = [], { id, replace, insertAt } = {}) {
|
|
1848
1931
|
if (
|
|
1849
1932
|
!(
|
|
1850
1933
|
(reelsItems && typeof reelsItems === 'object' && !Array.isArray(reelsItems)) ||
|
|
@@ -1854,88 +1937,64 @@ class AIRich extends BaseBuilder {
|
|
|
1854
1937
|
throw new TypeError('Reels items must be an object or an array of objects');
|
|
1855
1938
|
}
|
|
1856
1939
|
|
|
1857
|
-
|
|
1858
|
-
reelsItems = [reelsItems];
|
|
1859
|
-
}
|
|
1940
|
+
const items = Array.isArray(reelsItems) ? reelsItems : [reelsItems];
|
|
1860
1941
|
|
|
1861
|
-
const reels =
|
|
1942
|
+
const reels = items.map((item) => ({
|
|
1862
1943
|
...item,
|
|
1863
|
-
_avatar: Toolkit.resolveMedia(this.#client, item.profileIconUrl ?? item.profile_url ?? item.profile ?? '', 'image'
|
|
1864
|
-
_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'),
|
|
1865
1946
|
}));
|
|
1866
1947
|
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
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
|
+
},
|
|
1879
1979
|
},
|
|
1880
|
-
|
|
1980
|
+
];
|
|
1881
1981
|
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
sourceProviderURL: item.videoUrl ?? item.url ?? '',
|
|
1887
|
-
sourceQuery: '',
|
|
1888
|
-
faviconCDNURL: item._avatar,
|
|
1889
|
-
citationNumber: idx + 1,
|
|
1890
|
-
sourceTitle: item.username ?? '',
|
|
1891
|
-
});
|
|
1982
|
+
return this._addContent(section, submessages, {
|
|
1983
|
+
id,
|
|
1984
|
+
replace,
|
|
1985
|
+
insertAt,
|
|
1892
1986
|
});
|
|
1893
|
-
|
|
1894
|
-
this._sections.push(
|
|
1895
|
-
AIRich.newLayout(
|
|
1896
|
-
'HScroll',
|
|
1897
|
-
reels.map((item) => ({
|
|
1898
|
-
reels_url: item.videoUrl ?? item.url ?? '',
|
|
1899
|
-
thumbnail_url: item._thumbnail,
|
|
1900
|
-
creator: item.username ?? item.title ?? '',
|
|
1901
|
-
avatar_url: item._avatar,
|
|
1902
|
-
reels_title: item.reels_title ?? item.title ?? '',
|
|
1903
|
-
likes_count: item.likes_count ?? item.like ?? 0,
|
|
1904
|
-
shares_count: item.shares_count ?? item.share ?? 0,
|
|
1905
|
-
view_count: item.view_count ?? item.view ?? 0,
|
|
1906
|
-
reel_source: item.reel_source ?? item.source ?? 'IG',
|
|
1907
|
-
is_verified: !!(item.is_verified || item.verified),
|
|
1908
|
-
__typename: 'GenAIReelPrimitive',
|
|
1909
|
-
}))
|
|
1910
|
-
)
|
|
1911
|
-
);
|
|
1912
|
-
|
|
1913
|
-
return this;
|
|
1914
1987
|
}
|
|
1915
1988
|
|
|
1916
|
-
|
|
1917
|
-
/**
|
|
1918
|
-
* @param {{ resolveUrl?: boolean, instant?: boolean|'only' }} [options]
|
|
1919
|
-
* `instant: true` — sends BOTH: the GRID_IMAGE card (still shows WA's "can't verify"
|
|
1920
|
-
* forwarded-download prompt, unavoidable per-design of botForwardedMessage) AND a plain
|
|
1921
|
-
* (non-forwarded) imageMessage via send()'s inline-image fallback queue (`_inlineImages`,
|
|
1922
|
-
* shared with addInlineImage()) that renders instantly with no prompt. Two images, by design.
|
|
1923
|
-
* `instant: 'only'` — Vanz@Add (v4.9.2): skips building the GRID_IMAGE card entirely (no
|
|
1924
|
-
* submessage, no GenAIImaginePrimitive section) and queues ONLY the plain imageMessage.
|
|
1925
|
-
* One image, no prompt, nothing to download — use this when you don't need the rich card,
|
|
1926
|
-
* just the picture to show up immediately.
|
|
1927
|
-
*/
|
|
1928
|
-
addImage(imageUrl, { resolveUrl = false, instant = false } = {}) {
|
|
1989
|
+
addImage(imageUrl, { width, height, status = 'READY', update_text, resolveUrl = false, id, replace, insertAt } = {}) {
|
|
1929
1990
|
if (!(typeof imageUrl === 'string' || Buffer.isBuffer(imageUrl) || (Array.isArray(imageUrl) && imageUrl.every((v) => typeof v === 'string' || Buffer.isBuffer(v))))) {
|
|
1930
1991
|
throw new TypeError('imageUrl must be string | buffer | array of string/buffer');
|
|
1931
1992
|
}
|
|
1932
|
-
if (instant !== false && instant !== true && instant !== 'only') {
|
|
1933
|
-
throw new TypeError(`instant must be false, true, or 'only' — got ${JSON.stringify(instant)}`);
|
|
1934
|
-
}
|
|
1935
1993
|
|
|
1936
1994
|
const list = Array.isArray(imageUrl)
|
|
1937
1995
|
? imageUrl.map((v) => {
|
|
1938
1996
|
const url = Toolkit.resolveMedia(this.#client, v, 'image', { resolveUrl });
|
|
1997
|
+
|
|
1939
1998
|
return {
|
|
1940
1999
|
imagePreviewUrl: url,
|
|
1941
2000
|
imageHighResUrl: url,
|
|
@@ -1944,6 +2003,7 @@ class AIRich extends BaseBuilder {
|
|
|
1944
2003
|
})
|
|
1945
2004
|
: (() => {
|
|
1946
2005
|
const url = Toolkit.resolveMedia(this.#client, imageUrl, 'image', { resolveUrl });
|
|
2006
|
+
|
|
1947
2007
|
return [
|
|
1948
2008
|
{
|
|
1949
2009
|
imagePreviewUrl: url,
|
|
@@ -1953,115 +2013,46 @@ class AIRich extends BaseBuilder {
|
|
|
1953
2013
|
];
|
|
1954
2014
|
})();
|
|
1955
2015
|
|
|
1956
|
-
const
|
|
1957
|
-
|
|
1958
|
-
if (buildCard) {
|
|
1959
|
-
this._submessages.push({
|
|
1960
|
-
messageType: 1,
|
|
1961
|
-
gridImageMetadata: {
|
|
1962
|
-
gridImageUrl: {
|
|
1963
|
-
imagePreviewUrl: list[0]?.imagePreviewUrl,
|
|
1964
|
-
},
|
|
1965
|
-
imageUrls: list,
|
|
1966
|
-
},
|
|
1967
|
-
});
|
|
1968
|
-
}
|
|
1969
|
-
|
|
1970
|
-
list.forEach(({ imagePreviewUrl }) => {
|
|
1971
|
-
if (buildCard) {
|
|
1972
|
-
this._sections.push(
|
|
1973
|
-
AIRich.newLayout('Single', {
|
|
1974
|
-
media: {
|
|
1975
|
-
url: imagePreviewUrl,
|
|
1976
|
-
mime_type: 'image/png',
|
|
1977
|
-
},
|
|
1978
|
-
imagine_type: 'IMAGE',
|
|
1979
|
-
status: { status: 'READY' },
|
|
1980
|
-
__typename: 'GenAIImaginePrimitive',
|
|
1981
|
-
})
|
|
1982
|
-
);
|
|
1983
|
-
}
|
|
1984
|
-
|
|
1985
|
-
if (instant) {
|
|
1986
|
-
this._inlineImages.push({ url: imagePreviewUrl, caption: undefined });
|
|
1987
|
-
}
|
|
1988
|
-
});
|
|
1989
|
-
|
|
1990
|
-
return this;
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
// Vanz@Fix 15-08-26 (bug 41) --- addImage() only builds GRID_IMAGE (messageType 1).
|
|
1994
|
-
// There was no helper for standalone INLINE_IMAGE (messageType 3): callers were manually
|
|
1995
|
-
// pushing addSubmessage() (correct proto shape) + addSection() (WRONG shape — reused the
|
|
1996
|
-
// GRID_IMAGE/GenAIImaginePrimitive section schema instead of GenAIInlineImageUXPrimitive),
|
|
1997
|
-
// which broke client-side unifiedResponse rendering even though the submessage itself was fine.
|
|
1998
|
-
// Mirrors RichSubMessageType.INLINE_IMAGE handling in rich-message-utils.js's toUnified().
|
|
1999
|
-
/** 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). */
|
|
2000
|
-
addInlineImage(imageUrl, { text = '', alignment = 'center', tapLinkUrl = '', resolveUrl = false } = {}) {
|
|
2001
|
-
if (!(typeof imageUrl === 'string' || Buffer.isBuffer(imageUrl) || (imageUrl && typeof imageUrl === 'object'))) {
|
|
2002
|
-
throw new TypeError('imageUrl must be string | buffer | { imagePreviewUrl, imageHighResUrl, sourceUrl }');
|
|
2003
|
-
}
|
|
2004
|
-
|
|
2005
|
-
const ALIGNMENT_ENUM = { leading: 0, trailing: 1, center: 2 };
|
|
2006
|
-
const ALIGNMENT_NAME = ['AI_RICH_RESPONSE_IMAGE_LAYOUT_LEADING_ALIGNED', 'AI_RICH_RESPONSE_IMAGE_LAYOUT_TRAILING_ALIGNED', 'AI_RICH_RESPONSE_IMAGE_LAYOUT_CENTER_ALIGNED'];
|
|
2007
|
-
const alignmentNum = typeof alignment === 'number' ? alignment : (ALIGNMENT_ENUM[String(alignment).toLowerCase()] ?? ALIGNMENT_ENUM.center);
|
|
2008
|
-
|
|
2009
|
-
const url =
|
|
2010
|
-
imageUrl && typeof imageUrl === 'object'
|
|
2011
|
-
? {
|
|
2012
|
-
imagePreviewUrl: imageUrl.imagePreviewUrl || imageUrl.url,
|
|
2013
|
-
imageHighResUrl: imageUrl.imageHighResUrl || imageUrl.url,
|
|
2014
|
-
sourceUrl: imageUrl.sourceUrl || imageUrl.url,
|
|
2015
|
-
}
|
|
2016
|
-
: (() => {
|
|
2017
|
-
const resolved = Toolkit.resolveMedia(this.#client, imageUrl, 'image', { resolveUrl });
|
|
2018
|
-
return { imagePreviewUrl: resolved, imageHighResUrl: resolved, sourceUrl: resolved };
|
|
2019
|
-
})();
|
|
2020
|
-
|
|
2021
|
-
this._submessages.push({
|
|
2022
|
-
messageType: 3,
|
|
2023
|
-
imageMetadata: {
|
|
2024
|
-
imageUrl: url,
|
|
2025
|
-
imageText: text,
|
|
2026
|
-
alignment: alignmentNum,
|
|
2027
|
-
tapLinkUrl,
|
|
2028
|
-
},
|
|
2029
|
-
});
|
|
2030
|
-
|
|
2031
|
-
this._sections.push(
|
|
2016
|
+
const sections = list.map(({ imagePreviewUrl }) =>
|
|
2032
2017
|
AIRich.newLayout('Single', {
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2018
|
+
media: {
|
|
2019
|
+
url: imagePreviewUrl,
|
|
2020
|
+
mime_type: 'image/png',
|
|
2021
|
+
width,
|
|
2022
|
+
height,
|
|
2023
|
+
},
|
|
2024
|
+
imagine_type: 'IMAGE',
|
|
2025
|
+
status: {
|
|
2026
|
+
status,
|
|
2027
|
+
update_text,
|
|
2037
2028
|
},
|
|
2038
|
-
|
|
2039
|
-
alignment: ALIGNMENT_NAME[alignmentNum],
|
|
2040
|
-
tap_link_url: tapLinkUrl,
|
|
2041
|
-
__typename: 'GenAIInlineImageUXPrimitive',
|
|
2029
|
+
__typename: 'GenAIImaginePrimitive',
|
|
2042
2030
|
})
|
|
2043
2031
|
);
|
|
2044
2032
|
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2033
|
+
const submessage = {
|
|
2034
|
+
messageType: 1,
|
|
2035
|
+
gridImageMetadata: {
|
|
2036
|
+
gridImageUrl: {
|
|
2037
|
+
imagePreviewUrl: list[0]?.imagePreviewUrl,
|
|
2038
|
+
},
|
|
2039
|
+
imageUrls: list,
|
|
2040
|
+
},
|
|
2041
|
+
};
|
|
2050
2042
|
|
|
2051
|
-
|
|
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
|
+
});
|
|
2052
2052
|
}
|
|
2053
2053
|
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
// was the main source of blurose's slower response time. Pass { autoFill: true } to opt
|
|
2057
|
-
// back into the complete/slow path (real thumbnail + duration + file_length).
|
|
2058
|
-
// Vanz@Fix 23-08-26 --- addVideo() had no resolveUrl option at all (unlike addImage()), so the
|
|
2059
|
-
// video url always stayed a raw external link, which stock WA clients show a "download" state
|
|
2060
|
-
// for before rendering. Mirrors addImage()'s { resolveUrl } — when true, the url is uploaded to
|
|
2061
|
-
// WA's own media server first via Toolkit.toUrl() so it renders instantly like WA-native media.
|
|
2062
|
-
/** Add a video block. */
|
|
2063
|
-
addVideo(videoUrl, { autoFill = false, resolveUrl = false } = {}) {
|
|
2064
|
-
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;
|
|
2065
2056
|
|
|
2066
2057
|
const isValidPrimitive =
|
|
2067
2058
|
typeof videoUrl === 'string' ||
|
|
@@ -2075,17 +2066,15 @@ class AIRich extends BaseBuilder {
|
|
|
2075
2066
|
|
|
2076
2067
|
const items = Array.isArray(videoUrl) ? videoUrl : [videoUrl];
|
|
2077
2068
|
|
|
2078
|
-
this.
|
|
2079
|
-
messageType: 2,
|
|
2080
|
-
messageText: '[ Video tidak dapat dimuat ]',
|
|
2081
|
-
});
|
|
2069
|
+
const alert = this.createAlert('GenAIImaginePrimitive (ANIMATE)');
|
|
2082
2070
|
|
|
2083
|
-
|
|
2071
|
+
const sections = [];
|
|
2072
|
+
const submessages = [];
|
|
2073
|
+
|
|
2074
|
+
for (const item of items) {
|
|
2084
2075
|
const isObject = isObjectVideo(item);
|
|
2085
2076
|
|
|
2086
|
-
const url = isObject
|
|
2087
|
-
? Toolkit.resolveMedia(this.#client, item.url ?? '', 'video', { resolveUrl })
|
|
2088
|
-
: Toolkit.resolveMedia(this.#client, item, 'video', { resolveUrl });
|
|
2077
|
+
const url = isObject ? Toolkit.resolveMedia(this.#client, item.url ?? '', 'video') : Toolkit.resolveMedia(this.#client, item, 'video');
|
|
2089
2078
|
|
|
2090
2079
|
const bufferPromise = autoFill ? Promise.resolve(url).then((u) => Toolkit.fetchBuffer(u)) : null;
|
|
2091
2080
|
|
|
@@ -2111,17 +2100,15 @@ class AIRich extends BaseBuilder {
|
|
|
2111
2100
|
height: 300,
|
|
2112
2101
|
})
|
|
2113
2102
|
: autoFill
|
|
2114
|
-
? bufferPromise
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
)
|
|
2121
|
-
: null
|
|
2103
|
+
? bufferPromise?.then((b) =>
|
|
2104
|
+
Toolkit.getMp4Preview(b, {
|
|
2105
|
+
time: 0,
|
|
2106
|
+
result: 'base64',
|
|
2107
|
+
})
|
|
2108
|
+
)
|
|
2122
2109
|
: null;
|
|
2123
2110
|
|
|
2124
|
-
|
|
2111
|
+
sections.push(
|
|
2125
2112
|
AIRich.newLayout('Single', {
|
|
2126
2113
|
media: {
|
|
2127
2114
|
url,
|
|
@@ -2130,34 +2117,37 @@ class AIRich extends BaseBuilder {
|
|
|
2130
2117
|
duration,
|
|
2131
2118
|
},
|
|
2132
2119
|
imagine_type: 'ANIMATE',
|
|
2133
|
-
status: {
|
|
2120
|
+
status: {
|
|
2121
|
+
status,
|
|
2122
|
+
estimated_completion_time: estimatedTime != null ? Math.floor((Date.now() + estimatedTime) / 1000) : undefined,
|
|
2123
|
+
},
|
|
2134
2124
|
thumbnail: {
|
|
2135
2125
|
raw_media: thumbnail,
|
|
2136
2126
|
},
|
|
2137
2127
|
__typename: 'GenAIImaginePrimitive',
|
|
2138
2128
|
})
|
|
2139
2129
|
);
|
|
2140
|
-
}
|
|
2141
|
-
|
|
2142
|
-
return this;
|
|
2143
|
-
}
|
|
2130
|
+
}
|
|
2144
2131
|
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
2148
|
-
throw new TypeError('Product items must be an object or an array of objects');
|
|
2132
|
+
if (alert !== undefined) {
|
|
2133
|
+
submessages.push(alert);
|
|
2149
2134
|
}
|
|
2150
2135
|
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
if (missingTitleAt !== -1) {
|
|
2154
|
-
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');
|
|
2155
2138
|
}
|
|
2156
2139
|
|
|
2157
|
-
this.
|
|
2158
|
-
|
|
2159
|
-
|
|
2140
|
+
return this._addContent(sections, submessages[0], {
|
|
2141
|
+
id,
|
|
2142
|
+
replace,
|
|
2143
|
+
insertAt,
|
|
2160
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
|
+
}
|
|
2161
2151
|
|
|
2162
2152
|
const items = Array.isArray(data) ? data : [data];
|
|
2163
2153
|
|
|
@@ -2168,41 +2158,41 @@ class AIRich extends BaseBuilder {
|
|
|
2168
2158
|
sale_price: item.sale_price,
|
|
2169
2159
|
product_url: item.product_url ?? item.url,
|
|
2170
2160
|
image: {
|
|
2171
|
-
url: Toolkit.resolveMedia(this.#client, item.image_url ?? item.image, 'image'
|
|
2161
|
+
url: Toolkit.resolveMedia(this.#client, item.image_url ?? item.image, 'image'),
|
|
2172
2162
|
},
|
|
2173
2163
|
additional_images: [
|
|
2174
2164
|
{
|
|
2175
|
-
url: Toolkit.resolveMedia(this.#client, item.icon_url ?? item.icon, 'image'
|
|
2165
|
+
url: Toolkit.resolveMedia(this.#client, item.icon_url ?? item.icon, 'image'),
|
|
2176
2166
|
},
|
|
2177
2167
|
],
|
|
2178
2168
|
__typename: 'GenAIProductItemCardPrimitive',
|
|
2179
2169
|
}));
|
|
2180
2170
|
|
|
2181
|
-
|
|
2171
|
+
const section = AIRich.newLayout(Array.isArray(data) ? 'HScroll' : 'Single', Array.isArray(data) ? product : product[0]);
|
|
2182
2172
|
|
|
2183
|
-
|
|
2173
|
+
const submessage = this.createAlert('GenAIProductItemCardPrimitive');
|
|
2174
|
+
|
|
2175
|
+
return this._addContent(section, submessage, {
|
|
2176
|
+
id,
|
|
2177
|
+
replace,
|
|
2178
|
+
insertAt,
|
|
2179
|
+
});
|
|
2184
2180
|
}
|
|
2185
2181
|
|
|
2186
|
-
|
|
2187
|
-
addPost(data = {}, { resolveUrl = false } = {}) {
|
|
2182
|
+
addPost(data = {}, { id, replace, insertAt } = {}) {
|
|
2188
2183
|
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
2189
2184
|
throw new TypeError('Post items must be an object or an array of objects');
|
|
2190
2185
|
}
|
|
2191
2186
|
|
|
2192
2187
|
const posts = Array.isArray(data) ? data : [data];
|
|
2193
2188
|
|
|
2194
|
-
this._submessages.push({
|
|
2195
|
-
messageType: 2,
|
|
2196
|
-
messageText: '[ Postingan tidak dapat dimuat ]',
|
|
2197
|
-
});
|
|
2198
|
-
|
|
2199
2189
|
const primitives = posts.map((p) => ({
|
|
2200
2190
|
title: p.title ?? '',
|
|
2201
2191
|
subtitle: p.subtitle ?? '',
|
|
2202
2192
|
username: p.username ?? '',
|
|
2203
|
-
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'),
|
|
2204
2194
|
is_verified: !!(p.is_verified || p.verified),
|
|
2205
|
-
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'),
|
|
2206
2196
|
post_caption: p.post_caption ?? p.caption ?? '',
|
|
2207
2197
|
likes_count: p.likes_count ?? p.like ?? 0,
|
|
2208
2198
|
comments_count: p.comments_count ?? p.comment ?? 0,
|
|
@@ -2211,419 +2201,151 @@ class AIRich extends BaseBuilder {
|
|
|
2211
2201
|
post_deeplink: p.post_deeplink ?? p.deeplink ?? '',
|
|
2212
2202
|
source_app: p.source_app || p.source || 'INSTAGRAM',
|
|
2213
2203
|
footer_label: p.footer_label ?? p.footer ?? '',
|
|
2214
|
-
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'),
|
|
2215
2205
|
is_carousel: posts.length > 1,
|
|
2216
2206
|
orientation: p.orientation ?? 'LANDSCAPE',
|
|
2217
2207
|
post_type: p.post_type ?? 'VIDEO',
|
|
2218
2208
|
__typename: 'GenAIPostPrimitive',
|
|
2219
2209
|
}));
|
|
2220
2210
|
|
|
2221
|
-
|
|
2211
|
+
const section = AIRich.newLayout('HScroll', primitives);
|
|
2222
2212
|
|
|
2223
|
-
|
|
2213
|
+
const submessage = this.createAlert('GenAIPostPrimitive');
|
|
2214
|
+
|
|
2215
|
+
return this._addContent(section, submessage, {
|
|
2216
|
+
id,
|
|
2217
|
+
replace,
|
|
2218
|
+
insertAt,
|
|
2219
|
+
});
|
|
2224
2220
|
}
|
|
2225
2221
|
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2222
|
+
addMetadata(text, { id, replace, insertAt } = {}) {
|
|
2223
|
+
if (typeof text !== 'string') {
|
|
2224
|
+
throw new TypeError('Text must be a string');
|
|
2225
|
+
}
|
|
2230
2226
|
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
return this;
|
|
2236
|
-
}
|
|
2227
|
+
const section = AIRich.newLayout('Single', {
|
|
2228
|
+
text,
|
|
2229
|
+
__typename: 'GenAIMetadataTextPrimitive',
|
|
2230
|
+
});
|
|
2237
2231
|
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
}
|
|
2232
|
+
const submessage = {
|
|
2233
|
+
messageType: 2,
|
|
2234
|
+
messageText: text,
|
|
2235
|
+
};
|
|
2243
2236
|
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2237
|
+
return this._addContent(section, submessage, {
|
|
2238
|
+
id,
|
|
2239
|
+
replace,
|
|
2240
|
+
insertAt,
|
|
2241
|
+
});
|
|
2249
2242
|
}
|
|
2250
2243
|
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
}
|
|
2244
|
+
addTip(text, { id, replace, insertAt } = {}) {
|
|
2245
|
+
if (typeof text !== 'string') {
|
|
2246
|
+
throw new TypeError('Text must be a string');
|
|
2247
|
+
}
|
|
2256
2248
|
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2249
|
+
const section = AIRich.newLayout('Single', {
|
|
2250
|
+
text: 'ⓘ ' + text,
|
|
2251
|
+
__typename: 'GenAIMetadataTextPrimitive',
|
|
2252
|
+
});
|
|
2260
2253
|
|
|
2261
|
-
|
|
2254
|
+
const submessage = {
|
|
2262
2255
|
messageType: 2,
|
|
2263
2256
|
messageText: text,
|
|
2264
|
-
}
|
|
2265
|
-
|
|
2266
|
-
this._sections.push(
|
|
2267
|
-
AIRich.newLayout('Single', {
|
|
2268
|
-
text,
|
|
2269
|
-
__typename: 'GenAIMetadataTextPrimitive',
|
|
2270
|
-
})
|
|
2271
|
-
);
|
|
2272
|
-
|
|
2273
|
-
return this;
|
|
2274
|
-
}
|
|
2275
|
-
|
|
2276
|
-
/** Add a small "tip" callout banner. @param {string} text */
|
|
2277
|
-
addTip(text) {
|
|
2278
|
-
if (typeof text !== 'string' || !text) {
|
|
2279
|
-
throw new TypeError('addTip(text) requires a non-empty string');
|
|
2280
|
-
}
|
|
2257
|
+
};
|
|
2281
2258
|
|
|
2282
|
-
this.
|
|
2283
|
-
|
|
2284
|
-
|
|
2259
|
+
return this._addContent(section, submessage, {
|
|
2260
|
+
id,
|
|
2261
|
+
replace,
|
|
2262
|
+
insertAt,
|
|
2285
2263
|
});
|
|
2286
|
-
|
|
2287
|
-
this._sections.push(
|
|
2288
|
-
AIRich.newLayout('Single', {
|
|
2289
|
-
text,
|
|
2290
|
-
__typename: 'GenAIMetadataTextPrimitive',
|
|
2291
|
-
})
|
|
2292
|
-
);
|
|
2293
|
-
|
|
2294
|
-
return this;
|
|
2295
2264
|
}
|
|
2296
2265
|
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
// unknown enum values (kind/state on addWidget's ctas) are passed through as observed rather
|
|
2301
|
-
// than guessed at, and documented as experimental below.
|
|
2302
|
-
|
|
2303
|
-
/** Add a large heading-style text block (`FOATextPrimitive`) — visually distinct from `addText()`'s regular paragraph text. */
|
|
2304
|
-
addHeading(text) {
|
|
2305
|
-
if (typeof text !== 'string' || !text) {
|
|
2306
|
-
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');
|
|
2307
2269
|
}
|
|
2308
2270
|
|
|
2309
|
-
|
|
2310
|
-
messageType: 2,
|
|
2311
|
-
messageText: text,
|
|
2312
|
-
});
|
|
2313
|
-
|
|
2314
|
-
this._sections.push(
|
|
2315
|
-
AIRich.newLayout('Single', {
|
|
2316
|
-
text,
|
|
2317
|
-
__typename: 'FOATextPrimitive',
|
|
2318
|
-
})
|
|
2319
|
-
);
|
|
2320
|
-
|
|
2321
|
-
return this;
|
|
2322
|
-
}
|
|
2271
|
+
const isArray = Array.isArray(data);
|
|
2323
2272
|
|
|
2324
|
-
|
|
2325
|
-
* Add a "ready" static image card (`GenAIImagePrimitive`: preview + full-res, no generating/status
|
|
2326
|
-
* state) — distinct from `addImage()`'s AI-generation-style `GenAIImaginePrimitive`.
|
|
2327
|
-
* @param {string|Buffer} previewUrl Preview/thumbnail image.
|
|
2328
|
-
* @param {string|Buffer} [fullUrl] Full-resolution image; defaults to `previewUrl`.
|
|
2329
|
-
*/
|
|
2330
|
-
addImageCard(previewUrl, fullUrl = previewUrl, { resolveUrl = false } = {}) {
|
|
2331
|
-
if (!(typeof previewUrl === 'string' || Buffer.isBuffer(previewUrl))) {
|
|
2332
|
-
throw new TypeError('addImageCard(previewUrl) requires a string url or buffer');
|
|
2333
|
-
}
|
|
2273
|
+
const items = isArray ? data : [data];
|
|
2334
2274
|
|
|
2335
|
-
const
|
|
2336
|
-
|
|
2275
|
+
const widgets = items.map((item) => ({
|
|
2276
|
+
__typename: 'GenAI3PExtWidgetPrimitive',
|
|
2337
2277
|
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
imageUrls: [{ imagePreviewUrl: preview, imageHighResUrl: full, sourceUrl: full }],
|
|
2278
|
+
header: {
|
|
2279
|
+
__typename: 'GenAI3PExtWidgetStandardHeader',
|
|
2280
|
+
title: item.title ?? '',
|
|
2281
|
+
...(item.header ?? {}),
|
|
2343
2282
|
},
|
|
2344
|
-
});
|
|
2345
|
-
|
|
2346
|
-
this._sections.push(
|
|
2347
|
-
AIRich.newLayout('Single', {
|
|
2348
|
-
preview_image: { url: preview, mime_type: 'image/jpeg', __typename: 'GenAIMediaItem' },
|
|
2349
|
-
full_image: { url: full, mime_type: 'image/jpeg', __typename: 'GenAIMediaItem' },
|
|
2350
|
-
__typename: 'GenAIImagePrimitive',
|
|
2351
|
-
})
|
|
2352
|
-
);
|
|
2353
2283
|
|
|
2354
|
-
|
|
2355
|
-
|
|
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
|
+
})),
|
|
2356
2302
|
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
* rather than opening a url; `kind`/`state` semantics beyond the observed `'OTHER'`/`'PENDING'`
|
|
2361
|
-
* defaults aren't publicly documented, so treat this as experimental.
|
|
2362
|
-
*
|
|
2363
|
-
* Vanz@Add (v4.8) --- accepts an `{ layout }` override so consecutive `addWidget()` calls can
|
|
2364
|
-
* pick different renderings (e.g. one `HScroll` row, one `ActionRow` stack) instead of always
|
|
2365
|
-
* inferring HScroll-for-array/Single-for-object from the shape of `data`. Also accepts either
|
|
2366
|
-
* `ctas` (original key, matches the wire field) or `actions` (alias) on each item — whichever
|
|
2367
|
-
* is present is used; `ctas` wins if both are somehow given.
|
|
2368
|
-
* @param {Record<string, any>|Record<string, any>[]} data `{ title, ctas|actions: [{ label, tool_call_id?, kind?, state?, toast? }] }` (single or array).
|
|
2369
|
-
* @param {{layout?: 'Single'|'HScroll'|'ActionRow'|string}} [options] `layout` overrides the default single/array inference.
|
|
2370
|
-
*/
|
|
2371
|
-
addWidget(data = {}, { layout } = {}) {
|
|
2372
|
-
const items = Array.isArray(data) ? data : [data];
|
|
2303
|
+
...(item.body ?? {}),
|
|
2304
|
+
},
|
|
2305
|
+
}));
|
|
2373
2306
|
|
|
2374
|
-
|
|
2375
|
-
if (!item?.title) {
|
|
2376
|
-
throw new TypeError(`addWidget() item[${i}] is missing a required "title"`);
|
|
2377
|
-
}
|
|
2378
|
-
const ctas = item.ctas ?? item.actions;
|
|
2379
|
-
if (!Array.isArray(ctas) || !ctas.length) {
|
|
2380
|
-
throw new TypeError(`addWidget() item[${i}] requires a non-empty "ctas" (or "actions") array`);
|
|
2381
|
-
}
|
|
2382
|
-
});
|
|
2307
|
+
const section = AIRich.newLayout(layout ?? (isArray ? 'HScroll' : 'Single'), isArray ? widgets : widgets[0], options);
|
|
2383
2308
|
|
|
2384
|
-
this.
|
|
2385
|
-
messageType: 2,
|
|
2386
|
-
messageText: items.map((item) => item.title).join(', '),
|
|
2387
|
-
});
|
|
2309
|
+
const submessage = this.createAlert('GenAI3PExtWidgetStandardHeader');
|
|
2388
2310
|
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
body: {
|
|
2394
|
-
sections: item.sections ?? [],
|
|
2395
|
-
ctas: ctas.map((cta, idx) => ({
|
|
2396
|
-
label: cta.label ?? '',
|
|
2397
|
-
state: cta.state ?? 'PENDING',
|
|
2398
|
-
kind: cta.kind ?? 'OTHER',
|
|
2399
|
-
tool_call_id: cta.tool_call_id ?? String(idx).padStart(2, '0'),
|
|
2400
|
-
...(cta.toast !== false && {
|
|
2401
|
-
toast: { label: typeof cta.toast === 'string' ? cta.toast : item.title, __typename: 'GenAI3PExtWidgetToast' },
|
|
2402
|
-
}),
|
|
2403
|
-
__typename: 'GenAI3PExtWidgetCTA',
|
|
2404
|
-
})),
|
|
2405
|
-
__typename: item.body_typename ?? 'GenAI3PExtCalendarEventList',
|
|
2406
|
-
},
|
|
2407
|
-
__typename: 'GenAI3PExtWidgetPrimitive',
|
|
2408
|
-
};
|
|
2311
|
+
return this._addContent(section, submessage, {
|
|
2312
|
+
id,
|
|
2313
|
+
replace,
|
|
2314
|
+
insertAt,
|
|
2409
2315
|
});
|
|
2410
|
-
|
|
2411
|
-
const resolvedLayout = layout ?? (Array.isArray(data) ? 'HScroll' : 'Single');
|
|
2412
|
-
const asArray = resolvedLayout !== 'Single';
|
|
2413
|
-
|
|
2414
|
-
this._sections.push(AIRich.newLayout(resolvedLayout, asArray ? widgets : widgets[0]));
|
|
2415
|
-
|
|
2416
|
-
return this;
|
|
2417
2316
|
}
|
|
2418
2317
|
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
*/
|
|
2424
|
-
addFooterAction(actions) {
|
|
2425
|
-
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
|
+
}
|
|
2426
2322
|
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
}
|
|
2431
|
-
});
|
|
2323
|
+
const isArray = Array.isArray(data);
|
|
2324
|
+
|
|
2325
|
+
const items = isArray ? data : [data];
|
|
2432
2326
|
|
|
2433
|
-
const
|
|
2434
|
-
cta_text: item.text,
|
|
2435
|
-
cta_type: item.type ?? 'OPEN_URL',
|
|
2436
|
-
cta_url: item.url,
|
|
2327
|
+
const actions = items.map((item) => ({
|
|
2437
2328
|
__typename: 'GenAIFooterActionPrimitive',
|
|
2438
|
-
}));
|
|
2439
2329
|
|
|
2440
|
-
|
|
2330
|
+
cta_text: item.text ?? item.cta_text ?? '',
|
|
2441
2331
|
|
|
2442
|
-
|
|
2443
|
-
}
|
|
2332
|
+
cta_type: item.type ?? item.cta_type ?? 'OPEN_URL',
|
|
2444
2333
|
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
// have no dedicated AIRichResponseSubMessageType — WA carries them purely in the
|
|
2448
|
-
// unifiedResponse view-model JSON, so their submessage falls back to plain AI_RICH_RESPONSE_TEXT
|
|
2449
|
-
// like addTip/addHeading already do. Latex is the one exception: it has a real proto type
|
|
2450
|
-
// (AI_RICH_RESPONSE_LATEX = 8, confirmed in WAProto) with its own latexMetadata, so that one
|
|
2451
|
-
// gets a proper submessage instead of the text fallback.
|
|
2334
|
+
cta_url: item.url ?? item.cta_url ?? '',
|
|
2335
|
+
}));
|
|
2452
2336
|
|
|
2453
|
-
|
|
2454
|
-
addDivider() {
|
|
2455
|
-
this._submessages.push({ messageType: 2, messageText: '---' });
|
|
2456
|
-
this._sections.push(AIRich.newLayout('Single', { __typename: 'GenAIDividerPrimitive' }));
|
|
2457
|
-
return this;
|
|
2458
|
-
}
|
|
2337
|
+
const section = AIRich.newLayout(layout ?? (isArray ? 'HScroll' : 'Single'), isArray ? actions : actions[0], options);
|
|
2459
2338
|
|
|
2460
|
-
|
|
2461
|
-
addSpacer(spacing = 1) {
|
|
2462
|
-
if (typeof spacing !== 'number' || spacing < 0) {
|
|
2463
|
-
throw new TypeError('addSpacer(spacing) requires a non-negative number');
|
|
2464
|
-
}
|
|
2465
|
-
this._submessages.push({ messageType: 2, messageText: `spasi ${spacing}` });
|
|
2466
|
-
this._sections.push(AIRich.newLayout('Single', { spacing, __typename: 'GenAISpacerPrimitive' }));
|
|
2467
|
-
return this;
|
|
2468
|
-
}
|
|
2339
|
+
const submessage = this.createAlert('GenAIFooterActionPrimitive');
|
|
2469
2340
|
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
*/
|
|
2475
|
-
addLatex(expression) {
|
|
2476
|
-
if (typeof expression !== 'string' || !expression) {
|
|
2477
|
-
throw new TypeError('addLatex(expression) requires a non-empty string');
|
|
2478
|
-
}
|
|
2479
|
-
this._submessages.push({
|
|
2480
|
-
messageType: 8,
|
|
2481
|
-
latexMetadata: { text: expression, expressions: [{ latexExpression: expression }] },
|
|
2341
|
+
return this._addContent(section, submessage, {
|
|
2342
|
+
id,
|
|
2343
|
+
replace,
|
|
2344
|
+
insertAt,
|
|
2482
2345
|
});
|
|
2483
|
-
this._sections.push(AIRich.newLayout('Single', { latex_expression: expression, __typename: 'GenAILatexUXPrimitive' }));
|
|
2484
|
-
return this;
|
|
2485
|
-
}
|
|
2486
|
-
|
|
2487
|
-
/**
|
|
2488
|
-
* Add a task/checklist card (`GenAITaskPrimitive`).
|
|
2489
|
-
* @param {{task_id?: string, title: string, subtitle?: string, status?: string}} data
|
|
2490
|
-
*/
|
|
2491
|
-
addTask(data = {}) {
|
|
2492
|
-
if (!data?.title) {
|
|
2493
|
-
throw new TypeError('addTask() requires a "title"');
|
|
2494
|
-
}
|
|
2495
|
-
this._submessages.push({ messageType: 2, messageText: `Tugas: ${data.title}` });
|
|
2496
|
-
this._sections.push(
|
|
2497
|
-
AIRich.newLayout('Single', {
|
|
2498
|
-
task_id: data.task_id ?? '',
|
|
2499
|
-
title: data.title,
|
|
2500
|
-
subtitle: data.subtitle ?? '',
|
|
2501
|
-
status: data.status ?? 'IN_PROGRESS',
|
|
2502
|
-
__typename: 'GenAITaskPrimitive',
|
|
2503
|
-
})
|
|
2504
|
-
);
|
|
2505
|
-
// Safety net: GenAITaskPrimitive is a custom AI-only component the stock WA client
|
|
2506
|
-
// doesn't render visibly. Append a plain text section so the task is still visible.
|
|
2507
|
-
// Set data.textFallback = false to skip.
|
|
2508
|
-
if (data.textFallback !== false) {
|
|
2509
|
-
const fallbackText = data.subtitle ? `${data.title} — ${data.subtitle}` : data.title;
|
|
2510
|
-
this._sections.push(AIRich.newLayout('Single', { text: `Tugas: ${fallbackText}`, __typename: 'FOATextPrimitive' }));
|
|
2511
|
-
}
|
|
2512
|
-
return this;
|
|
2513
2346
|
}
|
|
2514
2347
|
|
|
2515
|
-
|
|
2516
|
-
* Add a "searching/working" progress banner (`GenAIBotProgressStatusPrimitive`) — a one-shot
|
|
2517
|
-
* status chip (unlike `addSuggest`, this isn't tappable). Distinct from `addThinkingStatus()`'s
|
|
2518
|
-
* icon/typename.
|
|
2519
|
-
* @param {string} title
|
|
2520
|
-
* @param {{icon?: string, is_in_progress?: boolean}} [options]
|
|
2521
|
-
*/
|
|
2522
|
-
addProgressStatus(title, { icon = 'SEARCH', is_in_progress = true, target_secondary_screen_id, target_secondary_screen_tab_id } = {}) {
|
|
2523
|
-
if (typeof title !== 'string' || !title) {
|
|
2524
|
-
throw new TypeError('addProgressStatus(title) requires a non-empty string');
|
|
2525
|
-
}
|
|
2526
|
-
this._submessages.push({ messageType: 2, messageText: title });
|
|
2527
|
-
const primitive = {
|
|
2528
|
-
title,
|
|
2529
|
-
icon,
|
|
2530
|
-
is_in_progress,
|
|
2531
|
-
meta_search_apps: [],
|
|
2532
|
-
__typename: 'GenAIBotProgressStatusPrimitive',
|
|
2533
|
-
};
|
|
2534
|
-
// NOTE: these two fields must be OMITTED when unset, not sent as `null` —
|
|
2535
|
-
// an explicit null here was reproducibly crashing the WA client renderer
|
|
2536
|
-
// on group-open/media-download. Only include when the caller actually passes one.
|
|
2537
|
-
if (target_secondary_screen_id != null) primitive.target_secondary_screen_id = target_secondary_screen_id;
|
|
2538
|
-
if (target_secondary_screen_tab_id != null) primitive.target_secondary_screen_tab_id = target_secondary_screen_tab_id;
|
|
2539
|
-
this._sections.push(AIRich.newLayout('Single', primitive));
|
|
2540
|
-
return this;
|
|
2541
|
-
}
|
|
2542
|
-
|
|
2543
|
-
/** Add a "thinking" status banner (`GenAIBotThinkingStatusPrimitive`). See `addProgressStatus()`. */
|
|
2544
|
-
addThinkingStatus(title, { icon = 'THINKING', is_in_progress = true, target_secondary_screen_id, target_secondary_screen_tab_id, textFallback = true } = {}) {
|
|
2545
|
-
if (typeof title !== 'string' || !title) {
|
|
2546
|
-
throw new TypeError('addThinkingStatus(title) requires a non-empty string');
|
|
2547
|
-
}
|
|
2548
|
-
this._submessages.push({ messageType: 2, messageText: title });
|
|
2549
|
-
const primitive = {
|
|
2550
|
-
title,
|
|
2551
|
-
icon,
|
|
2552
|
-
is_in_progress,
|
|
2553
|
-
meta_search_apps: [],
|
|
2554
|
-
__typename: 'GenAIBotThinkingStatusPrimitive',
|
|
2555
|
-
};
|
|
2556
|
-
// Same crash-avoidance rule as addProgressStatus(): omit, never null.
|
|
2557
|
-
if (target_secondary_screen_id != null) primitive.target_secondary_screen_id = target_secondary_screen_id;
|
|
2558
|
-
if (target_secondary_screen_tab_id != null) primitive.target_secondary_screen_tab_id = target_secondary_screen_tab_id;
|
|
2559
|
-
this._sections.push(AIRich.newLayout('Single', primitive));
|
|
2560
|
-
// Safety net: stock WA client doesn't render this primitive's own view (it's meant
|
|
2561
|
-
// as a transient spinner in the official app), so the card shows blank when forwarded.
|
|
2562
|
-
// Append a plain text section so the title is still visible. Set { textFallback: false } to skip.
|
|
2563
|
-
if (textFallback) {
|
|
2564
|
-
this._sections.push(AIRich.newLayout('Single', { text: title, __typename: 'FOATextPrimitive' }));
|
|
2565
|
-
}
|
|
2566
|
-
return this;
|
|
2567
|
-
}
|
|
2568
|
-
|
|
2569
|
-
/**
|
|
2570
|
-
* Add a subscription-quota-limit upsell card (`GenAIMetaSubsQuotaUpsellPrimitive`).
|
|
2571
|
-
* @param {{title: string, body?: string, body_line1?: string, body_line2?: string, buttons?: {label: string, action?: string, deeplink?: string}[]}} data
|
|
2572
|
-
*/
|
|
2573
|
-
addQuotaUpsell(data = {}) {
|
|
2574
|
-
if (!data?.title) {
|
|
2575
|
-
throw new TypeError('addQuotaUpsell() requires a "title"');
|
|
2576
|
-
}
|
|
2577
|
-
this._submessages.push({ messageType: 2, messageText: data.title });
|
|
2578
|
-
this._sections.push(
|
|
2579
|
-
AIRich.newLayout('Single', {
|
|
2580
|
-
title: data.title,
|
|
2581
|
-
body: data.body ?? '',
|
|
2582
|
-
body_line1: data.body_line1 ?? '',
|
|
2583
|
-
body_line2: data.body_line2 ?? '',
|
|
2584
|
-
buttons: (data.buttons ?? []).map((b) => ({
|
|
2585
|
-
label: b.label ?? '',
|
|
2586
|
-
action: b.action ?? 'OPEN_DEEPLINK',
|
|
2587
|
-
deeplink: b.deeplink ?? '',
|
|
2588
|
-
})),
|
|
2589
|
-
__typename: 'GenAIMetaSubsQuotaUpsellPrimitive',
|
|
2590
|
-
})
|
|
2591
|
-
);
|
|
2592
|
-
return this;
|
|
2593
|
-
}
|
|
2594
|
-
|
|
2595
|
-
/**
|
|
2596
|
-
* Add a raw Bloks payload (`FOABloksPrimitive`) — Meta's internal UI-description format.
|
|
2597
|
-
* Escape hatch: field meaning beyond what's passed through is undocumented, so this is the
|
|
2598
|
-
* most experimental primitive in this block; pass whatever your captured traffic shows.
|
|
2599
|
-
* @param {{type: string, data: string, uuid?: string, initial_response?: any, versioning_id?: string}} data
|
|
2600
|
-
*/
|
|
2601
|
-
addBloks(data = {}) {
|
|
2602
|
-
if (!data?.type) {
|
|
2603
|
-
throw new TypeError('addBloks() requires a "type"');
|
|
2604
|
-
}
|
|
2605
|
-
this._submessages.push({ messageType: 2, messageText: 'Bloks' });
|
|
2606
|
-
const primitive = {
|
|
2607
|
-
type: data.type,
|
|
2608
|
-
data: data.data ?? '{}',
|
|
2609
|
-
uuid: data.uuid ?? '',
|
|
2610
|
-
versioning_id: data.versioning_id ?? '',
|
|
2611
|
-
__typename: 'FOABloksPrimitive',
|
|
2612
|
-
};
|
|
2613
|
-
// Omit initial_response entirely when unset — same null-field crash as addProgressStatus/addThinkingStatus.
|
|
2614
|
-
if (data.initial_response != null) primitive.initial_response = data.initial_response;
|
|
2615
|
-
this._sections.push(AIRich.newLayout('Single', primitive));
|
|
2616
|
-
// Safety net: FOABloksPrimitive needs a real, client-registered Bloks screen to render
|
|
2617
|
-
// anything — arbitrary/placeholder payloads show up blank. Append a plain text section
|
|
2618
|
-
// so the card isn't empty. Set data.textFallback = false to skip.
|
|
2619
|
-
if (data.textFallback !== false) {
|
|
2620
|
-
this._sections.push(AIRich.newLayout('Single', { text: `Bloks: ${data.type}`, __typename: 'FOATextPrimitive' }));
|
|
2621
|
-
}
|
|
2622
|
-
return this;
|
|
2623
|
-
}
|
|
2624
|
-
|
|
2625
|
-
/** Add tappable follow-up suggestion chips below the message. @param {string|string[]} suggestion */
|
|
2626
|
-
addSuggest(suggestion, { scroll = true, layout } = {}) {
|
|
2348
|
+
addSuggest(suggestion, { scroll = true, layout, id, replace, insertAt } = {}) {
|
|
2627
2349
|
if (!(typeof suggestion === 'string' || (Array.isArray(suggestion) && suggestion.every((v) => typeof v === 'string')))) {
|
|
2628
2350
|
throw new TypeError('Suggestion must be a string or array of strings');
|
|
2629
2351
|
}
|
|
@@ -2644,18 +2366,28 @@ class AIRich extends BaseBuilder {
|
|
|
2644
2366
|
|
|
2645
2367
|
const type = layout ?? (suggest.length === 1 ? 'Single' : scroll ? 'HScroll' : 'ActionRow');
|
|
2646
2368
|
|
|
2647
|
-
|
|
2369
|
+
const section = AIRich.newLayout(type, type === 'Single' ? suggest[0] : suggest, {
|
|
2370
|
+
__typename: 'GenAIUnifiedResponseSection',
|
|
2371
|
+
});
|
|
2372
|
+
|
|
2373
|
+
const submessage = this.createAlert('GenAIFollowUpSuggestionPillPrimitive');
|
|
2648
2374
|
|
|
2649
|
-
return this
|
|
2375
|
+
return this._addContent(section, submessage, {
|
|
2376
|
+
id,
|
|
2377
|
+
replace,
|
|
2378
|
+
insertAt,
|
|
2379
|
+
});
|
|
2650
2380
|
}
|
|
2651
2381
|
|
|
2652
|
-
|
|
2653
|
-
|
|
2382
|
+
async build(
|
|
2383
|
+
jid,
|
|
2384
|
+
{ bypassDownload = true, forwarded = true, notification = false, includesUnifiedResponse = true, includesSubmessages = true, quoted, quotedParticipant, messageId, ...options } = {}
|
|
2385
|
+
) {
|
|
2654
2386
|
const forward = forwarded
|
|
2655
2387
|
? {
|
|
2656
2388
|
forwardingScore: 1,
|
|
2657
2389
|
isForwarded: true,
|
|
2658
|
-
forwardedAiBotMessageInfo: { botJid: '
|
|
2390
|
+
forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' },
|
|
2659
2391
|
forwardOrigin: 4,
|
|
2660
2392
|
}
|
|
2661
2393
|
: {};
|
|
@@ -2673,7 +2405,7 @@ class AIRich extends BaseBuilder {
|
|
|
2673
2405
|
const qObj = quoted
|
|
2674
2406
|
? {
|
|
2675
2407
|
stanzaId: quoted?.key?.id || quoted?.id,
|
|
2676
|
-
participant: quotedParticipant || quoted?.key?.participant || quoted?.key?.remoteJid,
|
|
2408
|
+
participant: quotedParticipant || quoted?.key?.participant || quoted?.participant || quoted?.key?.remoteJid,
|
|
2677
2409
|
quotedType: 0,
|
|
2678
2410
|
quotedMessage: typeof quoted === 'object' && quoted !== null ? (quoted.message ?? quoted) : undefined,
|
|
2679
2411
|
}
|
|
@@ -2689,140 +2421,88 @@ class AIRich extends BaseBuilder {
|
|
|
2689
2421
|
]
|
|
2690
2422
|
: [...(await waitAllPromises(this._sections))];
|
|
2691
2423
|
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
// Vanz@Fix 24-08-26 --- was `const responseId = crypto.randomUUID()` shared for BOTH
|
|
2697
|
-
// unifiedResponse.response_id and botMetadata.botResponseId, generated fresh every build()
|
|
2698
|
-
// with no override. Now each has its own id, pinned via setResponseId()/setBotResponseId()
|
|
2699
|
-
// if the caller set one (for sendEdit()-style in-place message updates), otherwise still
|
|
2700
|
-
// defaults to a fresh randomUUID() per build() exactly like before.
|
|
2701
|
-
const responseId = this._responseId ?? crypto.randomUUID();
|
|
2702
|
-
const botResponseId = this._botResponseId ?? crypto.randomUUID();
|
|
2424
|
+
if (this._dynamic) {
|
|
2425
|
+
this.refreshResponseId();
|
|
2426
|
+
this.refreshBotResponseId();
|
|
2427
|
+
}
|
|
2703
2428
|
|
|
2704
|
-
return
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
certificateChain: [botMetadataCertificate(), botMetadataCertificate(892)],
|
|
2716
|
-
version: 1,
|
|
2717
|
-
useCase: 1,
|
|
2718
|
-
signature: botMetadataSignature(),
|
|
2719
|
-
},
|
|
2720
|
-
],
|
|
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,
|
|
2721
2440
|
},
|
|
2722
|
-
...notif,
|
|
2723
2441
|
},
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
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') : '',
|
|
2450
|
+
},
|
|
2451
|
+
contextInfo: {
|
|
2452
|
+
...forward,
|
|
2453
|
+
...qObj,
|
|
2454
|
+
...this._contextInfo,
|
|
2455
|
+
},
|
|
2738
2456
|
},
|
|
2739
2457
|
},
|
|
2740
2458
|
},
|
|
2741
2459
|
},
|
|
2742
|
-
|
|
2460
|
+
{ messageId: messageId || generateMessageIDV2(), ...options }
|
|
2461
|
+
);
|
|
2743
2462
|
}
|
|
2744
2463
|
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
// { skipImageFallback: true } to opt out and send only the (image-less-looking) rich card.
|
|
2749
|
-
// Vanz@Fix: don't spread relayMessage-shaped `options` into sendMessage()'s options param —
|
|
2750
|
-
// the two calls expect different option shapes, so the fallback now only forwards `quoted`
|
|
2751
|
-
// (the one option that clearly applies to both) instead of blindly spreading everything.
|
|
2752
|
-
/** 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. */
|
|
2753
|
-
async send(jid, { forwarded, notification, includesUnifiedResponse, includesSubmessages, skipImageFallback = false, quoted, messageId, ...options } = {}) {
|
|
2754
|
-
const msg = await this.build({ forwarded, notification, includesUnifiedResponse, includesSubmessages, quoted, ...options });
|
|
2755
|
-
|
|
2756
|
-
if (!skipImageFallback && this._inlineImages.length) {
|
|
2757
|
-
for (const { url, caption } of this._inlineImages) {
|
|
2758
|
-
try {
|
|
2759
|
-
await this.#client.sendMessage(jid, { image: { url }, caption }, quoted ? { quoted } : {});
|
|
2760
|
-
} catch (err) {
|
|
2761
|
-
// Vanz@Fix: don't let a fallback image failure block the actual rich card from sending
|
|
2762
|
-
this.#client.logger?.warn?.({ err, url }, 'inline image fallback failed, continuing with rich card');
|
|
2763
|
-
}
|
|
2764
|
-
}
|
|
2464
|
+
async buildEdit(targetJid, targetId, { msg, messageId, ...options } = {}) {
|
|
2465
|
+
if (!msg) {
|
|
2466
|
+
msg = (await this.build(targetJid, options)).message;
|
|
2765
2467
|
}
|
|
2766
2468
|
|
|
2767
|
-
|
|
2768
|
-
// so we know exactly which id was sent, and stash it as _lastMessageKey. That's what lets
|
|
2769
|
-
// sendEdit() be called with no args afterwards and still know which message to patch.
|
|
2770
|
-
messageId = messageId || generateMessageIDV2();
|
|
2771
|
-
|
|
2772
|
-
await this.#client.relayMessage(jid, msg, { messageId, ...options });
|
|
2773
|
-
|
|
2774
|
-
this._lastMessageKey = { remoteJid: jid, fromMe: true, id: messageId };
|
|
2775
|
-
|
|
2776
|
-
return { key: this._lastMessageKey, message: msg };
|
|
2777
|
-
}
|
|
2778
|
-
|
|
2779
|
-
/**
|
|
2780
|
-
* Build a `protocolMessage` (type EDIT) that patches an already-sent AIRich message in place.
|
|
2781
|
-
* @param {string} targetJid Chat the original message lives in.
|
|
2782
|
-
* @param {string} targetId `key.id` of the original message (the id `send()`/`sendEdit()` returned).
|
|
2783
|
-
* @param {object} [opts] Pass `{ msg }` to reuse an already-built content object instead of rebuilding via build().
|
|
2784
|
-
*/
|
|
2785
|
-
async buildEdit(targetJid, targetId, { msg, messageId, ...options } = {}) {
|
|
2786
|
-
const editedMessage = msg || (await this.build({ ...options }));
|
|
2469
|
+
const editedMessage = msg;
|
|
2787
2470
|
|
|
2788
2471
|
if (!editedMessage) {
|
|
2789
|
-
throw new Error('buildEdit:
|
|
2472
|
+
throw new Error('buildEdit: msg does not contain botForwardedMessage');
|
|
2790
2473
|
}
|
|
2791
2474
|
|
|
2792
2475
|
return generateWAMessageFromContent(
|
|
2793
2476
|
targetJid,
|
|
2794
2477
|
{
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2478
|
+
botForwardedMessage: {
|
|
2479
|
+
message: {
|
|
2480
|
+
protocolMessage: {
|
|
2481
|
+
key: {
|
|
2482
|
+
remoteJid: targetJid,
|
|
2483
|
+
fromMe: true,
|
|
2484
|
+
id: targetId,
|
|
2485
|
+
},
|
|
2486
|
+
type: 14,
|
|
2487
|
+
editedMessage,
|
|
2488
|
+
},
|
|
2800
2489
|
},
|
|
2801
|
-
type: 14, // MESSAGE_EDIT
|
|
2802
|
-
editedMessage,
|
|
2803
2490
|
},
|
|
2804
2491
|
},
|
|
2805
2492
|
{ messageId: messageId || generateMessageIDV2(), ...options }
|
|
2806
2493
|
);
|
|
2807
2494
|
}
|
|
2808
2495
|
|
|
2809
|
-
/**
|
|
2810
|
-
* Rebuild this AIRich message's current content and patch it into an already-sent message in place
|
|
2811
|
-
* (WA edits the bubble instead of showing a new one). With no args, edits the message from the last
|
|
2812
|
-
* send()/sendEdit() call — that's the flow `.addX(...); await rich.sendEdit();` relies on.
|
|
2813
|
-
* @param {string} [jid] Defaults to the jid from the last send()/sendEdit().
|
|
2814
|
-
* @param {string} [id] Defaults to the message id from the last send()/sendEdit().
|
|
2815
|
-
*/
|
|
2816
2496
|
async sendEdit(jid, id, { msg, messageId, additionalNodes = [], ...options } = {}) {
|
|
2817
2497
|
jid = jid ?? this._lastMessageKey?.remoteJid;
|
|
2818
2498
|
id = id ?? this._lastMessageKey?.id;
|
|
2819
2499
|
|
|
2820
2500
|
if (!jid) {
|
|
2821
|
-
throw new Error('
|
|
2501
|
+
throw new Error('JID is required');
|
|
2822
2502
|
}
|
|
2823
2503
|
|
|
2824
2504
|
if (!id) {
|
|
2825
|
-
throw new Error('
|
|
2505
|
+
throw new Error('Message id is required');
|
|
2826
2506
|
}
|
|
2827
2507
|
|
|
2828
2508
|
const msgEdit = await this.buildEdit(jid, id, {
|
|
@@ -2836,13 +2516,36 @@ class AIRich extends BaseBuilder {
|
|
|
2836
2516
|
additionalNodes,
|
|
2837
2517
|
});
|
|
2838
2518
|
|
|
2839
|
-
// Vanz@Note --- deliberately NOT overwriting _lastMessageKey with msgEdit.key here: the
|
|
2840
|
-
// protocolMessage envelope has its own id, but the message the user actually sees (and the
|
|
2841
|
-
// one future sendEdit() calls need to keep patching) is still `id`/`jid` above.
|
|
2842
2519
|
return msgEdit;
|
|
2843
2520
|
}
|
|
2844
2521
|
|
|
2845
|
-
|
|
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
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
this._lastMessageKey = msg.key;
|
|
2545
|
+
|
|
2546
|
+
return msg;
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2846
2549
|
static tokenizer(code, lang = 'javascript') {
|
|
2847
2550
|
const keywordsMap = {
|
|
2848
2551
|
javascript: new Set([
|
|
@@ -3508,7 +3211,6 @@ class AIRich extends BaseBuilder {
|
|
|
3508
3211
|
};
|
|
3509
3212
|
}
|
|
3510
3213
|
|
|
3511
|
-
/** Convert a raw `string[][]` grid into the table metadata shape addTable()/addText() produce internally. */
|
|
3512
3214
|
static toTableMetadata(arr, { hyperlink = true, citation = true, latex = true } = {}) {
|
|
3513
3215
|
if (!Array.isArray(arr) || !arr.every((row) => Array.isArray(row) && row.every((cell) => typeof cell === 'string'))) {
|
|
3514
3216
|
throw new TypeError('Table must be a nested array of strings');
|
|
@@ -3557,120 +3259,35 @@ class AIRich extends BaseBuilder {
|
|
|
3557
3259
|
};
|
|
3558
3260
|
}
|
|
3559
3261
|
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
* Use this to show a pending-generation state before the real media is ready.
|
|
3564
|
-
* @param {{ imagine_type?: 'IMAGE'|'ANIMATE', estimated_completion_time?: number }} [options]
|
|
3565
|
-
*/
|
|
3566
|
-
addGenerating({ imagine_type = 'IMAGE', estimated_completion_time, textFallback = true } = {}) {
|
|
3567
|
-
this._submessages.push({ messageType: 2, messageText: '[ Sedang diproses... ]' });
|
|
3568
|
-
this._sections.push(
|
|
3569
|
-
AIRich.newLayout('Single', {
|
|
3570
|
-
media: { url: '', mime_type: imagine_type === 'ANIMATE' ? 'video/mp4' : 'image/png' },
|
|
3571
|
-
imagine_type,
|
|
3572
|
-
status: {
|
|
3573
|
-
status: 'GENERATING',
|
|
3574
|
-
estimated_completion_time: estimated_completion_time ?? Math.floor(Date.now() / 1000) + 30,
|
|
3575
|
-
},
|
|
3576
|
-
__typename: 'GenAIImaginePrimitive',
|
|
3577
|
-
})
|
|
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`
|
|
3578
3265
|
);
|
|
3579
|
-
// Vanz@Fix 23-08-26 (v4.8) --- media.url kosong + status GENERATING gak punya renderer
|
|
3580
|
-
// visual instan di stock WA client; sebelumnya cuma diem sampe WA nge-timeout sendiri
|
|
3581
|
-
// dan nampilin fallback bawaannya ("Saat ini, saya tidak bisa membuat gambar itu...").
|
|
3582
|
-
// Same fix class kayak addTask/addBloks: append FOATextPrimitive biar ada fallback
|
|
3583
|
-
// instan, gak perlu nunggu timeout WA. Set { textFallback: false } buat skip.
|
|
3584
|
-
if (textFallback) {
|
|
3585
|
-
this._sections.push(AIRich.newLayout('Single', { text: '[ Sedang diproses... ]', __typename: 'FOATextPrimitive' }));
|
|
3586
|
-
}
|
|
3587
|
-
return this;
|
|
3588
|
-
}
|
|
3589
|
-
|
|
3590
|
-
/**
|
|
3591
|
-
* Send a support-ticket marker message (`messageContextInfo.supportPayload`) — a plain
|
|
3592
|
-
* conversation message tagged as an AI/support-bot ticket, distinct from richResponseMessage.
|
|
3593
|
-
* @param {import('../../WAProto/index.js').WASocket} client
|
|
3594
|
-
* @param {string} jid
|
|
3595
|
-
* @param {string} text
|
|
3596
|
-
* @param {{ ticketId?: string, isAiMessage?: boolean, shouldShowSystemMessage?: boolean, version?: number }} [options]
|
|
3597
|
-
*/
|
|
3598
|
-
static async sendSupportPayload(client, jid, text, { ticketId = crypto.randomUUID(), isAiMessage = true, shouldShowSystemMessage = true, version = 1 } = {}) {
|
|
3599
|
-
if (!client) throw new Error('Socket is required');
|
|
3600
|
-
if (typeof text !== 'string' || !text) throw new TypeError('sendSupportPayload(client, jid, text) requires a non-empty string text');
|
|
3601
|
-
|
|
3602
|
-
const msg = {
|
|
3603
|
-
conversation: text,
|
|
3604
|
-
messageContextInfo: {
|
|
3605
|
-
messageSecret: crypto.randomBytes(32),
|
|
3606
|
-
supportPayload: JSON.stringify({
|
|
3607
|
-
version,
|
|
3608
|
-
is_ai_message: isAiMessage,
|
|
3609
|
-
should_show_system_message: shouldShowSystemMessage,
|
|
3610
|
-
ticket_id: ticketId,
|
|
3611
|
-
}),
|
|
3612
|
-
},
|
|
3613
|
-
};
|
|
3614
|
-
|
|
3615
|
-
return client.relayMessage(jid, msg, {
|
|
3616
|
-
additionalNodes: [
|
|
3617
|
-
{ tag: 'bot', attrs: { biz_bot: '1' } },
|
|
3618
|
-
{ tag: 'biz', attrs: {} },
|
|
3619
|
-
],
|
|
3620
|
-
});
|
|
3621
|
-
}
|
|
3622
3266
|
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
* `messageAssociation`). Distinct from a plain album — the client treats them as a single group.
|
|
3626
|
-
* @param {import('../../WAProto/index.js').WASocket} client
|
|
3627
|
-
* @param {string} jid
|
|
3628
|
-
* @param {{ image: string|Buffer, video: string|Buffer }} media
|
|
3629
|
-
*/
|
|
3630
|
-
static async sendPairedMedia(client, jid, { image, video } = {}) {
|
|
3631
|
-
if (!client) throw new Error('Socket is required');
|
|
3632
|
-
if (!image || !video) throw new TypeError('sendPairedMedia() requires both "image" and "video"');
|
|
3633
|
-
|
|
3634
|
-
const imagePrepared = await prepareWAMessageMedia(
|
|
3635
|
-
{ image: typeof image === 'string' ? { url: image } : image },
|
|
3636
|
-
{ upload: client.waUploadToServer }
|
|
3637
|
-
);
|
|
3638
|
-
const videoPrepared = await prepareWAMessageMedia(
|
|
3639
|
-
{ video: typeof video === 'string' ? { url: video } : video },
|
|
3640
|
-
{ 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`
|
|
3641
3269
|
);
|
|
3642
3270
|
|
|
3643
|
-
const
|
|
3644
|
-
jid,
|
|
3645
|
-
{
|
|
3646
|
-
imageMessage: {
|
|
3647
|
-
...imagePrepared.imageMessage,
|
|
3648
|
-
contextInfo: { pairedMediaType: 5, statusSourceType: 0 },
|
|
3649
|
-
},
|
|
3650
|
-
},
|
|
3651
|
-
{}
|
|
3652
|
-
);
|
|
3271
|
+
const signature = Buffer.concat([signatureMaterial, crypto.randomBytes(64 - signatureMaterial.length)]).toString('base64');
|
|
3653
3272
|
|
|
3654
|
-
|
|
3273
|
+
const certificateChain = [
|
|
3274
|
+
Buffer.concat([certificateMaterial, crypto.randomBytes(684 - certificateMaterial.length)]).toString('base64'),
|
|
3655
3275
|
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
{
|
|
3659
|
-
videoMessage: {
|
|
3660
|
-
...videoPrepared.videoMessage,
|
|
3661
|
-
contextInfo: { pairedMediaType: 6, statusSourceType: 0 },
|
|
3662
|
-
},
|
|
3663
|
-
messageContextInfo: {
|
|
3664
|
-
messageAssociation: { associationType: 12, parentMessageKey: imageMsg.key },
|
|
3665
|
-
},
|
|
3666
|
-
},
|
|
3667
|
-
{}
|
|
3668
|
-
);
|
|
3276
|
+
Buffer.concat([certificateMaterial, crypto.randomBytes(892 - certificateMaterial.length)]).toString('base64'),
|
|
3277
|
+
];
|
|
3669
3278
|
|
|
3670
|
-
return
|
|
3279
|
+
return {
|
|
3280
|
+
proofs: [
|
|
3281
|
+
{
|
|
3282
|
+
version: 1,
|
|
3283
|
+
useCase: 1,
|
|
3284
|
+
signature,
|
|
3285
|
+
certificateChain,
|
|
3286
|
+
},
|
|
3287
|
+
],
|
|
3288
|
+
};
|
|
3671
3289
|
}
|
|
3672
3290
|
|
|
3673
|
-
/** Build a raw submessage layout block by name — escape hatch for layouts not covered by the add*() helpers. */
|
|
3674
3291
|
static newLayout(name, data, extra = {}) {
|
|
3675
3292
|
return {
|
|
3676
3293
|
...extra,
|
|
@@ -3680,6 +3297,320 @@ class AIRich extends BaseBuilder {
|
|
|
3680
3297
|
},
|
|
3681
3298
|
};
|
|
3682
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
|
+
}
|
|
3683
3614
|
}
|
|
3684
3615
|
|
|
3685
3616
|
// Vanz@Alias --- AIRich diekspos ulang pake nama sendiri. Implementasi & referensi
|