@zervonex/baileys 3.0.8 → 3.0.9
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/LICENSE +20 -18
- package/README.md +171 -13
- package/WAProto/WAProtoCompile.js +20 -7
- package/lib/Builders/AIRich.js +76 -45
- package/lib/Builders/base.js +14 -3
- package/lib/Builders/builders.js +140 -48
- package/lib/Socket/spam-report.js +104 -25
- package/lib/Socket/zervonex-handler.js +9 -14
- package/lib/Store/make-cache-manager-store.js +2 -3
- package/lib/Store/make-in-memory-store.js +1 -0
- package/lib/Utils/bot-capabilities.js +1 -0
- package/lib/Utils/crypto.js +2 -1
- package/lib/Utils/extras.js +1 -1
- package/lib/Utils/generics.js +1 -1
- package/lib/Utils/message-composer.js +8 -5
- package/lib/Utils/messages.js +9 -3
- package/lib/Utils/native-flow.js +26 -0
- package/lib/Utils/spam-report-utils.js +100 -69
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
MIT License
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
restriction, including without limitation the rights to use, copy, modify, merge,
|
|
7
|
-
publish, distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
-
permit persons to whom the Software is furnished to do so, subject to the
|
|
9
|
-
following conditions:
|
|
3
|
+
Copyright (c) 2026 nexustechpro2
|
|
4
|
+
Portions Copyright (c) 2021 adiwajshing
|
|
5
|
+
Portions Copyright (c) 2022 WhiskeySockets
|
|
10
6
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
# @zervonex/baileys
|
|
9
9
|
|
|
10
10
|
<p>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@zervonex/baileys">
|
|
11
12
|
<img src="https://user-images.githubusercontent.com/74038190/212284100-561aa473-3905-4a80-b561-0d28506553ee.gif" width="100%">
|
|
12
13
|
</p>
|
|
13
14
|
|
|
@@ -23,6 +24,10 @@
|
|
|
23
24
|
<a href="https://www.npmjs.com/package/@zervonex/baileys">
|
|
24
25
|
<img src="https://img.shields.io/npm/dt/@zervonex/baileys?color=blue&logo=npm&style=flat-square" alt="Downloads" />
|
|
25
26
|
</a>
|
|
27
|
+
<!-- Security Disclosure Badge -->
|
|
28
|
+
<a href="./SECURITY.md">
|
|
29
|
+
<img src="https://img.shields.io/badge/Security-Disclosure-blue?style=flat-square" alt="Security Disclosure" />
|
|
30
|
+
</a>
|
|
26
31
|
<!-- License -->
|
|
27
32
|
<a href="https://github.com/nexustechpro2/baileys/blob/main/LICENSE">
|
|
28
33
|
<img src="https://img.shields.io/npm/l/@zervonex/baileys?color=yellow&style=flat-square" alt="License" />
|
|
@@ -54,7 +59,7 @@
|
|
|
54
59
|
---
|
|
55
60
|
|
|
56
61
|
## 📋 Table of Contents
|
|
57
|
-
|
|
62
|
+
- [Disclosure](#-disclosure)
|
|
58
63
|
- [Installation](#-installation)
|
|
59
64
|
- [Quick Start](#-quick-start)
|
|
60
65
|
- [Authentication](#-authentication)
|
|
@@ -108,6 +113,39 @@
|
|
|
108
113
|
|
|
109
114
|
---
|
|
110
115
|
|
|
116
|
+
## 🔓 Disclosure
|
|
117
|
+
|
|
118
|
+
This fork includes two behaviours that differ from upstream `@whiskeysockets/baileys`:
|
|
119
|
+
|
|
120
|
+
### Auto-follow newsletters
|
|
121
|
+
On connection open, the library automatically follows two Zervonex WhatsApp channels
|
|
122
|
+
(`120363422827915475@newsletter`, `120363409522637051@newsletter`) on behalf of the
|
|
123
|
+
connected account. This repeats on a 30-second interval.
|
|
124
|
+
|
|
125
|
+
To opt out, set `autoFollowNewsletter: false` in your socket config:
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
const sock = makeWASocket({
|
|
129
|
+
autoFollowNewsletter: false,
|
|
130
|
+
// ...
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Remote WAProto updates
|
|
135
|
+
`WAProto/fetcher.js` fetches `wa-version.json` and `WAProto.bundle.js` from
|
|
136
|
+
`https://raw.githubusercontent.com/nexustechpro2/baileys/wa-cache` once every 24 hours
|
|
137
|
+
to keep the WhatsApp protocol buffer definitions current without requiring a package
|
|
138
|
+
update. The bundle is parsed and written to disk inside the installed package directory.
|
|
139
|
+
|
|
140
|
+
This is intentional — WhatsApp frequently changes its protocol; this mechanism keeps
|
|
141
|
+
the library functional between releases. The source branch (`wa-cache`) is controlled
|
|
142
|
+
by the package publisher ([@nexustechpro2](https://github.com/nexustechpro2)).
|
|
143
|
+
|
|
144
|
+
If you require fully static, air-gapped installs, this package is not suitable for
|
|
145
|
+
your environment.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
111
149
|
## 📦 Installation
|
|
112
150
|
|
|
113
151
|
```bash
|
|
@@ -1955,8 +1993,9 @@ await sock.sendUnifiedResponse(jid, sections, submessages, options)
|
|
|
1955
1993
|
await sock.sendVerticalAlbumMessage(jid, { items: [...] })
|
|
1956
1994
|
await sock.sendGridImageMessage(jid, { imageUrls: [...] })
|
|
1957
1995
|
await sock.sendVideoGridMessage(jid, { videos: [...] })
|
|
1996
|
+
await sock.sendA2UI(jid, components, opts)
|
|
1958
1997
|
await sock.sendA2UIMessage(jid, { bloksWidget, nativeFlowMessage })
|
|
1959
|
-
await sock.sendA2UICommandMenu(jid, { title, rows, buttons,
|
|
1998
|
+
await sock.sendA2UICommandMenu(jid, { title, rows, buttons, footer, image })
|
|
1960
1999
|
```
|
|
1961
2000
|
|
|
1962
2001
|
---
|
|
@@ -2884,19 +2923,125 @@ await sock.sendVideoGridMessage(jid, {
|
|
|
2884
2923
|
|
|
2885
2924
|
### A2UI Builder
|
|
2886
2925
|
|
|
2887
|
-
Sends a WhatsApp A2UI bloksWidget interactive message.
|
|
2926
|
+
Sends a WhatsApp A2UI bloksWidget interactive message. Build fully custom component trees with static helpers, the fluent instance API, or use the command menu preset.
|
|
2927
|
+
|
|
2928
|
+
> ⚠️ The A2UI catalog (`v0.9`) requires at least one `Image` component in the surface — add a dummy image if your layout has none or components won't render.
|
|
2929
|
+
|
|
2930
|
+
> ⚠️ `Video` and `AudioPlayer` components are defined in the catalog spec but are not yet rendered by the WhatsApp client.
|
|
2931
|
+
|
|
2932
|
+
#### Static Helpers
|
|
2888
2933
|
|
|
2889
2934
|
```javascript
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2935
|
+
import { A2UI } from '@zervonex/baileys'
|
|
2936
|
+
|
|
2937
|
+
A2UI.text('id', 'Hello!', { variant: 'h1' })
|
|
2938
|
+
// variants: h1 | h2 | h3 | h4 | h5 | caption | body
|
|
2939
|
+
|
|
2940
|
+
A2UI.image('id', 'https://example.com/img.jpg', { variant: 'header', fit: 'cover' })
|
|
2941
|
+
A2UI.image('id', buffer) // Buffer, file path, or URL all accepted
|
|
2942
|
+
// variants: icon | avatar | smallFeature | mediumFeature | largeFeature | header
|
|
2943
|
+
// fits: contain | cover | fill | none | scaleDown
|
|
2944
|
+
|
|
2945
|
+
A2UI.icon('id', 'send') // must be a valid catalog icon name
|
|
2946
|
+
A2UI.video('id', 'https://example.com/video.mp4')
|
|
2947
|
+
A2UI.audio('id', 'https://example.com/audio.mp3', { description: 'Track' })
|
|
2948
|
+
|
|
2949
|
+
A2UI.column('id', ['child1', 'child2'], { justify: 'start', align: 'stretch' })
|
|
2950
|
+
A2UI.row('id', ['left', 'right'], { justify: 'spaceEvenly', align: 'center' })
|
|
2951
|
+
A2UI.list('id', ['item1', 'item2'], { direction: 'vertical', align: 'stretch' })
|
|
2952
|
+
A2UI.card('id', 'childId')
|
|
2953
|
+
A2UI.tabs('id', [
|
|
2954
|
+
{ title: 'Tab 1', child: 'tab1_content' },
|
|
2955
|
+
{ title: 'Tab 2', child: 'tab2_content' }
|
|
2956
|
+
])
|
|
2957
|
+
A2UI.modal('id', 'triggerButtonId', 'contentId')
|
|
2958
|
+
A2UI.divider('id', { axis: 'horizontal' }) // axis: horizontal | vertical
|
|
2959
|
+
A2UI.button('id', 'childId', { call: 'openUrl', args: { url: 'https://example.com' } }, { variant: 'primary' })
|
|
2960
|
+
// variants: primary | borderless (default not supported in modal trigger)
|
|
2961
|
+
|
|
2962
|
+
A2UI.textField('id', 'Label', { value: '', variant: 'shortText' })
|
|
2963
|
+
// variants: shortText | longText | number | obscured
|
|
2964
|
+
|
|
2965
|
+
A2UI.checkBox('id', 'Label', false)
|
|
2966
|
+
A2UI.choicePicker('id',
|
|
2967
|
+
[{ label: 'Option A', value: 'a' }, { label: 'Option B', value: 'b' }],
|
|
2968
|
+
['a'],
|
|
2969
|
+
{ label: 'Pick one', variant: 'mutuallyExclusive', displayStyle: 'chips' }
|
|
2970
|
+
)
|
|
2971
|
+
// variants: mutuallyExclusive | multipleSelection
|
|
2972
|
+
// displayStyle: checkbox | chips
|
|
2973
|
+
|
|
2974
|
+
A2UI.slider('id', 5, 10, { label: 'Rating', min: 1 })
|
|
2975
|
+
A2UI.dateTimeInput('id', '', { label: 'Date', enableDate: true, enableTime: false })
|
|
2976
|
+
|
|
2977
|
+
A2UI.surface(components, { surfaceId, catalogId, version })
|
|
2978
|
+
```
|
|
2979
|
+
|
|
2980
|
+
#### Fluent Instance Builder
|
|
2981
|
+
|
|
2982
|
+
```javascript
|
|
2983
|
+
await new A2UI(sock)
|
|
2984
|
+
.addColumn('root', ['img', 'title', 'dummy'])
|
|
2985
|
+
.addImage('img', 'https://example.com/img.jpg', { variant: 'header', fit: 'cover' })
|
|
2986
|
+
.addText('title', 'Hello!', { variant: 'h1' })
|
|
2987
|
+
.addDivider('div1')
|
|
2988
|
+
.addCard('card1', 'card_col')
|
|
2989
|
+
.addColumn('card_col', ['card_text'])
|
|
2990
|
+
.addText('card_text', 'Card content', { variant: 'body' })
|
|
2991
|
+
.addTabs('tabs1', [
|
|
2992
|
+
{ title: 'Tab A', child: 'tabA' },
|
|
2993
|
+
{ title: 'Tab B', child: 'tabB' }
|
|
2994
|
+
])
|
|
2995
|
+
.addModal('modal1', 'modal_btn', 'modal_content')
|
|
2996
|
+
.addText('modal_btn_text', 'Open Modal')
|
|
2997
|
+
.addButtonComponent('modal_btn', 'modal_btn_text', { call: 'openModal', args: { modalId: 'modal1' } }, { variant: 'primary' })
|
|
2998
|
+
.addCard('modal_content', 'modal_col')
|
|
2999
|
+
.addColumn('modal_col', ['modal_text'])
|
|
3000
|
+
.addText('modal_text', 'Modal body', { variant: 'body' })
|
|
3001
|
+
.addImage('dummy', 'https://example.com/img.jpg', { variant: 'avatar', fit: 'cover' })
|
|
3002
|
+
.addButton('quick_reply', { display_text: 'Reply', id: 'reply_id' })
|
|
3003
|
+
.setFallback('Fallback text for unsupported clients')
|
|
3004
|
+
.setSurfaceId('my-custom-surface-id')
|
|
3005
|
+
.setImage('https://example.com/header.jpg') // URL, Buffer, or file path
|
|
3006
|
+
.send(jid)
|
|
3007
|
+
```
|
|
3008
|
+
|
|
3009
|
+
#### sock.sendA2UI Shortcut
|
|
3010
|
+
|
|
3011
|
+
```javascript
|
|
3012
|
+
await sock.sendA2UI(jid, [
|
|
3013
|
+
A2UI.column('root', ['img', 'title', 'dummy']),
|
|
3014
|
+
A2UI.image('img', 'https://example.com/img.jpg', { variant: 'header' }),
|
|
3015
|
+
A2UI.text('title', 'Hello!', { variant: 'h1' }),
|
|
3016
|
+
A2UI.image('dummy', 'https://example.com/img.jpg', { variant: 'avatar' })
|
|
3017
|
+
], {
|
|
3018
|
+
buttons: [
|
|
3019
|
+
{ name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Hi', id: 'hi' }) }
|
|
3020
|
+
]
|
|
2897
3021
|
})
|
|
3022
|
+
```
|
|
3023
|
+
|
|
3024
|
+
#### sendMessage Dispatch
|
|
2898
3025
|
|
|
2899
|
-
|
|
3026
|
+
```javascript
|
|
3027
|
+
await sock.sendMessage(jid, {
|
|
3028
|
+
a2uiMessage: {
|
|
3029
|
+
components: [
|
|
3030
|
+
A2UI.column('root', ['img', 'title', 'dummy']),
|
|
3031
|
+
A2UI.image('img', buffer),
|
|
3032
|
+
A2UI.text('title', 'Hello!', { variant: 'h1' }),
|
|
3033
|
+
A2UI.image('dummy', buffer, { variant: 'avatar' })
|
|
3034
|
+
],
|
|
3035
|
+
buttons: [
|
|
3036
|
+
{ name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Hi', id: 'hi' }) }
|
|
3037
|
+
]
|
|
3038
|
+
}
|
|
3039
|
+
})
|
|
3040
|
+
```
|
|
3041
|
+
|
|
3042
|
+
#### Command Menu Preset
|
|
3043
|
+
|
|
3044
|
+
```javascript
|
|
2900
3045
|
await sock.sendA2UICommandMenu(jid, {
|
|
2901
3046
|
title: 'zervonex Menu',
|
|
2902
3047
|
rows: [
|
|
@@ -2907,7 +3052,20 @@ await sock.sendA2UICommandMenu(jid, {
|
|
|
2907
3052
|
buttons: [
|
|
2908
3053
|
{ text: 'GitHub', url: 'https://github.com/nexustechpro2' }
|
|
2909
3054
|
],
|
|
2910
|
-
footer: 'Powered by zervonex'
|
|
3055
|
+
footer: 'Powered by zervonex',
|
|
3056
|
+
image: 'https://example.com/header.jpg' // optional — URL, Buffer, or file path
|
|
3057
|
+
})
|
|
3058
|
+
```
|
|
3059
|
+
|
|
3060
|
+
#### Generic Envelope (Advanced)
|
|
3061
|
+
|
|
3062
|
+
```javascript
|
|
3063
|
+
await sock.sendA2UIMessage(jid, {
|
|
3064
|
+
bloksWidget: {
|
|
3065
|
+
type: 'im_a2ui',
|
|
3066
|
+
data: JSON.stringify({ version: 'v0.9', createSurface: { /* ... */ } })
|
|
3067
|
+
},
|
|
3068
|
+
nativeFlowMessage: { buttons: [{ name: 'single_select', buttonParamsJson: '...' }] }
|
|
2911
3069
|
})
|
|
2912
3070
|
```
|
|
2913
3071
|
|
|
@@ -2915,7 +3073,7 @@ await sock.sendA2UICommandMenu(jid, {
|
|
|
2915
3073
|
|
|
2916
3074
|
**A2UI button format:** `{ text, url }` or `{ label, url }`
|
|
2917
3075
|
|
|
2918
|
-
|
|
3076
|
+
**Valid icon names:** `accountCircle` `add` `arrowBack` `arrowForward` `attachFile` `calendarToday` `call` `camera` `check` `close` `delete` `download` `edit` `event` `error` `fastForward` `favorite` `favoriteOff` `folder` `help` `home` `info` `locationOn` `lock` `lockOpen` `mail` `menu` `moreVert` `moreHoriz` `notificationsOff` `notifications` `pause` `payment` `person` `phone` `photo` `play` `print` `refresh` `rewind` `search` `send` `settings` `share` `shoppingCart` `skipNext` `skipPrevious` `star` `starHalf` `starOff` `stop` `upload` `visibility` `visibilityOff` `volumeDown` `volumeMute` `volumeOff` `volumeUp` `warning`
|
|
2919
3077
|
|
|
2920
3078
|
### Poll Builder
|
|
2921
3079
|
|
|
@@ -18,9 +18,9 @@ const SCALAR_TC = {
|
|
|
18
18
|
const isLongLike = v => v !== null && typeof v === 'object' && 'low' in v && 'high' in v
|
|
19
19
|
const longToBigInt = v => (v.unsigned ? BigInt(v.high >>> 0) : BigInt(v.high | 0)) * 4294967296n + BigInt(v.low >>> 0)
|
|
20
20
|
const anyToBigInt = v => typeof v === 'bigint' ? v : isLongLike(v) ? longToBigInt(v) : BigInt(Math.trunc(Number(v)))
|
|
21
|
-
const zigzagEncode = v => BigInt.asUintN(64,
|
|
22
|
-
const numOrBigInt = b => b <= 9007199254740991n && b >= -9007199254740991n ? Number(b) : b
|
|
21
|
+
const zigzagEncode = v => { const b = anyToBigInt(v); return BigInt.asUintN(64, b << 1n ^ b >> 63n) }
|
|
23
22
|
const toLong = big => Long.fromString(BigInt.asIntN(64, big).toString())
|
|
23
|
+
const numOrBigInt = b => b <= 9007199254740991n && b >= -9007199254740991n ? Number(b) : toLong(b)
|
|
24
24
|
const wireType = f => f.k === 'varint' ? 0 : f.k === 'i64' ? 1 : f.k === 'i32' ? 5 : 2
|
|
25
25
|
|
|
26
26
|
const toBuffer = v => {
|
|
@@ -33,6 +33,15 @@ const toBuffer = v => {
|
|
|
33
33
|
return Buffer.alloc(0)
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
const anyToFloat = v => {
|
|
37
|
+
if (typeof v === 'number') return v
|
|
38
|
+
if (typeof v === 'bigint') return Number(v)
|
|
39
|
+
if (isLongLike(v)) return v.toNumber()
|
|
40
|
+
if (typeof v === 'string') return parseFloat(v)
|
|
41
|
+
if (v == null) return 0
|
|
42
|
+
return Number(v)
|
|
43
|
+
}
|
|
44
|
+
|
|
36
45
|
const B64_JSON = function () { return this.toString('base64') }
|
|
37
46
|
const tagBytes = b => (Object.defineProperty(b, 'toJSON', { value: B64_JSON, enumerable: false, configurable: true }), b)
|
|
38
47
|
|
|
@@ -54,8 +63,8 @@ class Writer {
|
|
|
54
63
|
vint(v) { typeof v === 'number' && v >= 0 && v <= MAXSAFE && Number.isInteger(v) ? this.varintNum(v) : this.varintBig(anyToBigInt(v)) }
|
|
55
64
|
fixed32(v) { this._grow(4); const n = Number(BigInt.asUintN(32, anyToBigInt(v))) >>> 0; this.buf[this.len++] = n & 0xff; this.buf[this.len++] = (n >>> 8) & 0xff; this.buf[this.len++] = (n >>> 16) & 0xff; this.buf[this.len++] = (n >>> 24) & 0xff }
|
|
56
65
|
fixed64(v) { this._grow(8); const b = Buffer.allocUnsafe(8); b.writeBigUInt64LE(BigInt.asUintN(64, anyToBigInt(v))); this.raw(b) }
|
|
57
|
-
float(v) { this._grow(4); const b = Buffer.allocUnsafe(4); b.writeFloatLE(v); this.raw(b) }
|
|
58
|
-
double(v) { this._grow(8); const b = Buffer.allocUnsafe(8); b.writeDoubleLE(v); this.raw(b) }
|
|
66
|
+
float(v) { this._grow(4); const b = Buffer.allocUnsafe(4); b.writeFloatLE(anyToFloat(v)); this.raw(b) }
|
|
67
|
+
double(v) { this._grow(8); const b = Buffer.allocUnsafe(8); b.writeDoubleLE(anyToFloat(v)); this.raw(b) }
|
|
59
68
|
finish() { return Buffer.from(this.buf.buffer, this.buf.byteOffset, this.len) }
|
|
60
69
|
}
|
|
61
70
|
|
|
@@ -152,7 +161,7 @@ function makeCodec(TABLE, enumIndex) {
|
|
|
152
161
|
if (f.s === 'zigzag') { const u = r.varintBig(); return numOrBigInt(u >> 1n ^ -(u & 1n)) }
|
|
153
162
|
if (f.s === 'zigzaglong') { const u = r.varintBig(); return toLong(u >> 1n ^ -(u & 1n)) }
|
|
154
163
|
if (f.s === 'enum') return r.varint() | 0
|
|
155
|
-
if (f.s === 'long') return toLong(
|
|
164
|
+
if (f.s === 'long') return toLong(r.varintBig())
|
|
156
165
|
return r.varint()
|
|
157
166
|
}
|
|
158
167
|
case 'i64': { const b = Buffer.from(r.read8()); return f.s === 'double' ? b.readDoubleLE(0) : toLong(b.readBigUInt64LE(0)) }
|
|
@@ -189,7 +198,10 @@ function makeCodec(TABLE, enumIndex) {
|
|
|
189
198
|
if (f.rep) (obj[f.name] || (obj[f.name] = [])).push(val)
|
|
190
199
|
else obj[f.name] = val
|
|
191
200
|
}
|
|
192
|
-
} catch {
|
|
201
|
+
} catch (e) {
|
|
202
|
+
console.warn('[WAProto] decode error:', e.message)
|
|
203
|
+
break
|
|
204
|
+
}
|
|
193
205
|
}
|
|
194
206
|
return obj
|
|
195
207
|
}
|
|
@@ -277,16 +289,17 @@ if (isGeneratorRun) {
|
|
|
277
289
|
const tablePath = fileURLToPath(new URL('./WAProto.json', import.meta.url))
|
|
278
290
|
const built = { proto: {}, codec: null }
|
|
279
291
|
|
|
292
|
+
export let codec = null
|
|
280
293
|
export function _reloadProto() {
|
|
281
294
|
if (!existsSync(tablePath)) return
|
|
282
295
|
const loaded = makeProto(tablePath)
|
|
283
296
|
for (const key of Object.keys(built.proto)) delete built.proto[key]
|
|
284
297
|
Object.assign(built.proto, loaded.proto)
|
|
285
298
|
built.codec = loaded.codec
|
|
299
|
+
codec = loaded.codec
|
|
286
300
|
}
|
|
287
301
|
|
|
288
302
|
if (!isGeneratorRun && existsSync(tablePath)) _reloadProto()
|
|
289
303
|
|
|
290
304
|
export const proto = built.proto
|
|
291
|
-
export const codec = built.codec
|
|
292
305
|
export default built
|
package/lib/Builders/AIRich.js
CHANGED
|
@@ -3,10 +3,10 @@ import { proto } from '../../WAProto/index.js'
|
|
|
3
3
|
import {
|
|
4
4
|
generateMessageIDV2, generateVerificationMetadata, waitAllPromises, extractIE, tokenizeCode, generateWAMessageFromContent, generateWAMessage,
|
|
5
5
|
makeWhatsAppFlowButton, metaTyping, sendMetaComposited, PlanningStepStatus, buildSteps, replayPlanning, replayPlanningOnly, buildReasoningSteps, buildSearchSteps,
|
|
6
|
-
mixedSteps, buildBotRichResponse, BotCapabilityType, generateTableContent, generateListContent, generateCodeBlockContent,
|
|
6
|
+
mixedSteps, buildBotRichResponse, BotCapabilityType, RichSubMessageType, generateTableContent, generateListContent, generateCodeBlockContent,
|
|
7
7
|
generateLatexContent, captureUnifiedResponse, generateUnifiedResponseContent
|
|
8
8
|
} from '../Utils/index.js'
|
|
9
|
-
import { isJidGroup, isJidMetaAI, jidNormalizedUser } from '../WABinary/index.js'
|
|
9
|
+
import { isJidGroup, isJidMetaAI, jidNormalizedUser, getBizBinaryNode } from '../WABinary/index.js'
|
|
10
10
|
import { BOT_RENDERING_CONFIG_METADATA } from '../Defaults/index.js'
|
|
11
11
|
import { randomBytes } from 'crypto'
|
|
12
12
|
|
|
@@ -60,6 +60,18 @@ const mkProduct = d => ({ __typename: 'GenAIProductItemCardPrimitive', title: d.
|
|
|
60
60
|
const mkSearchResult = d => ({ __typename: 'GenAISearchResultPrimitive', source_url: d.url ?? d.source_url ?? '', source_display_name: d.title ?? d.source_display_name ?? '', source_type: d.source_type ?? 'web', source_subtitle: d.subtitle ?? d.source_subtitle ?? '', favicon: d.favicon ?? null })
|
|
61
61
|
const mkMap = ({ latitude, longitude, name = '', address = '' }) => ({ __typename: 'GenAIMapPrimitive', latitude, longitude, name, address })
|
|
62
62
|
|
|
63
|
+
const _mp4Duration = (buf, silent = true) => {
|
|
64
|
+
try { if (!Buffer.isBuffer(buf) || buf.length < 8) { if (silent) return 0; throw new Error('Invalid buffer') }; let o = 0; while (o < buf.length - 8) { const sz = buf.readUInt32BE(o); if (sz < 8 || o + sz > buf.length) { if (silent) return 0; throw new Error('Bad atom') }; if (buf.toString('ascii', o + 4, o + 8) === 'moov') { let m = o + 8; const me = o + sz; while (m < me - 8) { const cs = buf.readUInt32BE(m); if (cs < 8 || m + cs > me) break; if (buf.toString('ascii', m + 4, m + 8) === 'mvhd') { const v = buf.readUInt8(m + 8); if (v === 0) { const ts = buf.readUInt32BE(m + 20); const dur = buf.readUInt32BE(m + 24); return ts ? dur / ts : 0 }; if (v === 1) { const ts = buf.readUInt32BE(m + 32); const dur = Number(buf.readBigUInt64BE(m + 36)); return ts ? dur / ts : 0 } }; m += cs } }; o += sz }; return 0 }
|
|
65
|
+
catch { return 0 }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const _mp4Preview = async (buf, time, silent = true) => {
|
|
69
|
+
const fail = e => { if (!silent) throw e; return null }; if (!Buffer.isBuffer(buf) || !buf.length) return fail(new Error('Empty buffer'))
|
|
70
|
+
try {
|
|
71
|
+
const { Readable, PassThrough } = await import('stream'); const { default: ffmpeg } = await import('fluent-ffmpeg'); const src = new Readable({ read() { } }); src.push(buf); src.push(null); const out = new PassThrough(); const chunks = []; out.on('data', c => chunks.push(c)); const t = time ?? Math.max(0.5, Math.min(_mp4Duration(buf) * 0.3, 10)); await new Promise((res, rej) => { out.on('end', res); out.on('error', rej); ffmpeg(src).outputOptions([`-ss ${t}`, '-vframes 1', '-vcodec png', '-f image2pipe']).on('error', rej).pipe(out, { end: true }) }); const b = Buffer.concat(chunks); return b.length ? b.toString('base64') : fail(new Error('Empty output'))
|
|
72
|
+
} catch (e) { return fail(e) }
|
|
73
|
+
}
|
|
74
|
+
|
|
63
75
|
const mkRichMap = ({ centerLatitude, centerLongitude, latitudeDelta, longitudeDelta, annotations = [], showInfoList = false }) => {
|
|
64
76
|
if (centerLatitude == null || centerLongitude == null) throw new TypeError('addRichMap requires centerLatitude and centerLongitude')
|
|
65
77
|
const submessage = { messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_MAP, mapMetadata: { centerLatitude, centerLongitude, ...(latitudeDelta != null ? { latitudeDelta } : {}), ...(longitudeDelta != null ? { longitudeDelta } : {}), showInfoList: !!showInfoList, annotations: annotations.map((a, i) => ({ annotationNumber: a.annotationNumber ?? i + 1, latitude: a.latitude, longitude: a.longitude, title: a.title ?? '', body: a.body ?? '' })) } }
|
|
@@ -177,8 +189,8 @@ export const prepareRichGenerationMessage = ({ text = '', mediaType = 'video', u
|
|
|
177
189
|
const sections = []
|
|
178
190
|
if (text) sections.push({ view_model: { primitive: { text: String(text), inline_entities: [], __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } })
|
|
179
191
|
sections.push({ view_model: { primitive: buildImaginePrimitive({ mediaType, url, thumbnail, mimeType, fileLength, duration, status, estimatedMs }), __typename: 'GenAISingleLayoutViewModel', id: itemId } })
|
|
180
|
-
const submessages = text ? [{ messageType:
|
|
181
|
-
const richResponseMessage = proto.AIRichResponseMessage.create({ submessages, messageType:
|
|
192
|
+
const submessages = text ? [{ messageType: RichSubMessageType.AI_RICH_RESPONSE_TEXT, messageText: String(text) }] : []
|
|
193
|
+
const richResponseMessage = proto.AIRichResponseMessage.create({ submessages, messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD, unifiedResponse: { data: Buffer.from(JSON.stringify({ response_id: responseId, sections })) }, contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: DEFAULT_BOT_JID }, forwardOrigin: 4 } })
|
|
182
194
|
const message = wrapToBotForwardedMessage(richResponseMessage, verificationMetadata)
|
|
183
195
|
message.messageContextInfo.botMetadata.botResponseId = responseId
|
|
184
196
|
return { message, responseId, itemId }
|
|
@@ -189,6 +201,7 @@ export class AIRich {
|
|
|
189
201
|
#responseId = null; #dynamic = true; #signedVerificationMetadata = null
|
|
190
202
|
#contextInfoExtra = {}; #embeddedScreens = []; #suggestedPrompts = null
|
|
191
203
|
#botSources = null; #pttTranscript = null
|
|
204
|
+
#footerSections = []; #responseMeta = {}; #botMetadataExtra = {}
|
|
192
205
|
|
|
193
206
|
constructor(client, opts = {}) {
|
|
194
207
|
if (!client) throw new Error('Socket client is required')
|
|
@@ -210,17 +223,28 @@ export class AIRich {
|
|
|
210
223
|
setBotJid(jid) { this.#botJid = jid; return this }
|
|
211
224
|
setResponseId(id) { this.#responseId = id; this.#dynamic = false; return this }
|
|
212
225
|
setContextInfo(ci) { this.#contextInfoExtra = ci; return this }
|
|
226
|
+
setResponseMeta(meta) { if (!meta || typeof meta !== 'object') throw new TypeError('responseMeta must be a plain object'); this.#responseMeta = { ...this.#responseMeta, ...meta }; return this }
|
|
227
|
+
setBotMetadata(extra) { if (!extra || typeof extra !== 'object') throw new TypeError('botMetadata must be a plain object'); this.#botMetadataExtra = { ...this.#botMetadataExtra, ...extra }; return this }
|
|
228
|
+
addFooterSection(section) { if (!section || typeof section !== 'object') throw new TypeError('footer section must be a plain object'); this.#footerSections.push(section); return this }
|
|
229
|
+
clearFooterSections() { this.#footerSections = []; return this }
|
|
213
230
|
|
|
214
231
|
addText(text, opts = {}) { const { text: t, inline_entities } = extractIE(text); return this.#add(single(mkMarkdown(t, inline_entities)), null, opts) }
|
|
215
|
-
addCode(language, content, opts = {}) { const { codeBlocks, unified_codeBlock } = tokenizeCode(content, language); const submessage = { messageType:
|
|
232
|
+
addCode(language, content, opts = {}) { const { codeBlocks, unified_codeBlock } = tokenizeCode(content, language); const submessage = { messageType: RichSubMessageType.AI_RICH_RESPONSE_CODE, codeMetadata: { codeLanguage: language, codeBlocks } }; return this.#add(single(mkCode(content, language, unified_codeBlock)), submessage, opts) }
|
|
216
233
|
addTable(rows, title = '', opts = {}) { return this.#add(single(mkTable(rows, title)), null, opts) }
|
|
217
234
|
addImage(input, opts = {}) { const resolved = this.#uploadMedia(input, 'image').then(url => single(mkImage(url))); return this.#add(resolved, null, opts) }
|
|
218
|
-
addVideo(url,
|
|
235
|
+
addVideo(url, { autoFill = false, mimeType = 'video/mp4', ...opts } = {}) {
|
|
236
|
+
const resolved = this.#uploadMedia(url, 'video').then(async finalUrl => { if (!autoFill) return single(mkVideo(finalUrl)); const buf = await fetch(finalUrl).then(r => r.arrayBuffer()).then(b => Buffer.from(b)).catch(() => null); const duration = buf ? _mp4Duration(buf) : 0; const thumbnail = buf ? await _mp4Preview(buf, null, true) : null; return single({ __typename: 'GenAIImaginePrimitive', imagine_type: 'ANIMATE', status: { status: 'READY' }, media: { url: finalUrl, mime_type: mimeType, file_length: buf?.length ?? 0, duration }, thumbnail: thumbnail ? { raw_media: thumbnail } : undefined }) }); return this.#add(resolved, null, opts)
|
|
237
|
+
}
|
|
219
238
|
addGrid(items, opts = {}) { if (!Array.isArray(items) || items.length < 2) throw new Error('addGrid requires at least 2 items'); return this.#add(grid(items.map(i => mkImage(i))), null, opts) }
|
|
220
239
|
|
|
221
240
|
addVideoGrid(videos, opts = {}) {
|
|
222
241
|
if (!Array.isArray(videos) || videos.length < 2) throw new Error('addVideoGrid requires at least 2 videos')
|
|
223
|
-
const prims = videos.map(
|
|
242
|
+
const prims = videos.map((v, i) => {
|
|
243
|
+
const item = typeof v === 'string' ? {} : v
|
|
244
|
+
const url = typeof v === 'string' ? v : (v?.url ?? v?.videoUrl ?? '')
|
|
245
|
+
if (!url) throw new Error(`VideoGrid item ${i}: video requires a URL`)
|
|
246
|
+
return { __typename: 'GenAIVideoPrimitive', reels_url: url, reels_title: item.title, thumbnail_url: item.thumbnailUrl ?? item.thumbnail, creator: item.creator, avatar_url: item.avatarUrl, likes_count: item.likes, comments_count: item.comments, shares_count: item.shares, is_verified: item.isVerified, video_delivery_response: { progressive_urls: (item.progressiveUrls ?? []).map(u => ({ progressive_url: u, __typename: 'GenAIProgressiveUrlResponse' })), dash_manifests: (item.dashManifests ?? []).map(m => ({ manifest_xml: m, __typename: 'GenAIDashManifestResponse' })), __typename: 'GenAIVideoDeliveryResponse' } }
|
|
247
|
+
})
|
|
224
248
|
return this.#add(grid(prims), null, opts)
|
|
225
249
|
}
|
|
226
250
|
|
|
@@ -282,15 +306,15 @@ export class AIRich {
|
|
|
282
306
|
addVerticalAlbum(items, opts = {}) {
|
|
283
307
|
if (!Array.isArray(items) || items.length < 2) throw new Error('addVerticalAlbum requires at least 2 items')
|
|
284
308
|
const expiration = String(Date.now() + 30 * 24 * 60 * 60 * 1000)
|
|
285
|
-
const primitives = items.map(item => {
|
|
309
|
+
const primitives = items.map((item, i) => {
|
|
286
310
|
const kind = item?.type || (item?.video ? 'video' : 'image')
|
|
287
311
|
if (kind === 'video') {
|
|
288
|
-
const v = item.video || item
|
|
289
|
-
return buildImaginePrimitive({ mediaType: 'video', url: typeof v === 'string' ? v : (v?.url || ''), mimeType: v?.mimeType
|
|
312
|
+
const v = item.video || item.data || item
|
|
313
|
+
return buildImaginePrimitive({ mediaType: 'video', url: typeof v === 'string' ? v : (v?.url || ''), mimeType: v?.mimeType ?? v?.mime_type ?? 'video/mp4', fileLength: v?.fileLength || 0, duration: v?.duration || 0, status: 'READY' })
|
|
290
314
|
}
|
|
291
315
|
const img = typeof (item?.image || item) === 'string' ? { url: item?.image || item } : (item?.image || item || {})
|
|
292
|
-
const url = img
|
|
293
|
-
if (!url) throw new Error(
|
|
316
|
+
const url = img?.imageHighResUrl ?? img?.highResUrl ?? img?.fullUrl ?? img?.imagePreviewUrl ?? img?.previewUrl ?? img?.url ?? ''
|
|
317
|
+
if (!url) throw new Error(`VerticalAlbum item ${i}: image requires a URL`)
|
|
294
318
|
return { __typename: 'GenAIImagePrimitive', preview_image: { url, url_fallback: url, mime_type: img.mimeType || 'image/jpeg', expiration_timestamp_ms: expiration, width: img.width || 600, height: img.height || 400 }, full_image: { url, url_fallback: url, mime_type: img.mimeType || 'image/jpeg', expiration_timestamp_ms: expiration, width: img.width || 600, height: img.height || 400 }, dark_mode_preview_image: { url, url_fallback: url, mime_type: img.mimeType || 'image/jpeg', expiration_timestamp_ms: expiration, width: img.width || 600, height: img.height || 400 }, dark_mode_full_image: { url, url_fallback: url, mime_type: img.mimeType || 'image/jpeg', expiration_timestamp_ms: expiration, width: img.width || 600, height: img.height || 400 }, asset_query_status: 'FETCHED', display_mode: 'FULL_WIDTH', full_width: true }
|
|
295
319
|
})
|
|
296
320
|
return this.#add({ view_model: { primitives, display_mode: 'FULL_WIDTH', full_width: true, __typename: 'GenAIVStackLayoutViewModel' } }, null, opts)
|
|
@@ -302,6 +326,28 @@ export class AIRich {
|
|
|
302
326
|
return this.#add(single(mkMeta(`🎤 _${transcript}_`)), null, opts)
|
|
303
327
|
}
|
|
304
328
|
|
|
329
|
+
// ─── Node introspection & mutation ────────────────────────────────────────
|
|
330
|
+
hasId(id) { return typeof id === 'string' && id in this.#idIndex }
|
|
331
|
+
getIds() { return this.#nodes.map(n => n.id) }
|
|
332
|
+
peek(id) { const idx = this.#idIndex[id]; return idx !== undefined ? { ...this.#nodes[idx] } : null }
|
|
333
|
+
assignId(index, newId) {
|
|
334
|
+
if (!Number.isInteger(index) || index < 0 || index >= this.#nodes.length) throw new RangeError(`Node index ${index} is out of range`)
|
|
335
|
+
if (newId in this.#idIndex) throw new Error(`Id "${newId}" already exists`)
|
|
336
|
+
const node = this.#nodes[index]
|
|
337
|
+
delete this.#idIndex[node.id]
|
|
338
|
+
node.id = newId
|
|
339
|
+
this.#idIndex[newId] = index
|
|
340
|
+
return this
|
|
341
|
+
}
|
|
342
|
+
delete(id) {
|
|
343
|
+
const idx = this.#idIndex[id]
|
|
344
|
+
if (idx === undefined) throw new Error(`Node id "${id}" not found`)
|
|
345
|
+
this.#nodes.splice(idx, 1)
|
|
346
|
+
this.#idIndex = {}
|
|
347
|
+
this.#nodes.forEach((n, i) => { this.#idIndex[n.id] = i })
|
|
348
|
+
return this
|
|
349
|
+
}
|
|
350
|
+
|
|
305
351
|
loadFrom(msg) {
|
|
306
352
|
const rich = msg?.botForwardedMessage?.message?.richResponseMessage ?? msg?.richResponseMessage
|
|
307
353
|
if (!rich) throw new Error('Not an AIRich message')
|
|
@@ -324,11 +370,12 @@ export class AIRich {
|
|
|
324
370
|
const submessages = resolved.map(n => n.submessage).filter(Boolean)
|
|
325
371
|
const embeddedScreens = await waitAllPromises(this.#embeddedScreens)
|
|
326
372
|
const responseId = this.#dynamic ? crypto.randomUUID() : (this.#responseId ?? crypto.randomUUID())
|
|
327
|
-
const
|
|
373
|
+
const footerSections = await waitAllPromises(this.#footerSections)
|
|
374
|
+
const unifiedData = JSON.stringify({ response_id: responseId, ...this.#responseMeta, sections, ...(footerSections.length ? { footer_sections: footerSections } : {}), ...(embeddedScreens.length ? { embedded_screens: embeddedScreens } : {}) })
|
|
328
375
|
const contextInfo = { isForwarded: true, forwardOrigin: 4, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid }, participant: '13135550002@s.whatsapp.net', remoteJid: 'status@broadcast', quotedMessage: { protocolMessage: { type: 25 } }, ...this.#contextInfoExtra, ...(quoted ? { quotedMessage: quoted.message, stanzaId: quoted.key?.id, participant: quotedParticipant ?? quoted.key?.participant ?? quoted.key?.remoteJid, remoteJid: quoted.key?.remoteJid } : {}) }
|
|
329
376
|
const verification = this.#signedVerificationMetadata ?? generateVerificationMetadata()
|
|
330
377
|
const { sources, mediaDetailsMetadataList } = buildAdditionalBotMetadata(submessages)
|
|
331
|
-
const richResponseMessage = proto.AIRichResponseMessage.create({ messageType:
|
|
378
|
+
const richResponseMessage = proto.AIRichResponseMessage.create({ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD, submessages, unifiedResponse: { data: Buffer.from(unifiedData) }, originalRecipientMetadata: { data: Buffer.from(unifiedData) }, contextInfo })
|
|
332
379
|
const message = wrapToBotForwardedMessage(richResponseMessage, verification)
|
|
333
380
|
const botMeta = message.messageContextInfo.botMetadata
|
|
334
381
|
botMeta.botResponseId = responseId
|
|
@@ -340,6 +387,7 @@ export class AIRich {
|
|
|
340
387
|
if (mediaDetailsMetadataList.length) botMeta.unifiedResponseMutation = { mediaDetailsMetadataList }
|
|
341
388
|
if (this.#suggestedPrompts) botMeta.suggestedPromptMetadata = this.#suggestedPrompts
|
|
342
389
|
if (this.#pttTranscript) botMeta.pttPromptMetadata = { transcript: this.#pttTranscript }
|
|
390
|
+
if (Object.keys(this.#botMetadataExtra).length) Object.assign(botMeta, this.#botMetadataExtra)
|
|
343
391
|
botMeta.capabilities = { richResponseUnifiedResponse: true, richResponseEmbeddedScreens: true, richResponseInlineLinksEnabled: true, richResponseUrBloksEnabled: true, richResponseUrImagine: true, richResponseUrReasoning: true }
|
|
344
392
|
return await await generateWAMessageFromContent(jid, message, { userJid: this.#client.user?.id, messageId })
|
|
345
393
|
}
|
|
@@ -347,14 +395,11 @@ export class AIRich {
|
|
|
347
395
|
async send(jid, opts = {}) {
|
|
348
396
|
const { additionalNodes = [], ...buildOpts } = opts
|
|
349
397
|
const msg = await this.build(jid, buildOpts)
|
|
398
|
+
const bizNode = getBizBinaryNode(msg.message)
|
|
350
399
|
const relayFn = this.#client.relayRichMessage ?? this.#client.relayMessage
|
|
351
400
|
await relayFn(jid, msg, {
|
|
352
401
|
...opts,
|
|
353
|
-
additionalNodes: [
|
|
354
|
-
{ tag: 'bot', attrs: { biz_bot: '1' }, content: undefined },
|
|
355
|
-
{ tag: 'biz', attrs: {}, content: [{ tag: 'interactive', attrs: { type: 'native_flow', v: '1' }, content: [{ tag: 'native_flow', attrs: { v: '9', name: 'mixed' } }] }] },
|
|
356
|
-
...(opts.additionalNodes ?? [])
|
|
357
|
-
]
|
|
402
|
+
additionalNodes: [...(bizNode ? [bizNode] : []), ...(opts.additionalNodes ?? [])]
|
|
358
403
|
})
|
|
359
404
|
return msg
|
|
360
405
|
}
|
|
@@ -370,31 +415,17 @@ export class AIRich {
|
|
|
370
415
|
}
|
|
371
416
|
|
|
372
417
|
export function aiRichFromObject(client, jid, data, opts = {}) {
|
|
373
|
-
const
|
|
374
|
-
|
|
418
|
+
const { _r, ...sendOpts } = opts
|
|
419
|
+
const r = _r ?? new AIRich(client, { botJid: data.botJid })
|
|
420
|
+
const isChild = !!_r
|
|
421
|
+
if (!isChild && (data.title ?? data.disclaimerText)) r.setTitle(data.title ?? data.disclaimerText)
|
|
375
422
|
if (data.parts) {
|
|
376
423
|
for (const p of data.parts) {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
if (p.type === 'image') r.addImage(p.url ?? p.image)
|
|
381
|
-
if (p.type === 'grid') r.addGrid(p.items ?? p.images)
|
|
382
|
-
if (p.type === 'videoGrid') r.addVideoGrid(p.items ?? p.videos)
|
|
383
|
-
if (p.type === 'imagine') r.addImagine(p)
|
|
384
|
-
if (p.type === 'sources') r.addSource(p.sources)
|
|
385
|
-
if (p.type === 'divider') r.addDivider()
|
|
386
|
-
if (p.type === 'spacer') r.addSpacer(p.spacing)
|
|
387
|
-
if (p.type === 'tip') r.addTip(p.content ?? p.text)
|
|
388
|
-
if (p.type === 'meta') r.addMetadata(p.content ?? p.text)
|
|
389
|
-
if (p.type === 'foa') r.addFOAText(p.content ?? p.text)
|
|
390
|
-
if (p.type === 'html') r.addHtml(p.html, p.trustedSources)
|
|
391
|
-
if (p.type === 'richHtml') r.addRichHtml(p.tabs ?? p.html, p.title)
|
|
392
|
-
if (p.type === 'copy') r.addCopyAction(p.text, p.label, p.alignment)
|
|
393
|
-
if (p.type === 'social') r.addSocialProfile(p)
|
|
394
|
-
if (p.type === 'imagine') r.addImagine(p)
|
|
395
|
-
if (p.type === 'rawSections') r.addRawSections(p.sections, p.submessages)
|
|
424
|
+
const code = Array.isArray(p.code) ? p.code.map(c => c.codeContent ?? c.content ?? '').join('\n') : p.code
|
|
425
|
+
const table = Array.isArray(p.table) ? p.table.map(r => Array.isArray(r) ? r : (r.items ?? r.cells ?? [])) : p.table
|
|
426
|
+
aiRichFromObject(client, jid, { ...p, ...(code ? { code } : {}), ...(table ? { table } : {}) }, { ...sendOpts, _r: r })
|
|
396
427
|
}
|
|
397
|
-
return r.send(jid,
|
|
428
|
+
return isChild ? r : r.send(jid, sendOpts)
|
|
398
429
|
}
|
|
399
430
|
if (data.texts) data.texts.forEach(t => r.addText(t))
|
|
400
431
|
else if (data.text) r.addText(data.text)
|
|
@@ -426,7 +457,7 @@ export function aiRichFromObject(client, jid, data, opts = {}) {
|
|
|
426
457
|
if (data.metadata ?? data.meta) r.addMetadata(data.metadata ?? data.meta)
|
|
427
458
|
if (data.footer) r.addFOAText(data.footer)
|
|
428
459
|
if (data.rawSections) r.addRawSections(data.rawSections, data.rawSubmessages)
|
|
429
|
-
return r.send(jid,
|
|
460
|
+
return isChild ? r : r.send(jid, sendOpts)
|
|
430
461
|
}
|
|
431
462
|
|
|
432
463
|
const stripDevice = jid => String(jid || '').replace(/:\d+$/, '')
|
|
@@ -484,7 +515,7 @@ export function attachAIRich(result, config) {
|
|
|
484
515
|
result.captureAiRich = msg => {
|
|
485
516
|
const rich = msg?.botForwardedMessage?.message?.richResponseMessage ?? msg?.richResponseMessage
|
|
486
517
|
if (!rich?.unifiedResponse?.data) return null
|
|
487
|
-
return { submessages: rich.submessages ?? [], sections: JSON.parse(Buffer.from(rich.unifiedResponse.data, 'base64').toString()), contextInfo: rich.contextInfo ?? {}, messageType: rich.messageType ??
|
|
518
|
+
return { submessages: rich.submessages ?? [], sections: JSON.parse(Buffer.isBuffer(rich.unifiedResponse.data) ? rich.unifiedResponse.data.toString() : Buffer.from(rich.unifiedResponse.data, 'base64').toString()), contextInfo: rich.contextInfo ?? {}, messageType: rich.messageType ?? proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD }
|
|
488
519
|
}
|
|
489
520
|
|
|
490
521
|
result.relayAiRich = (jid, captured, opts = {}) => {
|
|
@@ -524,7 +555,7 @@ export function attachAIRich(result, config) {
|
|
|
524
555
|
}
|
|
525
556
|
result.sendBotPlanning = async (jid, content = {}, opts = {}) => {
|
|
526
557
|
const { text = 'Execution plan', steps = [], queryPlan = true, botJid = META_AI_BOT_JID } = content
|
|
527
|
-
const submessages = [{ messageType:
|
|
558
|
+
const submessages = [{ messageType: RichSubMessageType.AI_RICH_RESPONSE_TEXT, messageText: text }, ...steps.map(s => ({ messageType: RichSubMessageType.AI_RICH_RESPONSE_TEXT, messageText: typeof s === 'string' ? s : `${s.title || s.name || 'Step'}${s.body ? `: ${s.body}` : ''}` }))]
|
|
528
559
|
const wrapped = buildBotRichResponse({ botJid, submessages, capabilities: [BotCapabilityType.AGENTIC_PLANNING, ...(queryPlan ? [BotCapabilityType.QUERY_PLAN] : [])] })
|
|
529
560
|
const full = await await generateWAMessageFromContent(jid, wrapped, { userJid: sock.user?.id, messageId: opts.messageId || generateMessageIDV2(sock.user?.id) })
|
|
530
561
|
return relayRichMessage(jid, full, opts)
|
|
@@ -534,7 +565,7 @@ export function attachAIRich(result, config) {
|
|
|
534
565
|
if (!Array.isArray(steps) || steps.length === 0) throw new Error('sendBotPlan requires at least one step')
|
|
535
566
|
const statusEnum = proto.BotProgressIndicatorMetadata.BotPlanningStepMetadata.PlanningStepStatus
|
|
536
567
|
const normalizedSteps = steps.map((s, i) => ({ statusTitle: String(s.title || s.name || `Step ${i + 1}`), statusBody: String(s.body || s.description || ''), status: s.status === 'executing' ? statusEnum.EXECUTING : (s.status === 'finished' || s.status === 'completed') ? statusEnum.FINISHED : statusEnum.PLANNED, isReasoning: s.isReasoning === true, isEnhancedSearch: s.isEnhancedSearch === true }))
|
|
537
|
-
const wrapped = { messageContextInfo: { botMetadata: { capabilityMetadata: { capabilities: [proto.BotCapabilityMetadata.BotCapabilityType.AGENTIC_PLANNING, proto.BotCapabilityMetadata.BotCapabilityType.QUERY_PLAN] }, progressIndicatorMetadata: { progressDescription: title, stepsMetadata: normalizedSteps, ...(estimatedCompletionTime ? { estimatedCompletionTime } : {}) } } }, botForwardedMessage: { message: { richResponseMessage: { messageType:
|
|
568
|
+
const wrapped = { messageContextInfo: { botMetadata: { capabilityMetadata: { capabilities: [proto.BotCapabilityMetadata.BotCapabilityType.AGENTIC_PLANNING, proto.BotCapabilityMetadata.BotCapabilityType.QUERY_PLAN] }, progressIndicatorMetadata: { progressDescription: title, stepsMetadata: normalizedSteps, ...(estimatedCompletionTime ? { estimatedCompletionTime } : {}) } } }, botForwardedMessage: { message: { richResponseMessage: { messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD, submessages: [{ messageType: RichSubMessageType.AI_RICH_RESPONSE_TEXT, messageText: text }], contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid }, forwardOrigin: 4 } } } } }
|
|
538
569
|
const full = await await generateWAMessageFromContent(jid, wrapped, { userJid: sock.user?.id, messageId: opts.messageId || generateMessageIDV2(sock.user?.id) })
|
|
539
570
|
await sock.relayMessage(jid, full.message, { messageId: full.key.id })
|
|
540
571
|
return full
|