@kyyinfinite/lumina 1.0.6 → 1.0.7
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 +74 -15
- package/src/builders/base.js +13 -4
- package/src/builders/button-v2.js +21 -3
- package/src/builders/button.js +136 -29
- package/src/builders/carousel.js +16 -7
- package/src/builders/index.js +6 -0
- package/src/compat/toolkit.js +145 -0
- package/src/index.js +18 -0
- package/src/utils/socket-to-conn.js +41 -0
package/package.json
CHANGED
package/src/builders/ai-rich.js
CHANGED
|
@@ -12,13 +12,21 @@ import {
|
|
|
12
12
|
import { singleLayout, hscrollLayout, actionRowLayout } from '../proto/layouts.js'
|
|
13
13
|
import { assembleRichResponse } from '../proto/rich-response.js'
|
|
14
14
|
import { MessageType, ImagineType, LayoutKind } from '../proto/enums.js'
|
|
15
|
+
import { isRawSocket, socketToServices } from '../utils/socket-to-conn.js'
|
|
15
16
|
|
|
16
17
|
export class AIRichBuilder {
|
|
17
18
|
#conn
|
|
18
19
|
#proto
|
|
19
20
|
#media
|
|
20
21
|
|
|
22
|
+
/**
|
|
23
|
+
* @param {object} conn Connection instance OR raw Baileys socket (legacy: new AIRich(socket))
|
|
24
|
+
*/
|
|
21
25
|
constructor(conn, proto, media) {
|
|
26
|
+
// ── Auto-detect raw socket (backward compat: new AIRich(socket)) ──────────
|
|
27
|
+
const services = isRawSocket(conn) ? socketToServices(conn) : null
|
|
28
|
+
if (services) { conn = services.conn; proto = services.proto; media = services.media }
|
|
29
|
+
|
|
22
30
|
applyContentFields(this)
|
|
23
31
|
this.#conn = conn
|
|
24
32
|
this.#proto = proto
|
|
@@ -34,6 +42,8 @@ export class AIRichBuilder {
|
|
|
34
42
|
this._quotedParticipant = undefined
|
|
35
43
|
}
|
|
36
44
|
|
|
45
|
+
// ─── Flags ────────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
37
47
|
forwarded(v = true) { this._forwarded = v; return this }
|
|
38
48
|
notification(v) { this._notification = v; return this }
|
|
39
49
|
includesUnifiedResponse(v = true) { this._includesUnifiedResponse = v; return this }
|
|
@@ -45,6 +55,8 @@ export class AIRichBuilder {
|
|
|
45
55
|
return this
|
|
46
56
|
}
|
|
47
57
|
|
|
58
|
+
// ─── Low-level section/submessage injection ───────────────────────────────────
|
|
59
|
+
|
|
48
60
|
submessage(msg) {
|
|
49
61
|
const items = Array.isArray(msg) ? msg : [msg]
|
|
50
62
|
items.forEach((m) => ensureObjectOrArray(m, 'submessage'))
|
|
@@ -52,6 +64,30 @@ export class AIRichBuilder {
|
|
|
52
64
|
return this
|
|
53
65
|
}
|
|
54
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Directly push a raw section object (or array of objects) into the sections list.
|
|
69
|
+
* Useful when you've built a section manually via proto helpers.
|
|
70
|
+
*/
|
|
71
|
+
section(s) {
|
|
72
|
+
const items = Array.isArray(s) ? s : [s]
|
|
73
|
+
for (const item of items) {
|
|
74
|
+
if (typeof item !== 'object' || item === null || Array.isArray(item)) {
|
|
75
|
+
throw new ValidationError('Section must be a plain object or array of plain objects', {
|
|
76
|
+
code: 'LUMINA_VALIDATION_SECTION',
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
this._sections.push(item)
|
|
80
|
+
}
|
|
81
|
+
return this
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** @deprecated use `.submessage()` */
|
|
85
|
+
addSubmessage(msg) { return this.submessage(msg) }
|
|
86
|
+
/** @deprecated use `.section()` */
|
|
87
|
+
addSection(s) { return this.section(s) }
|
|
88
|
+
|
|
89
|
+
// ─── Content builders ─────────────────────────────────────────────────────────
|
|
90
|
+
|
|
55
91
|
async text(text, opts = {}) {
|
|
56
92
|
ensureString(text, 'text')
|
|
57
93
|
const { hyperlink = true, citation = true, latex = true } = opts
|
|
@@ -176,10 +212,10 @@ export class AIRichBuilder {
|
|
|
176
212
|
const resolved = await Promise.all(
|
|
177
213
|
list.map(async (item) => {
|
|
178
214
|
const avatarSrc = item.profileIconUrl ?? item.profile_url ?? item.profile
|
|
179
|
-
const thumbSrc
|
|
215
|
+
const thumbSrc = item.thumbnailUrl ?? item.thumbnail
|
|
180
216
|
const [avatar, thumbnail] = await Promise.all([
|
|
181
217
|
avatarSrc ? this.#media.resolve(avatarSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
|
|
182
|
-
thumbSrc
|
|
218
|
+
thumbSrc ? this.#media.resolve(thumbSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
|
|
183
219
|
])
|
|
184
220
|
return { ...item, avatar, thumbnail }
|
|
185
221
|
}),
|
|
@@ -219,10 +255,10 @@ export class AIRichBuilder {
|
|
|
219
255
|
this._submessages.push({ messageType: MessageType.TEXT, messageText: '[ CANNOT_LOAD_PRODUCT ]' })
|
|
220
256
|
const products = await Promise.all(
|
|
221
257
|
list.map(async (p) => {
|
|
222
|
-
const imgSrc
|
|
258
|
+
const imgSrc = p.image_url ?? p.image
|
|
223
259
|
const iconSrc = p.icon_url ?? p.icon
|
|
224
260
|
const [imageUrl, iconUrl] = await Promise.all([
|
|
225
|
-
imgSrc
|
|
261
|
+
imgSrc ? this.#media.resolve(imgSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
|
|
226
262
|
iconSrc ? this.#media.resolve(iconSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
|
|
227
263
|
])
|
|
228
264
|
return {
|
|
@@ -246,12 +282,12 @@ export class AIRichBuilder {
|
|
|
246
282
|
const primitives = await Promise.all(
|
|
247
283
|
list.map(async (p) => {
|
|
248
284
|
const profileSrc = p.profile_picture_url ?? p.profile_url ?? p.profile
|
|
249
|
-
const thumbSrc
|
|
250
|
-
const footerSrc
|
|
285
|
+
const thumbSrc = p.thumbnail_url ?? p.thumbnail
|
|
286
|
+
const footerSrc = p.footer_icon ?? p.icon
|
|
251
287
|
const [profileUrl, thumbUrl, footerIcon] = await Promise.all([
|
|
252
288
|
profileSrc ? this.#media.resolve(profileSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
|
|
253
|
-
thumbSrc
|
|
254
|
-
footerSrc
|
|
289
|
+
thumbSrc ? this.#media.resolve(thumbSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
|
|
290
|
+
footerSrc ? this.#media.resolve(footerSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
|
|
255
291
|
])
|
|
256
292
|
return postPrimitive({
|
|
257
293
|
title: p.title ?? '', subtitle: p.subtitle ?? '', username: p.username ?? '',
|
|
@@ -285,13 +321,10 @@ export class AIRichBuilder {
|
|
|
285
321
|
}
|
|
286
322
|
const pills = suggestion.map((text) => followUpSuggestionPillPrimitive(text))
|
|
287
323
|
let kind
|
|
288
|
-
if (layout)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
} else {
|
|
293
|
-
kind = scroll ? LayoutKind.HSCROLL : LayoutKind.ACTION_ROW
|
|
294
|
-
}
|
|
324
|
+
if (layout) kind = layout
|
|
325
|
+
else if (pills.length === 1) kind = LayoutKind.SINGLE
|
|
326
|
+
else kind = scroll ? LayoutKind.HSCROLL : LayoutKind.ACTION_ROW
|
|
327
|
+
|
|
295
328
|
if (kind === LayoutKind.SINGLE) {
|
|
296
329
|
this._sections.push(singleLayout(pills[0], { __typename: 'GenAIUnifiedResponseSection' }))
|
|
297
330
|
} else if (kind === LayoutKind.HSCROLL) {
|
|
@@ -302,6 +335,32 @@ export class AIRichBuilder {
|
|
|
302
335
|
return this
|
|
303
336
|
}
|
|
304
337
|
|
|
338
|
+
// ─── Legacy add* aliases (paste / AIRich API) ─────────────────────────────────
|
|
339
|
+
/** @deprecated use `.text()` */
|
|
340
|
+
async addText(text, opts) { return this.text(text, opts) }
|
|
341
|
+
/** @deprecated use `.code()` */
|
|
342
|
+
async addCode(language, code) { return this.code(language, code) }
|
|
343
|
+
/** @deprecated use `.table()` */
|
|
344
|
+
async addTable(table, opts) { return this.table(table, opts) }
|
|
345
|
+
/** @deprecated use `.image()` */
|
|
346
|
+
async addImage(source, opts) { return this.image(source, opts) }
|
|
347
|
+
/** @deprecated use `.video()` */
|
|
348
|
+
async addVideo(source, opts) { return this.video(source, opts) }
|
|
349
|
+
/** @deprecated use `.source()` */
|
|
350
|
+
async addSource(sources) { return this.source(sources) }
|
|
351
|
+
/** @deprecated use `.reels()` */
|
|
352
|
+
async addReels(items) { return this.reels(items) }
|
|
353
|
+
/** @deprecated use `.product()` */
|
|
354
|
+
async addProduct(item) { return this.product(item) }
|
|
355
|
+
/** @deprecated use `.post()` */
|
|
356
|
+
async addPost(item) { return this.post(item) }
|
|
357
|
+
/** @deprecated use `.tip()` */
|
|
358
|
+
async addTip(text) { return this.tip(text) }
|
|
359
|
+
/** @deprecated use `.suggest()` */
|
|
360
|
+
async addSuggest(suggestion, opts) { return this.suggest(suggestion, opts) }
|
|
361
|
+
|
|
362
|
+
// ─── Build & send ─────────────────────────────────────────────────────────────
|
|
363
|
+
|
|
305
364
|
async build(opts = {}) {
|
|
306
365
|
return assembleRichResponse({
|
|
307
366
|
title: this._title,
|
package/src/builders/base.js
CHANGED
|
@@ -45,15 +45,24 @@ export function applyContentFields(target, initial = {}) {
|
|
|
45
45
|
return target
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// ─── Legacy aliases (paste / BaseBuilder API) ───────────────────────────────
|
|
49
|
+
target.setTitle = target.title
|
|
50
|
+
target.setSubtitle = target.subtitle
|
|
51
|
+
target.setBody = target.body
|
|
52
|
+
target.setFooter = target.footer
|
|
53
|
+
target.setContextInfo = target.contextInfo
|
|
54
|
+
/** @deprecated use `.payload()` */
|
|
55
|
+
target.addPayload = target.payload
|
|
56
|
+
|
|
48
57
|
return target
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
export function readContentFields(target) {
|
|
52
61
|
return {
|
|
53
|
-
title:
|
|
54
|
-
subtitle:
|
|
55
|
-
body:
|
|
56
|
-
footer:
|
|
62
|
+
title: target._title,
|
|
63
|
+
subtitle: target._subtitle,
|
|
64
|
+
body: target._body,
|
|
65
|
+
footer: target._footer,
|
|
57
66
|
contextInfo: target._contextInfo,
|
|
58
67
|
extraPayload: target._extraPayload,
|
|
59
68
|
}
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import { applyContentFields } from './base.js'
|
|
2
|
-
import {
|
|
2
|
+
import { ensurePlainObject } from '../utils/validator.js'
|
|
3
3
|
import { createInteractiveNodes } from '../proto/relay-nodes.js'
|
|
4
4
|
import { HeaderType } from '../proto/enums.js'
|
|
5
5
|
import { uuid } from '../utils/id.js'
|
|
6
6
|
import { fetchBuffer } from '../media/fetch.js'
|
|
7
7
|
import { resize } from '../media/image.js'
|
|
8
|
+
import { isRawSocket, socketToServices } from '../utils/socket-to-conn.js'
|
|
8
9
|
|
|
9
10
|
export class ButtonV2Builder {
|
|
11
|
+
#conn
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {object} conn Connection instance OR raw Baileys socket (legacy)
|
|
15
|
+
*/
|
|
10
16
|
constructor(conn) {
|
|
17
|
+
// ── Auto-detect raw socket (backward compat: new ButtonV2(socket)) ─────────
|
|
18
|
+
const services = isRawSocket(conn) ? socketToServices(conn) : null
|
|
19
|
+
if (services) conn = services.conn
|
|
20
|
+
|
|
11
21
|
applyContentFields(this)
|
|
12
22
|
this.#conn = conn
|
|
13
23
|
this._buttons = []
|
|
@@ -15,8 +25,6 @@ export class ButtonV2Builder {
|
|
|
15
25
|
this._data = null
|
|
16
26
|
}
|
|
17
27
|
|
|
18
|
-
#conn
|
|
19
|
-
|
|
20
28
|
button(displayText, id = uuid()) {
|
|
21
29
|
this._buttons.push({ buttonId: id, buttonText: { displayText }, type: 1 })
|
|
22
30
|
return this
|
|
@@ -40,6 +48,16 @@ export class ButtonV2Builder {
|
|
|
40
48
|
return this
|
|
41
49
|
}
|
|
42
50
|
|
|
51
|
+
// ─── Legacy aliases (paste / ButtonV2 API) ───────────────────────────────────
|
|
52
|
+
/** @deprecated use `.button()` */
|
|
53
|
+
addButton(displayText, id = uuid()) { return this.button(displayText, id) }
|
|
54
|
+
/** @deprecated use `.rawButton()` */
|
|
55
|
+
addRawButton(obj) { return this.rawButton(obj) }
|
|
56
|
+
/** @deprecated use `.thumbnail()` */
|
|
57
|
+
setThumbnail(source) { return this.thumbnail(source) }
|
|
58
|
+
/** @deprecated use `.media()` */
|
|
59
|
+
setMedia(obj) { return this.media(obj) }
|
|
60
|
+
|
|
43
61
|
async build(jid, opts = {}) {
|
|
44
62
|
let jpegThumbnail = null
|
|
45
63
|
if (this._thumbnailSource) {
|
package/src/builders/button.js
CHANGED
|
@@ -2,6 +2,7 @@ import { applyContentFields } from './base.js'
|
|
|
2
2
|
import { coerceMediaSource, ensurePlainObject } from '../utils/validator.js'
|
|
3
3
|
import { createInteractiveNodes } from '../proto/relay-nodes.js'
|
|
4
4
|
import { SimpleButtonType } from '../proto/enums.js'
|
|
5
|
+
import { isRawSocket, socketToServices } from '../utils/socket-to-conn.js'
|
|
5
6
|
|
|
6
7
|
class SelectionBuilder {
|
|
7
8
|
constructor() {
|
|
@@ -9,10 +10,7 @@ class SelectionBuilder {
|
|
|
9
10
|
this._sections = []
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
title(t) {
|
|
13
|
-
this._title = t
|
|
14
|
-
return this
|
|
15
|
-
}
|
|
13
|
+
title(t) { this._title = t; return this }
|
|
16
14
|
|
|
17
15
|
section(title, builder) {
|
|
18
16
|
const section = { title, highlight_label: '', rows: [] }
|
|
@@ -27,10 +25,7 @@ class SelectionBuilder {
|
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
class SectionBuilder {
|
|
30
|
-
constructor(rowsRef) {
|
|
31
|
-
this._rows = rowsRef
|
|
32
|
-
}
|
|
33
|
-
|
|
28
|
+
constructor(rowsRef) { this._rows = rowsRef }
|
|
34
29
|
row(header, title, description, id) {
|
|
35
30
|
this._rows.push({ header, title, description, id })
|
|
36
31
|
return this
|
|
@@ -38,7 +33,18 @@ class SectionBuilder {
|
|
|
38
33
|
}
|
|
39
34
|
|
|
40
35
|
export class ButtonBuilder {
|
|
36
|
+
#conn
|
|
37
|
+
#proto
|
|
38
|
+
#media
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {object} conn Connection instance OR raw Baileys socket (legacy)
|
|
42
|
+
*/
|
|
41
43
|
constructor(conn, proto, media) {
|
|
44
|
+
// ── Auto-detect raw socket (backward compat: new Button(socket)) ──────────
|
|
45
|
+
const services = isRawSocket(conn) ? socketToServices(conn) : null
|
|
46
|
+
if (services) { conn = services.conn; proto = services.proto; media = services.media }
|
|
47
|
+
|
|
42
48
|
applyContentFields(this)
|
|
43
49
|
this.#conn = conn
|
|
44
50
|
this.#proto = proto
|
|
@@ -46,11 +52,12 @@ export class ButtonBuilder {
|
|
|
46
52
|
this._buttons = []
|
|
47
53
|
this._data = null
|
|
48
54
|
this._params = {}
|
|
55
|
+
// For legacy makeRow / makeSection support
|
|
56
|
+
this._currentSelectionIndex = -1
|
|
57
|
+
this._currentSectionIndex = -1
|
|
49
58
|
}
|
|
50
59
|
|
|
51
|
-
|
|
52
|
-
#proto
|
|
53
|
-
#media
|
|
60
|
+
// ─── Media setters ───────────────────────────────────────────────────────────
|
|
54
61
|
|
|
55
62
|
media(type, source, opts = {}) {
|
|
56
63
|
if (!['image', 'video', 'document'].includes(type)) {
|
|
@@ -61,10 +68,44 @@ export class ButtonBuilder {
|
|
|
61
68
|
return this
|
|
62
69
|
}
|
|
63
70
|
|
|
64
|
-
image(source, opts)
|
|
65
|
-
video(source, opts)
|
|
71
|
+
image(source, opts) { return this.media('image', source, opts) }
|
|
72
|
+
video(source, opts) { return this.media('video', source, opts) }
|
|
66
73
|
document(source, opts) { return this.media('document', source, opts) }
|
|
67
74
|
|
|
75
|
+
/** @deprecated use `.media(obj)` – raw object form */
|
|
76
|
+
setMedia(obj) {
|
|
77
|
+
ensurePlainObject(obj, 'media')
|
|
78
|
+
this._data = obj
|
|
79
|
+
return this
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Legacy set* shims (image / video / document with url-or-buffer) ──────────
|
|
83
|
+
setImage(path, opts = {}) {
|
|
84
|
+
if (!path) throw new Error('Url or buffer needed')
|
|
85
|
+
this._data = Buffer.isBuffer(path) ? { image: path, ...opts } : { image: { url: path }, ...opts }
|
|
86
|
+
return this
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setVideo(path, opts = {}) {
|
|
90
|
+
if (!path) throw new Error('Url or buffer needed')
|
|
91
|
+
this._data = Buffer.isBuffer(path) ? { video: path, ...opts } : { video: { url: path }, ...opts }
|
|
92
|
+
return this
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
setDocument(path, opts = {}) {
|
|
96
|
+
if (!path) throw new Error('Url or buffer needed')
|
|
97
|
+
this._data = Buffer.isBuffer(path) ? { document: path, ...opts } : { document: { url: path }, ...opts }
|
|
98
|
+
return this
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─── Params / params ─────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
params(obj) { ensurePlainObject(obj, 'params'); this._params = obj; return this }
|
|
104
|
+
/** @deprecated use `.params()` */
|
|
105
|
+
setParams(obj) { return this.params(obj) }
|
|
106
|
+
|
|
107
|
+
// ─── Raw button ──────────────────────────────────────────────────────────────
|
|
108
|
+
|
|
68
109
|
button(name, params, extra = {}) {
|
|
69
110
|
this._buttons.push({
|
|
70
111
|
name,
|
|
@@ -74,27 +115,34 @@ export class ButtonBuilder {
|
|
|
74
115
|
return this
|
|
75
116
|
}
|
|
76
117
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
118
|
+
/** @deprecated use `.button()` */
|
|
119
|
+
addButton(name, params) { return this.button(name, params) }
|
|
120
|
+
|
|
121
|
+
// ─── Simple buttons ──────────────────────────────────────────────────────────
|
|
82
122
|
|
|
83
123
|
simpleButton(kind, displayText, id, opts = {}) {
|
|
84
124
|
const name = SimpleButtonType[kind]
|
|
85
125
|
if (!name) throw new TypeError(`unknown simple button kind: ${kind}`)
|
|
86
|
-
this._buttons.push({
|
|
87
|
-
name,
|
|
88
|
-
buttonParamsJson: JSON.stringify({ display_text: displayText, id, ...opts }),
|
|
89
|
-
})
|
|
126
|
+
this._buttons.push({ name, buttonParamsJson: JSON.stringify({ display_text: displayText, id, ...opts }) })
|
|
90
127
|
return this
|
|
91
128
|
}
|
|
92
129
|
|
|
93
|
-
reply(displayText, id, opts)
|
|
94
|
-
call(displayText, id, opts)
|
|
95
|
-
reminder(displayText, id, opts)
|
|
96
|
-
cancelReminder(displayText, id, opts)
|
|
97
|
-
address(displayText, id, opts)
|
|
130
|
+
reply(displayText, id, opts) { return this.simpleButton('reply', displayText, id, opts) }
|
|
131
|
+
call(displayText, id, opts) { return this.simpleButton('call', displayText, id, opts) }
|
|
132
|
+
reminder(displayText, id, opts) { return this.simpleButton('reminder', displayText, id, opts) }
|
|
133
|
+
cancelReminder(displayText, id, opts){ return this.simpleButton('cancelReminder', displayText, id, opts) }
|
|
134
|
+
address(displayText, id, opts) { return this.simpleButton('address', displayText, id, opts) }
|
|
135
|
+
|
|
136
|
+
/** @deprecated use `.reply()` */
|
|
137
|
+
addReply(displayText, id, opts) { return this.reply(displayText, id, opts) }
|
|
138
|
+
/** @deprecated use `.call()` */
|
|
139
|
+
addCall(displayText, id, opts) { return this.call(displayText, id, opts) }
|
|
140
|
+
/** @deprecated use `.reminder()` */
|
|
141
|
+
addReminder(displayText, id, opts) { return this.reminder(displayText, id, opts) }
|
|
142
|
+
/** @deprecated use `.cancelReminder()` */
|
|
143
|
+
addCancelReminder(displayText, id, opts) { return this.cancelReminder(displayText, id, opts) }
|
|
144
|
+
/** @deprecated use `.address()` */
|
|
145
|
+
addAddress(displayText, id, opts) { return this.address(displayText, id, opts) }
|
|
98
146
|
|
|
99
147
|
url(displayText, url, opts = {}) {
|
|
100
148
|
this._buttons.push({
|
|
@@ -122,6 +170,19 @@ export class ButtonBuilder {
|
|
|
122
170
|
return this
|
|
123
171
|
}
|
|
124
172
|
|
|
173
|
+
/** @deprecated use `.url()` */
|
|
174
|
+
addUrl(displayText, url, webview_interaction = false, opts = {}) {
|
|
175
|
+
return this.url(displayText, url, { webview_interaction, ...opts })
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** @deprecated use `.copy()` */
|
|
179
|
+
addCopy(displayText, copyCode, opts = {}) { return this.copy(displayText, copyCode, opts) }
|
|
180
|
+
|
|
181
|
+
/** @deprecated use `.location()` */
|
|
182
|
+
addLocation(opts = {}) { return this.location(opts) }
|
|
183
|
+
|
|
184
|
+
// ─── Selection (new fluent builder API) ──────────────────────────────────────
|
|
185
|
+
|
|
125
186
|
selection(title, builder, extra = {}) {
|
|
126
187
|
const sel = new SelectionBuilder()
|
|
127
188
|
sel.title(title)
|
|
@@ -130,11 +191,57 @@ export class ButtonBuilder {
|
|
|
130
191
|
return this
|
|
131
192
|
}
|
|
132
193
|
|
|
133
|
-
|
|
134
|
-
|
|
194
|
+
// ─── Legacy selection API (paste compatible) ──────────────────────────────────
|
|
195
|
+
// addSelection() / makeSection() / makeRow() use mutable cursor state
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @deprecated Prefer `.selection(title, cb)` builder style.
|
|
199
|
+
* Adds a single_select button and sets cursor for makeSection/makeRow.
|
|
200
|
+
*/
|
|
201
|
+
addSelection(title, options = {}) {
|
|
202
|
+
this._buttons.push({
|
|
203
|
+
...options,
|
|
204
|
+
name: 'single_select',
|
|
205
|
+
buttonParamsJson: JSON.stringify({ title, sections: [] }),
|
|
206
|
+
})
|
|
207
|
+
this._currentSelectionIndex = this._buttons.length - 1
|
|
208
|
+
this._currentSectionIndex = -1
|
|
135
209
|
return this
|
|
136
210
|
}
|
|
137
211
|
|
|
212
|
+
/**
|
|
213
|
+
* @deprecated Append a section to the current selection (cursor must be set by addSelection).
|
|
214
|
+
*/
|
|
215
|
+
makeSection(title = '', highlight_label = '') {
|
|
216
|
+
if (this._currentSelectionIndex === -1) throw new Error('You need to create a selection first')
|
|
217
|
+
const params = JSON.parse(this._buttons[this._currentSelectionIndex].buttonParamsJson)
|
|
218
|
+
params.sections.push({ title, highlight_label, rows: [] })
|
|
219
|
+
this._currentSectionIndex = params.sections.length - 1
|
|
220
|
+
this._buttons[this._currentSelectionIndex].buttonParamsJson = JSON.stringify(params)
|
|
221
|
+
return this
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @deprecated Append a row to the current section.
|
|
226
|
+
*/
|
|
227
|
+
makeRow(header = '', title = '', description = '', id = '') {
|
|
228
|
+
if (this._currentSelectionIndex === -1 || this._currentSectionIndex === -1) {
|
|
229
|
+
throw new Error('You need to create a selection and a section first')
|
|
230
|
+
}
|
|
231
|
+
const params = JSON.parse(this._buttons[this._currentSelectionIndex].buttonParamsJson)
|
|
232
|
+
params.sections[this._currentSectionIndex].rows.push({ header, title, description, id })
|
|
233
|
+
this._buttons[this._currentSelectionIndex].buttonParamsJson = JSON.stringify(params)
|
|
234
|
+
return this
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ─── Utility ─────────────────────────────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
clear() { this._buttons = []; return this }
|
|
240
|
+
/** @deprecated use `.clear()` */
|
|
241
|
+
clearButtons() { return this.clear() }
|
|
242
|
+
|
|
243
|
+
// ─── Build & send ─────────────────────────────────────────────────────────────
|
|
244
|
+
|
|
138
245
|
async toCard() {
|
|
139
246
|
let mediaPayload = {}
|
|
140
247
|
if (this._data) {
|
package/src/builders/carousel.js
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import { applyContentFields } from './base.js'
|
|
2
2
|
import { createInteractiveNodes } from '../proto/relay-nodes.js'
|
|
3
3
|
import { CardBuilder } from './card.js'
|
|
4
|
+
import { isRawSocket, socketToServices } from '../utils/socket-to-conn.js'
|
|
4
5
|
|
|
5
6
|
export class CarouselBuilder {
|
|
7
|
+
#conn
|
|
8
|
+
#proto
|
|
9
|
+
#media
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {object} conn Connection instance OR raw Baileys socket (legacy)
|
|
13
|
+
*/
|
|
6
14
|
constructor(conn, proto, media) {
|
|
15
|
+
// ── Auto-detect raw socket (backward compat: new Carousel(socket)) ─────────
|
|
16
|
+
const services = isRawSocket(conn) ? socketToServices(conn) : null
|
|
17
|
+
if (services) { conn = services.conn; proto = services.proto; media = services.media }
|
|
18
|
+
|
|
7
19
|
applyContentFields(this, { body: '', footer: '' })
|
|
8
20
|
this.#conn = conn
|
|
9
21
|
this.#proto = proto
|
|
@@ -11,10 +23,6 @@ export class CarouselBuilder {
|
|
|
11
23
|
this._cards = []
|
|
12
24
|
}
|
|
13
25
|
|
|
14
|
-
#conn
|
|
15
|
-
#proto
|
|
16
|
-
#media
|
|
17
|
-
|
|
18
26
|
newCard() {
|
|
19
27
|
return new CardBuilder(this.#conn)
|
|
20
28
|
}
|
|
@@ -30,9 +38,10 @@ export class CarouselBuilder {
|
|
|
30
38
|
return this
|
|
31
39
|
}
|
|
32
40
|
|
|
33
|
-
cards(...cards) {
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
cards(...cards) { return this.card(cards.flat()) }
|
|
42
|
+
|
|
43
|
+
/** @deprecated use `.card()` */
|
|
44
|
+
addCard(card) { return this.card(card) }
|
|
36
45
|
|
|
37
46
|
async build(jid, opts = {}) {
|
|
38
47
|
return this.#conn.generateMessage(
|
package/src/builders/index.js
CHANGED
|
@@ -5,3 +5,9 @@ export { CardBuilder } from './card.js'
|
|
|
5
5
|
export { AIRichBuilder } from './ai-rich.js'
|
|
6
6
|
export { StickerBuilder } from './sticker.js'
|
|
7
7
|
export { applyContentFields, readContentFields } from './base.js'
|
|
8
|
+
|
|
9
|
+
// Legacy aliases
|
|
10
|
+
export { ButtonBuilder as Button } from './button.js'
|
|
11
|
+
export { ButtonV2Builder as ButtonV2 } from './button-v2.js'
|
|
12
|
+
export { CarouselBuilder as Carousel } from './carousel.js'
|
|
13
|
+
export { AIRichBuilder as AIRich } from './ai-rich.js'
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toolkit — backward-compatibility shim matching the original monolithic library's
|
|
3
|
+
* static helper class. All methods delegate to the modular Lumina internals.
|
|
4
|
+
*
|
|
5
|
+
* Usage (legacy):
|
|
6
|
+
* import { Toolkit } from '@kyyinfinite/lumina'
|
|
7
|
+
* const url = await Toolkit.toUrl(sock, buffer, 'image')
|
|
8
|
+
*/
|
|
9
|
+
import { extractInlineEntities } from '../parsers/inline-entity.js'
|
|
10
|
+
import { resize as _resize } from '../media/image.js'
|
|
11
|
+
import { resolveDeep } from '../utils/promise.js'
|
|
12
|
+
import { fetchBuffer as _fetchBuffer } from '../media/fetch.js'
|
|
13
|
+
import { getMp4Duration as _getMp4Duration, extractThumbnail as _extractThumbnail } from '../media/video.js'
|
|
14
|
+
import { isRawSocket, socketToServices } from '../utils/socket-to-conn.js'
|
|
15
|
+
import { resolveMedia as _resolveMedia } from '../media/resolver.js'
|
|
16
|
+
import { uploadToWhatsApp } from '../media/uploader.js'
|
|
17
|
+
|
|
18
|
+
function getConn(client) {
|
|
19
|
+
if (isRawSocket(client)) return socketToServices(client).conn
|
|
20
|
+
return client // already a Connection
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class Toolkit {
|
|
24
|
+
// ─── Inline entity extraction ──────────────────────────────────────────────
|
|
25
|
+
/**
|
|
26
|
+
* Extract hyperlinks / citations / latex from markdown-like text.
|
|
27
|
+
* Returns the same shape as the original: { text, ie, inline_entities }
|
|
28
|
+
*/
|
|
29
|
+
static extractIE(text, opts = {}) {
|
|
30
|
+
const { text: rewritten, entities, metadata } = extractInlineEntities(text, opts)
|
|
31
|
+
return { text: rewritten, ie: entities, inline_entities: metadata }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ─── Image ────────────────────────────────────────────────────────────────
|
|
35
|
+
/**
|
|
36
|
+
* Resize a buffer to (x, y) using sharp.
|
|
37
|
+
* @param {Buffer} buffer
|
|
38
|
+
* @param {number} x width
|
|
39
|
+
* @param {number} y height
|
|
40
|
+
* @param {string} [fit='cover']
|
|
41
|
+
*/
|
|
42
|
+
static async resize(buffer, x, y, fit = 'cover') {
|
|
43
|
+
return _resize(buffer, { width: x, height: y, fit, format: 'png' })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─── Promise utility ──────────────────────────────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* Recursively resolve all Promises inside a nested object / array.
|
|
49
|
+
*/
|
|
50
|
+
static async waitAllPromises(input) {
|
|
51
|
+
return resolveDeep(input)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─── Fetch ────────────────────────────────────────────────────────────────
|
|
55
|
+
/**
|
|
56
|
+
* Download a URL and return a Buffer. Returns Buffer.alloc(0) on error when silent=true.
|
|
57
|
+
* @param {string} url
|
|
58
|
+
* @param {object} [options] fetch options
|
|
59
|
+
* @param {object} [meta]
|
|
60
|
+
* @param {boolean} [meta.silent=true]
|
|
61
|
+
*/
|
|
62
|
+
static async fetchBuffer(url, options = {}, { silent = true } = {}) {
|
|
63
|
+
return _fetchBuffer(url, { ...options, silent })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ─── Media upload ─────────────────────────────────────────────────────────
|
|
67
|
+
/**
|
|
68
|
+
* Upload a buffer / URL to WhatsApp servers and return the CDN URL.
|
|
69
|
+
* @param {object} _client Raw Baileys socket or Connection
|
|
70
|
+
* @param {Buffer|string} path Buffer or local/remote URL
|
|
71
|
+
* @param {string} [mediaType='document']
|
|
72
|
+
*/
|
|
73
|
+
static async toUrl(_client, path, mediaType = 'document') {
|
|
74
|
+
if (!path) throw new Error('Url or buffer needed')
|
|
75
|
+
const conn = getConn(_client)
|
|
76
|
+
const source = Buffer.isBuffer(path) ? path : { url: path }
|
|
77
|
+
return uploadToWhatsApp(conn, source, mediaType)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─── Media resolver ───────────────────────────────────────────────────────
|
|
81
|
+
/**
|
|
82
|
+
* Resolve media to a URL, Buffer, or base64 string.
|
|
83
|
+
* @param {object} _client Raw Baileys socket or Connection
|
|
84
|
+
* @param {Buffer|string|Array} media
|
|
85
|
+
* @param {string} [mediaType='image']
|
|
86
|
+
* @param {object} [opts]
|
|
87
|
+
* @param {boolean} [opts.resolveUrl=false]
|
|
88
|
+
* @param {boolean} [opts.resolveWAUrl=false]
|
|
89
|
+
* @param {'url'|'buffer'|'base64'} [opts.result='url']
|
|
90
|
+
* @param {boolean} [opts.resize=false]
|
|
91
|
+
* @param {number} [opts.width=300]
|
|
92
|
+
* @param {number} [opts.height=300]
|
|
93
|
+
*/
|
|
94
|
+
static async resolveMedia(_client, media, mediaType = 'image', opts = {}) {
|
|
95
|
+
const { result = 'url', resize: doResize = false, width = 300, height = 300 } = opts
|
|
96
|
+
const conn = getConn(_client)
|
|
97
|
+
|
|
98
|
+
let strategy
|
|
99
|
+
if (result === 'buffer') strategy = 'buffer'
|
|
100
|
+
else if (result === 'base64') strategy = 'base64'
|
|
101
|
+
else strategy = 'auto'
|
|
102
|
+
|
|
103
|
+
const resizeOpts = doResize ? { width, height, fit: 'cover' } : undefined
|
|
104
|
+
|
|
105
|
+
if (Array.isArray(media)) {
|
|
106
|
+
return Promise.all(
|
|
107
|
+
media.map((m) => _resolveMedia(conn, m, { mediaType, strategy, resize: resizeOpts }))
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return _resolveMedia(conn, media, { mediaType, strategy, resize: resizeOpts })
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── Video ────────────────────────────────────────────────────────────────
|
|
115
|
+
/**
|
|
116
|
+
* Parse MP4 duration from a buffer (in seconds). Returns 0 on error when silent=true.
|
|
117
|
+
*/
|
|
118
|
+
static getMp4Duration(buffer, { silent = true } = {}) {
|
|
119
|
+
return _getMp4Duration(buffer, { silent })
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Extract a frame from an MP4 buffer as PNG.
|
|
124
|
+
* @param {Buffer} videoBuffer
|
|
125
|
+
* @param {object} [opts]
|
|
126
|
+
* @param {number} [opts.time] Timestamp in seconds (default: 20% of duration)
|
|
127
|
+
* @param {'buffer'|'base64'} [opts.result='buffer']
|
|
128
|
+
* @param {boolean} [opts.resize=true]
|
|
129
|
+
* @param {number} [opts.width=300]
|
|
130
|
+
* @param {number} [opts.height=300]
|
|
131
|
+
* @param {boolean} [opts.silent=true]
|
|
132
|
+
*/
|
|
133
|
+
static async getMp4Preview(videoBuffer, opts = {}) {
|
|
134
|
+
const {
|
|
135
|
+
time, result = 'buffer', resize: doResize = true,
|
|
136
|
+
width = 300, height = 300, silent = true,
|
|
137
|
+
} = opts
|
|
138
|
+
return _extractThumbnail(videoBuffer, {
|
|
139
|
+
time, result,
|
|
140
|
+
resizeOutput: doResize, width, height, silent,
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export default Toolkit
|
package/src/index.js
CHANGED
|
@@ -29,9 +29,12 @@ import {
|
|
|
29
29
|
import { singleLayout, hscrollLayout, actionRowLayout, layoutFor } from './proto/layouts.js'
|
|
30
30
|
import { LuminaError, ValidationError, MediaError, ProtoError, ConnectionError, ProtocolError } from './errors.js'
|
|
31
31
|
import { createLogger } from './utils/logger.js'
|
|
32
|
+
import { Toolkit } from './compat/toolkit.js'
|
|
32
33
|
|
|
33
34
|
export const VERSION = '1.0.0'
|
|
34
35
|
|
|
36
|
+
// ─── Primary exports ──────────────────────────────────────────────────────────
|
|
37
|
+
|
|
35
38
|
export {
|
|
36
39
|
Bot, Connection,
|
|
37
40
|
ButtonBuilder, ButtonV2Builder, CarouselBuilder, CardBuilder, AIRichBuilder, StickerBuilder,
|
|
@@ -49,6 +52,21 @@ export {
|
|
|
49
52
|
singleLayout, hscrollLayout, actionRowLayout, layoutFor,
|
|
50
53
|
LuminaError, ValidationError, MediaError, ProtoError, ConnectionError, ProtocolError,
|
|
51
54
|
createLogger,
|
|
55
|
+
Toolkit,
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
// ─── Legacy class-name aliases (paste / monolithic library API) ───────────────
|
|
59
|
+
// Allows code written for the original format to work without changes:
|
|
60
|
+
// import { AIRich, Button, ButtonV2, Carousel } from '@kyyinfinite/lumina'
|
|
61
|
+
// new AIRich(sock), new Button(sock), etc.
|
|
62
|
+
|
|
63
|
+
/** @deprecated Use AIRichBuilder */
|
|
64
|
+
export const AIRich = AIRichBuilder
|
|
65
|
+
/** @deprecated Use ButtonBuilder */
|
|
66
|
+
export const Button = ButtonBuilder
|
|
67
|
+
/** @deprecated Use ButtonV2Builder */
|
|
68
|
+
export const ButtonV2 = ButtonV2Builder
|
|
69
|
+
/** @deprecated Use CarouselBuilder */
|
|
70
|
+
export const Carousel = CarouselBuilder
|
|
71
|
+
|
|
54
72
|
export default Bot
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Connection } from '../client/connection.js'
|
|
2
|
+
import { MediaService } from '../services/media-service.js'
|
|
3
|
+
import { ProtoService } from '../services/proto-service.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* JID used by the original monolithic library for media uploads.
|
|
7
|
+
* Kept as default so legacy code (new AIRich(socket)) works without extra config.
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_UPLOAD_JID = '@newsletter'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check whether `v` looks like a raw Baileys socket (has relayMessage + ev).
|
|
13
|
+
*/
|
|
14
|
+
export function isRawSocket(v) {
|
|
15
|
+
return (
|
|
16
|
+
v != null &&
|
|
17
|
+
typeof v === 'object' &&
|
|
18
|
+
typeof v.relayMessage === 'function' &&
|
|
19
|
+
v.ev != null
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Wrap a raw Baileys socket into the three service objects that Lumina builders expect.
|
|
25
|
+
* Returns null if `socketOrConn` is already a Connection (not a raw socket).
|
|
26
|
+
*
|
|
27
|
+
* @param {object} socketOrConn Raw Baileys socket or existing Connection
|
|
28
|
+
* @param {object} [opts]
|
|
29
|
+
* @param {string} [opts.uploadJid] JID used for media upload (defaults to @newsletter)
|
|
30
|
+
* @returns {{ conn: Connection, media: MediaService, proto: ProtoService } | null}
|
|
31
|
+
*/
|
|
32
|
+
export function socketToServices(socketOrConn, opts = {}) {
|
|
33
|
+
if (!isRawSocket(socketOrConn)) return null
|
|
34
|
+
const uploadJid = opts.uploadJid ?? DEFAULT_UPLOAD_JID
|
|
35
|
+
const conn = new Connection(socketOrConn, { uploadJid })
|
|
36
|
+
const media = new MediaService(conn, { uploadJid })
|
|
37
|
+
const proto = new ProtoService(conn)
|
|
38
|
+
return { conn, media, proto }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default { isRawSocket, socketToServices }
|