@kyyinfinite/lumina 1.0.0 → 1.0.1
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/package.json +1 -1
- package/src/builders/ai-rich.js +0 -165
- package/src/builders/base.js +4 -51
- package/src/builders/button-v2.js +13 -78
- package/src/builders/button.js +20 -234
- package/src/builders/card.js +9 -76
- package/src/builders/carousel.js +4 -61
- package/src/builders/index.js +0 -7
- package/src/client/bot.js +29 -160
- package/src/client/connection.js +4 -111
- package/src/errors.js +0 -37
- package/src/index.d.ts +1 -28
- package/src/index.js +20 -121
- package/src/media/fetch.js +2 -33
- package/src/media/image.js +1 -41
- package/src/media/resolver.js +3 -55
- package/src/media/uploader.js +0 -30
- package/src/media/video.js +1 -39
- package/src/parsers/code-tokenizer-keywords.js +0 -12
- package/src/parsers/code-tokenizer.js +0 -42
- package/src/parsers/index.js +0 -7
- package/src/parsers/inline-entity.js +8 -117
- package/src/parsers/table-metadata.js +1 -35
- package/src/proto/enums.js +9 -65
- package/src/proto/layouts.js +3 -64
- package/src/proto/primitives.js +4 -91
- package/src/proto/relay-nodes.js +1 -32
- package/src/proto/rich-response.js +6 -57
- package/src/proto/updater.js +0 -85
- package/src/services/index.js +0 -7
- package/src/services/media-service.js +1 -102
- package/src/services/message-service.js +16 -158
- package/src/services/proto-service.js +3 -57
- package/src/utils/id.js +0 -25
- package/src/utils/logger.js +2 -39
- package/src/utils/mime.js +17 -73
- package/src/utils/promise.js +0 -26
- package/src/utils/validator.js +6 -71
package/package.json
CHANGED
package/src/builders/ai-rich.js
CHANGED
|
@@ -1,28 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file builders/ai-rich.js
|
|
3
|
-
* @module lumina/builders/ai-rich
|
|
4
|
-
*
|
|
5
|
-
* AIRichBuilder — modern, fully-chainable builder for WhatsApp's AI Rich
|
|
6
|
-
* Response feature.
|
|
7
|
-
*
|
|
8
|
-
* This is the productionised successor of the legacy `AIRich` class (which
|
|
9
|
-
* was 1,270 lines, 57% of `_build-m.js`). Every legacy issue flagged in
|
|
10
|
-
* Tahap-1 analysis is fixed here:
|
|
11
|
-
*
|
|
12
|
-
* 1. Promise-in-state anti-pattern → eager await on every `add*()`.
|
|
13
|
-
* 2. 12x hardcoded `__typename` strings → sourced from `proto/primitives`.
|
|
14
|
-
* 3. 5x `messageType` magic numbers → sourced from `proto/enums.MessageType`.
|
|
15
|
-
* 4. Hardcoded `botJid: '0@bot'` → from `proto/enums.BOT_JID`.
|
|
16
|
-
* 5. Hardcoded `forwardOrigin: 4` → from `proto/enums.ForOrigin`.
|
|
17
|
-
* 6. Personal leftover `disclaimerText: '~ Ahmad tumbuh kembang'` removed.
|
|
18
|
-
* 7. Typo `GenATableUXPrimitive` corrected to `GenAITableUXPrimitive`.
|
|
19
|
-
* 8. `tokenizer` + `toTableMetadata` + `newLayout` extracted to dedicated modules.
|
|
20
|
-
* 9. 5x duplicated validation helper → `utils/validator.ensureObjectOrArray`.
|
|
21
|
-
* 10. Footer handling consolidated (was inlined in `build()`).
|
|
22
|
-
*
|
|
23
|
-
* API (verb-first fluent):
|
|
24
|
-
* bot.ai().title(t).text(...).code(...).image(...).table(...).send(jid)
|
|
25
|
-
*/
|
|
26
1
|
|
|
27
2
|
import { applyContentFields } from './base.js'
|
|
28
3
|
import { ensureObjectOrArray, ensureString, ensureStringArray } from '../utils/validator.js'
|
|
@@ -50,100 +25,54 @@ import { singleLayout, hscrollLayout, actionRowLayout } from '../proto/layouts.j
|
|
|
50
25
|
import { assembleRichResponse } from '../proto/rich-response.js'
|
|
51
26
|
import { MessageType, ImagineType, LayoutKind } from '../proto/enums.js'
|
|
52
27
|
|
|
53
|
-
/** @typedef {import('../client/connection.js').Connection} Connection */
|
|
54
|
-
/** @typedef {import('../services/proto-service.js').ProtoService} ProtoService */
|
|
55
|
-
/** @typedef {import('../services/media-service.js').MediaService} MediaService */
|
|
56
28
|
|
|
57
29
|
export class AIRichBuilder {
|
|
58
|
-
/**
|
|
59
|
-
* @param {Connection} conn
|
|
60
|
-
* @param {ProtoService} proto
|
|
61
|
-
* @param {MediaService} media
|
|
62
|
-
*/
|
|
63
30
|
constructor(conn, proto, media) {
|
|
64
31
|
applyContentFields(this)
|
|
65
32
|
this.#conn = conn
|
|
66
33
|
this.#proto = proto
|
|
67
34
|
this.#media = media
|
|
68
35
|
|
|
69
|
-
/** @type {Array<object>} */
|
|
70
36
|
this._submessages = []
|
|
71
|
-
/** @type {Array<object>} */
|
|
72
37
|
this._sections = []
|
|
73
|
-
/** @type {Array<object>} */
|
|
74
38
|
this._richResponseSources = []
|
|
75
39
|
|
|
76
|
-
/** @type {boolean} */
|
|
77
40
|
this._forwarded = true
|
|
78
|
-
/** @type {boolean | object} */
|
|
79
41
|
this._notification = false
|
|
80
|
-
/** @type {boolean} */
|
|
81
42
|
this._includesUnifiedResponse = true
|
|
82
|
-
/** @type {boolean} */
|
|
83
43
|
this._includesSubmessages = true
|
|
84
|
-
/** @type {object | undefined} */
|
|
85
44
|
this._quoted
|
|
86
|
-
/** @type {string | undefined} */
|
|
87
45
|
this._quotedParticipant
|
|
88
46
|
}
|
|
89
47
|
|
|
90
|
-
/** @type {Connection} */ #conn
|
|
91
|
-
/** @type {ProtoService} */ #proto
|
|
92
|
-
/** @type {MediaService} */ #media
|
|
93
48
|
|
|
94
|
-
// ─── Envelope options ───────────────────────────────────────────────
|
|
95
49
|
|
|
96
|
-
/**
|
|
97
|
-
* Toggle forwarding metadata (default: on).
|
|
98
|
-
*
|
|
99
|
-
* @param {boolean} [v=true]
|
|
100
|
-
*/
|
|
101
50
|
forwarded(v = true) {
|
|
102
51
|
this._forwarded = v
|
|
103
52
|
return this
|
|
104
53
|
}
|
|
105
54
|
|
|
106
|
-
/**
|
|
107
|
-
* Attach session-transparency metadata.
|
|
108
|
-
*
|
|
109
|
-
* @param {boolean | object} v `true` for defaults, or `{ disclaimerText, hcaId, sessionTransparencyType }`.
|
|
110
|
-
*/
|
|
111
55
|
notification(v) {
|
|
112
56
|
this._notification = v
|
|
113
57
|
return this
|
|
114
58
|
}
|
|
115
59
|
|
|
116
|
-
/**
|
|
117
|
-
* @param {object} quoted
|
|
118
|
-
* @param {string} [quotedParticipant]
|
|
119
|
-
*/
|
|
120
60
|
quoted(quoted, quotedParticipant) {
|
|
121
61
|
this._quoted = quoted
|
|
122
62
|
this._quotedParticipant = quotedParticipant
|
|
123
63
|
return this
|
|
124
64
|
}
|
|
125
65
|
|
|
126
|
-
/**
|
|
127
|
-
* @param {boolean} [v=true]
|
|
128
|
-
*/
|
|
129
66
|
includesUnifiedResponse(v = true) {
|
|
130
67
|
this._includesUnifiedResponse = v
|
|
131
68
|
return this
|
|
132
69
|
}
|
|
133
70
|
|
|
134
|
-
/**
|
|
135
|
-
* @param {boolean} [v=true]
|
|
136
|
-
*/
|
|
137
71
|
includesSubmessages(v = true) {
|
|
138
72
|
this._includesSubmessages = v
|
|
139
73
|
return this
|
|
140
74
|
}
|
|
141
75
|
|
|
142
|
-
/**
|
|
143
|
-
* Push a raw submessage object (escape hatch).
|
|
144
|
-
*
|
|
145
|
-
* @param {object | object[]} msg
|
|
146
|
-
*/
|
|
147
76
|
submessage(msg) {
|
|
148
77
|
const items = Array.isArray(msg) ? msg : [msg]
|
|
149
78
|
items.forEach((m) => ensureObjectOrArray(m, 'submessage'))
|
|
@@ -151,16 +80,7 @@ export class AIRichBuilder {
|
|
|
151
80
|
return this
|
|
152
81
|
}
|
|
153
82
|
|
|
154
|
-
// ─── Primitives ─────────────────────────────────────────────────────
|
|
155
83
|
|
|
156
|
-
/**
|
|
157
|
-
* Add a markdown-text primitive. Inline entities (`[text](url)`, `[](url)`,
|
|
158
|
-
* `[text]<url|w|h|fh|p>`) are auto-extracted.
|
|
159
|
-
*
|
|
160
|
-
* @param {string} text
|
|
161
|
-
* @param {object} [opts] { hyperlink, citation, latex } — all default true.
|
|
162
|
-
* @returns {Promise<this>}
|
|
163
|
-
*/
|
|
164
84
|
async text(text, opts = {}) {
|
|
165
85
|
ensureString(text, 'text')
|
|
166
86
|
const { hyperlink = true, citation = true, latex = true } = opts
|
|
@@ -182,13 +102,6 @@ export class AIRichBuilder {
|
|
|
182
102
|
return this
|
|
183
103
|
}
|
|
184
104
|
|
|
185
|
-
/**
|
|
186
|
-
* Add a code-block primitive. Code is auto-tokenized for syntax highlighting.
|
|
187
|
-
*
|
|
188
|
-
* @param {string} language e.g. 'javascript', 'python', 'go'.
|
|
189
|
-
* @param {string} code
|
|
190
|
-
* @returns {Promise<this>}
|
|
191
|
-
*/
|
|
192
105
|
async code(language, code) {
|
|
193
106
|
ensureString(language, 'language')
|
|
194
107
|
ensureString(code, 'code')
|
|
@@ -207,13 +120,6 @@ export class AIRichBuilder {
|
|
|
207
120
|
return this
|
|
208
121
|
}
|
|
209
122
|
|
|
210
|
-
/**
|
|
211
|
-
* Add a table primitive.
|
|
212
|
-
*
|
|
213
|
-
* @param {string[][]} table First row is the header.
|
|
214
|
-
* @param {object} [opts] { title, hyperlink, citation, latex }
|
|
215
|
-
* @returns {Promise<this>}
|
|
216
|
-
*/
|
|
217
123
|
async table(table, opts = {}) {
|
|
218
124
|
if (!Array.isArray(table)) {
|
|
219
125
|
throw new ValidationError('table must be a 2-D array', { code: 'LUMINA_VALIDATION_TABLE' })
|
|
@@ -230,18 +136,10 @@ export class AIRichBuilder {
|
|
|
230
136
|
return this
|
|
231
137
|
}
|
|
232
138
|
|
|
233
|
-
/**
|
|
234
|
-
* Add image primitive(s). Auto-uploads to WhatsApp.
|
|
235
|
-
*
|
|
236
|
-
* @param {string | Buffer | Array<string | Buffer>} source
|
|
237
|
-
* @param {object} [opts] { resolveUrl } — passed to MediaService.resolve.
|
|
238
|
-
* @returns {Promise<this>}
|
|
239
|
-
*/
|
|
240
139
|
async image(source, opts = {}) {
|
|
241
140
|
const { resolveUrl = false } = opts
|
|
242
141
|
const list = Array.isArray(source) ? source : [source]
|
|
243
142
|
|
|
244
|
-
/** @type {Array<{ imagePreviewUrl: string, imageHighResUrl: string, sourceUrl: string }>} */
|
|
245
143
|
const resolved = await Promise.all(
|
|
246
144
|
list.map(async (s) => {
|
|
247
145
|
const url = await this.#media.resolve(s, {
|
|
@@ -277,20 +175,11 @@ export class AIRichBuilder {
|
|
|
277
175
|
return this
|
|
278
176
|
}
|
|
279
177
|
|
|
280
|
-
/**
|
|
281
|
-
* Add video primitive(s). Auto-fills file_length, duration, and thumbnail
|
|
282
|
-
* by downloading & inspecting the buffer (configurable via `autoFill`).
|
|
283
|
-
*
|
|
284
|
-
* @param {string | Buffer | object | Array} source
|
|
285
|
-
* @param {object} [opts] { autoFill = true }
|
|
286
|
-
* @returns {Promise<this>}
|
|
287
|
-
*/
|
|
288
178
|
async video(source, opts = {}) {
|
|
289
179
|
const { autoFill = true } = opts
|
|
290
180
|
const isObjectVideo = (v) => v && typeof v === 'object' && v.url
|
|
291
181
|
const items = Array.isArray(source) ? source : [source]
|
|
292
182
|
|
|
293
|
-
// Placeholder submessage — matches the legacy '[ CANNOT_LOAD_VIDEO ]' pattern.
|
|
294
183
|
this._submessages.push({
|
|
295
184
|
messageType: MessageType.TEXT,
|
|
296
185
|
messageText: '[ CANNOT_LOAD_VIDEO ]',
|
|
@@ -323,7 +212,6 @@ export class AIRichBuilder {
|
|
|
323
212
|
})
|
|
324
213
|
}
|
|
325
214
|
} catch {
|
|
326
|
-
// Swallow autofill failures — the video URL alone is still useful.
|
|
327
215
|
}
|
|
328
216
|
}
|
|
329
217
|
|
|
@@ -344,14 +232,6 @@ export class AIRichBuilder {
|
|
|
344
232
|
return this
|
|
345
233
|
}
|
|
346
234
|
|
|
347
|
-
/**
|
|
348
|
-
* Add source(s) — citation footnotes with favicon, URL, and display text.
|
|
349
|
-
*
|
|
350
|
-
* @param {Array<string> | Array<Array<string>>} sources
|
|
351
|
-
* Each entry is either `[iconUrl, url, text]` or (when only strings)
|
|
352
|
-
* an array of URLs that get empty favicons.
|
|
353
|
-
* @returns {Promise<this>}
|
|
354
|
-
*/
|
|
355
235
|
async source(sources = []) {
|
|
356
236
|
if (!Array.isArray(sources)) {
|
|
357
237
|
throw new ValidationError('sources must be an array', { code: 'LUMINA_VALIDATION_SOURCE' })
|
|
@@ -376,12 +256,6 @@ export class AIRichBuilder {
|
|
|
376
256
|
return this
|
|
377
257
|
}
|
|
378
258
|
|
|
379
|
-
/**
|
|
380
|
-
* Add reel(s) — short-video cards with avatar, thumbnail, like/view counts.
|
|
381
|
-
*
|
|
382
|
-
* @param {object | object[]} items
|
|
383
|
-
* @returns {Promise<this>}
|
|
384
|
-
*/
|
|
385
259
|
async reels(items) {
|
|
386
260
|
ensureObjectOrArray(items, 'reels')
|
|
387
261
|
const list = Array.isArray(items) ? items : [items]
|
|
@@ -439,12 +313,6 @@ export class AIRichBuilder {
|
|
|
439
313
|
return this
|
|
440
314
|
}
|
|
441
315
|
|
|
442
|
-
/**
|
|
443
|
-
* Add product card(s).
|
|
444
|
-
*
|
|
445
|
-
* @param {object | object[]} item
|
|
446
|
-
* @returns {Promise<this>}
|
|
447
|
-
*/
|
|
448
316
|
async product(item) {
|
|
449
317
|
ensureObjectOrArray(item, 'product')
|
|
450
318
|
const list = Array.isArray(item) ? item : [item]
|
|
@@ -483,12 +351,6 @@ export class AIRichBuilder {
|
|
|
483
351
|
return this
|
|
484
352
|
}
|
|
485
353
|
|
|
486
|
-
/**
|
|
487
|
-
* Add post(s).
|
|
488
|
-
*
|
|
489
|
-
* @param {object | object[]} item
|
|
490
|
-
* @returns {Promise<this>}
|
|
491
|
-
*/
|
|
492
354
|
async post(item) {
|
|
493
355
|
ensureObjectOrArray(item, 'post')
|
|
494
356
|
const list = Array.isArray(item) ? item : [item]
|
|
@@ -552,12 +414,6 @@ export class AIRichBuilder {
|
|
|
552
414
|
return this
|
|
553
415
|
}
|
|
554
416
|
|
|
555
|
-
/**
|
|
556
|
-
* Add a tip / metadata-text primitive (small footer-style hint).
|
|
557
|
-
*
|
|
558
|
-
* @param {string} text
|
|
559
|
-
* @returns {Promise<this>}
|
|
560
|
-
*/
|
|
561
417
|
async tip(text) {
|
|
562
418
|
ensureString(text, 'tip text')
|
|
563
419
|
this._submessages.push({ messageType: MessageType.TEXT, messageText: text })
|
|
@@ -565,13 +421,6 @@ export class AIRichBuilder {
|
|
|
565
421
|
return this
|
|
566
422
|
}
|
|
567
423
|
|
|
568
|
-
/**
|
|
569
|
-
* Add follow-up suggestion pill(s).
|
|
570
|
-
*
|
|
571
|
-
* @param {string | string[]} suggestion
|
|
572
|
-
* @param {object} [opts] { scroll = true, layout?: 'Single'|'HScroll'|'ActionRow' }
|
|
573
|
-
* @returns {Promise<this>}
|
|
574
|
-
*/
|
|
575
424
|
async suggest(suggestion, opts = {}) {
|
|
576
425
|
const { scroll = true, layout } = opts
|
|
577
426
|
|
|
@@ -602,14 +451,7 @@ export class AIRichBuilder {
|
|
|
602
451
|
return this
|
|
603
452
|
}
|
|
604
453
|
|
|
605
|
-
// ─── Lifecycle ──────────────────────────────────────────────────────
|
|
606
454
|
|
|
607
|
-
/**
|
|
608
|
-
* Assemble the `botForwardedMessage` envelope (without relaying).
|
|
609
|
-
*
|
|
610
|
-
* @param {object} [opts] Overrides forwarded / notification / etc.
|
|
611
|
-
* @returns {Promise<object>}
|
|
612
|
-
*/
|
|
613
455
|
async build(opts = {}) {
|
|
614
456
|
return assembleRichResponse({
|
|
615
457
|
title: this._title,
|
|
@@ -628,13 +470,6 @@ export class AIRichBuilder {
|
|
|
628
470
|
})
|
|
629
471
|
}
|
|
630
472
|
|
|
631
|
-
/**
|
|
632
|
-
* Build + relay via the Baileys socket.
|
|
633
|
-
*
|
|
634
|
-
* @param {string} jid
|
|
635
|
-
* @param {object} [opts]
|
|
636
|
-
* @returns {Promise<string>} Message ID.
|
|
637
|
-
*/
|
|
638
473
|
async send(jid, opts = {}) {
|
|
639
474
|
const envelope = await this.build(opts)
|
|
640
475
|
return this.#conn.relayMessage(jid, envelope, opts)
|
package/src/builders/base.js
CHANGED
|
@@ -1,39 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file builders/base.js
|
|
3
|
-
* @module lumina/builders/base
|
|
4
|
-
*
|
|
5
|
-
* Builder-state composer. Lumina deliberately avoids a `BaseBuilder` parent
|
|
6
|
-
* class — inheritance forces every builder to carry every state field
|
|
7
|
-
* (`_title`, `_subtitle`, etc.) whether or not they need it, and locks the
|
|
8
|
-
* framework into a single hierarchy.
|
|
9
|
-
*
|
|
10
|
-
* Instead, we expose `applyContentFields(target)` — a mixin that attaches
|
|
11
|
-
* the fluent content methods (`title`, `body`, `footer`, `contextInfo`,
|
|
12
|
-
* `payload`) to whatever builder invokes it. A builder composes only the
|
|
13
|
-
* capabilities it needs.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
1
|
import { ensurePlainObject } from '../utils/validator.js'
|
|
17
2
|
import { ValidationError } from '../errors.js'
|
|
18
3
|
|
|
19
|
-
/**
|
|
20
|
-
* Apply the shared content-mixin to a builder instance.
|
|
21
|
-
*
|
|
22
|
-
* Attaches:
|
|
23
|
-
* - title(t) → sets _title
|
|
24
|
-
* - subtitle(t) → sets _subtitle
|
|
25
|
-
* - body(t) → sets _body
|
|
26
|
-
* - footer(t) → sets _footer
|
|
27
|
-
* - contextInfo(o) → sets _contextInfo (validated plain object)
|
|
28
|
-
* - payload(o) → merges into _extraPayload (validated plain object)
|
|
29
|
-
*
|
|
30
|
-
* Each method returns `this` for chaining.
|
|
31
|
-
*
|
|
32
|
-
* @template {object} T
|
|
33
|
-
* @param {T} target The builder instance to mixin into.
|
|
34
|
-
* @param {object} [initial] Optional initial state.
|
|
35
|
-
* @returns {T & ContentFields}
|
|
36
|
-
*/
|
|
37
4
|
export function applyContentFields(target, initial = {}) {
|
|
38
5
|
target._title = initial.title ?? ''
|
|
39
6
|
target._subtitle = initial.subtitle ?? ''
|
|
@@ -43,33 +10,25 @@ export function applyContentFields(target, initial = {}) {
|
|
|
43
10
|
target._extraPayload = initial.extraPayload ?? {}
|
|
44
11
|
|
|
45
12
|
target.title = function (t) {
|
|
46
|
-
if (typeof t !== 'string') {
|
|
47
|
-
throw new ValidationError('title must be a string', { code: 'LUMINA_VALIDATION_TITLE' })
|
|
48
|
-
}
|
|
13
|
+
if (typeof t !== 'string') throw new ValidationError('title must be a string', { code: 'LUMINA_VALIDATION_TITLE' })
|
|
49
14
|
target._title = t
|
|
50
15
|
return target
|
|
51
16
|
}
|
|
52
17
|
|
|
53
18
|
target.subtitle = function (t) {
|
|
54
|
-
if (typeof t !== 'string') {
|
|
55
|
-
throw new ValidationError('subtitle must be a string', { code: 'LUMINA_VALIDATION_SUBTITLE' })
|
|
56
|
-
}
|
|
19
|
+
if (typeof t !== 'string') throw new ValidationError('subtitle must be a string', { code: 'LUMINA_VALIDATION_SUBTITLE' })
|
|
57
20
|
target._subtitle = t
|
|
58
21
|
return target
|
|
59
22
|
}
|
|
60
23
|
|
|
61
24
|
target.body = function (t) {
|
|
62
|
-
if (typeof t !== 'string') {
|
|
63
|
-
throw new ValidationError('body must be a string', { code: 'LUMINA_VALIDATION_BODY' })
|
|
64
|
-
}
|
|
25
|
+
if (typeof t !== 'string') throw new ValidationError('body must be a string', { code: 'LUMINA_VALIDATION_BODY' })
|
|
65
26
|
target._body = t
|
|
66
27
|
return target
|
|
67
28
|
}
|
|
68
29
|
|
|
69
30
|
target.footer = function (t) {
|
|
70
|
-
if (typeof t !== 'string') {
|
|
71
|
-
throw new ValidationError('footer must be a string', { code: 'LUMINA_VALIDATION_FOOTER' })
|
|
72
|
-
}
|
|
31
|
+
if (typeof t !== 'string') throw new ValidationError('footer must be a string', { code: 'LUMINA_VALIDATION_FOOTER' })
|
|
73
32
|
target._footer = t
|
|
74
33
|
return target
|
|
75
34
|
}
|
|
@@ -89,12 +48,6 @@ export function applyContentFields(target, initial = {}) {
|
|
|
89
48
|
return target
|
|
90
49
|
}
|
|
91
50
|
|
|
92
|
-
/**
|
|
93
|
-
* Helper: extract the standard content fields as an envelope fragment.
|
|
94
|
-
*
|
|
95
|
-
* @param {object} target A builder that has had applyContentFields called on it.
|
|
96
|
-
* @returns {{ title: string, subtitle: string, body: string, footer: string, contextInfo: object, extraPayload: object }}
|
|
97
|
-
*/
|
|
98
51
|
export function readContentFields(target) {
|
|
99
52
|
return {
|
|
100
53
|
title: target._title,
|
|
@@ -1,100 +1,45 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file builders/button-v2.js
|
|
3
|
-
* @module lumina/builders/button-v2
|
|
4
|
-
*
|
|
5
|
-
* ButtonV2Builder — legacy `buttonsMessage` format. Preserved for users who
|
|
6
|
-
* target WhatsApp clients that do not yet support native flow.
|
|
7
|
-
*
|
|
8
|
-
* Refactored to use the same mixin-based content fields, registry-based
|
|
9
|
-
* button factory, and centralised interactive relay nodes as ButtonBuilder.
|
|
10
|
-
* The hardcoded `headerType: 6` (LOCATION_THUMBNAIL) magic number is now
|
|
11
|
-
* sourced from `proto/enums.HeaderType`.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
1
|
import { applyContentFields } from './base.js'
|
|
15
2
|
import { coerceMediaSource, ensurePlainObject } from '../utils/validator.js'
|
|
16
3
|
import { createInteractiveNodes } from '../proto/relay-nodes.js'
|
|
17
4
|
import { HeaderType } from '../proto/enums.js'
|
|
18
5
|
import { uuid } from '../utils/id.js'
|
|
19
|
-
|
|
20
6
|
import { fetchBuffer } from '../media/fetch.js'
|
|
21
7
|
import { resize } from '../media/image.js'
|
|
22
8
|
|
|
23
|
-
/** @typedef {import('../client/connection.js').Connection} Connection */
|
|
24
|
-
|
|
25
9
|
export class ButtonV2Builder {
|
|
26
|
-
/** @param {Connection} conn */
|
|
27
10
|
constructor(conn) {
|
|
28
11
|
applyContentFields(this)
|
|
29
12
|
this.#conn = conn
|
|
30
|
-
|
|
31
|
-
/** @type {Array<object>} */
|
|
32
13
|
this._buttons = []
|
|
33
|
-
/** @type {string | Buffer | null} */
|
|
34
14
|
this._thumbnailSource = null
|
|
35
|
-
/** @type {object | null} */
|
|
36
15
|
this._data = null
|
|
37
16
|
}
|
|
38
17
|
|
|
39
|
-
|
|
18
|
+
#conn
|
|
40
19
|
|
|
41
|
-
/**
|
|
42
|
-
* Add a legacy button with a display text and (optional) explicit ID.
|
|
43
|
-
* Auto-generates a UUID when `id` is omitted.
|
|
44
|
-
*
|
|
45
|
-
* @param {string} displayText
|
|
46
|
-
* @param {string} [id]
|
|
47
|
-
*/
|
|
48
20
|
button(displayText, id = uuid()) {
|
|
49
|
-
this._buttons.push({
|
|
50
|
-
buttonId: id,
|
|
51
|
-
buttonText: { displayText },
|
|
52
|
-
type: 1,
|
|
53
|
-
})
|
|
21
|
+
this._buttons.push({ buttonId: id, buttonText: { displayText }, type: 1 })
|
|
54
22
|
return this
|
|
55
23
|
}
|
|
56
24
|
|
|
57
|
-
/**
|
|
58
|
-
* Push a raw, already-shaped button object.
|
|
59
|
-
*
|
|
60
|
-
* @param {object} obj
|
|
61
|
-
*/
|
|
62
25
|
rawButton(obj) {
|
|
63
26
|
ensurePlainObject(obj, 'button')
|
|
64
27
|
this._buttons.push(obj)
|
|
65
28
|
return this
|
|
66
29
|
}
|
|
67
30
|
|
|
68
|
-
/**
|
|
69
|
-
* Set the JPEG thumbnail (used by the location-fallback path).
|
|
70
|
-
*
|
|
71
|
-
* @param {string | Buffer} source
|
|
72
|
-
*/
|
|
73
31
|
thumbnail(source) {
|
|
74
32
|
if (!source) throw new TypeError('thumbnail source is required')
|
|
75
33
|
this._thumbnailSource = source
|
|
76
34
|
return this
|
|
77
35
|
}
|
|
78
36
|
|
|
79
|
-
/**
|
|
80
|
-
* Provide a fully-shaped media descriptor that overrides the
|
|
81
|
-
* location-thumbnail fallback entirely.
|
|
82
|
-
*
|
|
83
|
-
* @param {object} obj
|
|
84
|
-
*/
|
|
85
37
|
media(obj) {
|
|
86
38
|
ensurePlainObject(obj, 'media')
|
|
87
39
|
this._data = obj
|
|
88
40
|
return this
|
|
89
41
|
}
|
|
90
42
|
|
|
91
|
-
/**
|
|
92
|
-
* Build the proto message object (without relaying).
|
|
93
|
-
*
|
|
94
|
-
* @param {string} jid
|
|
95
|
-
* @param {object} [opts]
|
|
96
|
-
* @returns {Promise<object>}
|
|
97
|
-
*/
|
|
98
43
|
async build(jid, opts = {}) {
|
|
99
44
|
let jpegThumbnail = null
|
|
100
45
|
if (this._thumbnailSource) {
|
|
@@ -106,18 +51,16 @@ export class ButtonV2Builder {
|
|
|
106
51
|
}
|
|
107
52
|
}
|
|
108
53
|
|
|
109
|
-
const fallback =
|
|
110
|
-
|
|
111
|
-
{
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
},
|
|
120
|
-
}
|
|
54
|
+
const fallback = this._data ?? {
|
|
55
|
+
headerType: HeaderType.LOCATION_THUMBNAIL,
|
|
56
|
+
locationMessage: {
|
|
57
|
+
degreesLatitude: 0,
|
|
58
|
+
degreesLongitude: 0,
|
|
59
|
+
name: this._title,
|
|
60
|
+
address: this._subtitle,
|
|
61
|
+
jpegThumbnail,
|
|
62
|
+
},
|
|
63
|
+
}
|
|
121
64
|
|
|
122
65
|
return this.#conn.generateMessage(
|
|
123
66
|
jid,
|
|
@@ -136,16 +79,8 @@ export class ButtonV2Builder {
|
|
|
136
79
|
)
|
|
137
80
|
}
|
|
138
81
|
|
|
139
|
-
/**
|
|
140
|
-
* Build + relay.
|
|
141
|
-
*
|
|
142
|
-
* @param {string} jid
|
|
143
|
-
* @param {object} [opts]
|
|
144
|
-
*/
|
|
145
82
|
async send(jid, opts = {}) {
|
|
146
|
-
if (this._buttons.length === 0)
|
|
147
|
-
throw new Error('ButtonV2 requires at least one button')
|
|
148
|
-
}
|
|
83
|
+
if (this._buttons.length === 0) throw new Error('ButtonV2 requires at least one button')
|
|
149
84
|
const msg = await this.build(jid, opts)
|
|
150
85
|
await this.#conn.relayMessage(msg.key.remoteJid, msg.message, {
|
|
151
86
|
messageId: msg.key.id,
|