@kyyinfinite/lumina 1.0.5 → 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 +120 -211
- 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
|
@@ -1,61 +1,53 @@
|
|
|
1
|
-
|
|
2
1
|
import { applyContentFields } from './base.js'
|
|
3
2
|
import { ensureObjectOrArray, ensureString, ensureStringArray } from '../utils/validator.js'
|
|
4
3
|
import { ValidationError } from '../errors.js'
|
|
5
|
-
|
|
6
4
|
import { extractInlineEntities } from '../parsers/inline-entity.js'
|
|
7
5
|
import { tokenizeCode } from '../parsers/code-tokenizer.js'
|
|
8
6
|
import { toTableMetadata } from '../parsers/table-metadata.js'
|
|
9
|
-
|
|
10
7
|
import {
|
|
11
|
-
markdownTextPrimitive,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
searchResultPrimitive,
|
|
15
|
-
reelPrimitive,
|
|
16
|
-
imaginePrimitive,
|
|
17
|
-
productCardPrimitive,
|
|
18
|
-
postPrimitive,
|
|
19
|
-
metadataTextPrimitive,
|
|
20
|
-
followUpSuggestionPillPrimitive,
|
|
21
|
-
shapeSourceEntry,
|
|
22
|
-
shapeReelEntry,
|
|
8
|
+
markdownTextPrimitive, codePrimitive, tablePrimitive, searchResultPrimitive,
|
|
9
|
+
reelPrimitive, imaginePrimitive, productCardPrimitive, postPrimitive,
|
|
10
|
+
metadataTextPrimitive, followUpSuggestionPillPrimitive, shapeSourceEntry, shapeReelEntry,
|
|
23
11
|
} from '../proto/primitives.js'
|
|
24
12
|
import { singleLayout, hscrollLayout, actionRowLayout } from '../proto/layouts.js'
|
|
25
13
|
import { assembleRichResponse } from '../proto/rich-response.js'
|
|
26
14
|
import { MessageType, ImagineType, LayoutKind } from '../proto/enums.js'
|
|
27
|
-
|
|
15
|
+
import { isRawSocket, socketToServices } from '../utils/socket-to-conn.js'
|
|
28
16
|
|
|
29
17
|
export class AIRichBuilder {
|
|
18
|
+
#conn
|
|
19
|
+
#proto
|
|
20
|
+
#media
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {object} conn Connection instance OR raw Baileys socket (legacy: new AIRich(socket))
|
|
24
|
+
*/
|
|
30
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
|
+
|
|
31
30
|
applyContentFields(this)
|
|
32
31
|
this.#conn = conn
|
|
33
32
|
this.#proto = proto
|
|
34
33
|
this.#media = media
|
|
35
|
-
|
|
36
34
|
this._submessages = []
|
|
37
35
|
this._sections = []
|
|
38
36
|
this._richResponseSources = []
|
|
39
|
-
|
|
40
37
|
this._forwarded = true
|
|
41
38
|
this._notification = false
|
|
42
39
|
this._includesUnifiedResponse = true
|
|
43
40
|
this._includesSubmessages = true
|
|
44
|
-
this._quoted
|
|
45
|
-
this._quotedParticipant
|
|
41
|
+
this._quoted = undefined
|
|
42
|
+
this._quotedParticipant = undefined
|
|
46
43
|
}
|
|
47
44
|
|
|
45
|
+
// ─── Flags ────────────────────────────────────────────────────────────────────
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
notification(v) {
|
|
56
|
-
this._notification = v
|
|
57
|
-
return this
|
|
58
|
-
}
|
|
47
|
+
forwarded(v = true) { this._forwarded = v; return this }
|
|
48
|
+
notification(v) { this._notification = v; return this }
|
|
49
|
+
includesUnifiedResponse(v = true) { this._includesUnifiedResponse = v; return this }
|
|
50
|
+
includesSubmessages(v = true) { this._includesSubmessages = v; return this }
|
|
59
51
|
|
|
60
52
|
quoted(quoted, quotedParticipant) {
|
|
61
53
|
this._quoted = quoted
|
|
@@ -63,15 +55,7 @@ export class AIRichBuilder {
|
|
|
63
55
|
return this
|
|
64
56
|
}
|
|
65
57
|
|
|
66
|
-
|
|
67
|
-
this._includesUnifiedResponse = v
|
|
68
|
-
return this
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
includesSubmessages(v = true) {
|
|
72
|
-
this._includesSubmessages = v
|
|
73
|
-
return this
|
|
74
|
-
}
|
|
58
|
+
// ─── Low-level section/submessage injection ───────────────────────────────────
|
|
75
59
|
|
|
76
60
|
submessage(msg) {
|
|
77
61
|
const items = Array.isArray(msg) ? msg : [msg]
|
|
@@ -80,42 +64,47 @@ export class AIRichBuilder {
|
|
|
80
64
|
return this
|
|
81
65
|
}
|
|
82
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 ─────────────────────────────────────────────────────────
|
|
83
90
|
|
|
84
91
|
async text(text, opts = {}) {
|
|
85
92
|
ensureString(text, 'text')
|
|
86
93
|
const { hyperlink = true, citation = true, latex = true } = opts
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
citation,
|
|
91
|
-
latex,
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
this._submessages.push({
|
|
95
|
-
messageType: MessageType.TEXT,
|
|
96
|
-
messageText: rewritten,
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
this._sections.push(
|
|
100
|
-
singleLayout(markdownTextPrimitive(rewritten, metadata.length ? metadata : undefined)),
|
|
101
|
-
)
|
|
94
|
+
const { text: rewritten, metadata } = extractInlineEntities(text, { hyperlink, citation, latex })
|
|
95
|
+
this._submessages.push({ messageType: MessageType.TEXT, messageText: rewritten })
|
|
96
|
+
this._sections.push(singleLayout(markdownTextPrimitive(rewritten, metadata.length ? metadata : undefined)))
|
|
102
97
|
return this
|
|
103
98
|
}
|
|
104
99
|
|
|
105
100
|
async code(language, code) {
|
|
106
101
|
ensureString(language, 'language')
|
|
107
102
|
ensureString(code, 'code')
|
|
108
|
-
|
|
109
103
|
const { codeBlock, unifiedBlocks } = tokenizeCode(code, language)
|
|
110
|
-
|
|
111
104
|
this._submessages.push({
|
|
112
105
|
messageType: MessageType.CODE,
|
|
113
|
-
codeMetadata: {
|
|
114
|
-
codeLanguage: language,
|
|
115
|
-
codeBlocks: codeBlock,
|
|
116
|
-
},
|
|
106
|
+
codeMetadata: { codeLanguage: language, codeBlocks: codeBlock },
|
|
117
107
|
})
|
|
118
|
-
|
|
119
108
|
this._sections.push(singleLayout(codePrimitive(language, unifiedBlocks)))
|
|
120
109
|
return this
|
|
121
110
|
}
|
|
@@ -126,34 +115,22 @@ export class AIRichBuilder {
|
|
|
126
115
|
}
|
|
127
116
|
const { title = '', hyperlink = true, citation = true, latex = true } = opts
|
|
128
117
|
const meta = toTableMetadata(table, { title, hyperlink, citation, latex })
|
|
129
|
-
|
|
130
118
|
this._submessages.push({
|
|
131
119
|
messageType: MessageType.TABLE,
|
|
132
120
|
tableMetadata: { title: meta.title, rows: meta.rows },
|
|
133
121
|
})
|
|
134
|
-
|
|
135
122
|
this._sections.push(singleLayout(tablePrimitive(meta.unifiedRows)))
|
|
136
123
|
return this
|
|
137
124
|
}
|
|
138
125
|
|
|
139
126
|
async image(source, opts = {}) {
|
|
140
|
-
const { resolveUrl = false } = opts
|
|
141
127
|
const list = Array.isArray(source) ? source : [source]
|
|
142
|
-
|
|
143
128
|
const resolved = await Promise.all(
|
|
144
129
|
list.map(async (s) => {
|
|
145
|
-
const url = await this.#media.resolve(s, {
|
|
146
|
-
|
|
147
|
-
strategy: resolveUrl ? 'auto' : 'auto',
|
|
148
|
-
})
|
|
149
|
-
return {
|
|
150
|
-
imagePreviewUrl: url,
|
|
151
|
-
imageHighResUrl: url,
|
|
152
|
-
sourceUrl: url,
|
|
153
|
-
}
|
|
130
|
+
const url = await this.#media.resolve(s, { mediaType: 'image', strategy: 'auto' })
|
|
131
|
+
return { imagePreviewUrl: url, imageHighResUrl: url, sourceUrl: url }
|
|
154
132
|
}),
|
|
155
133
|
)
|
|
156
|
-
|
|
157
134
|
this._submessages.push({
|
|
158
135
|
messageType: MessageType.RICH_RESPONSE,
|
|
159
136
|
gridImageMetadata: {
|
|
@@ -161,15 +138,9 @@ export class AIRichBuilder {
|
|
|
161
138
|
imageUrls: resolved,
|
|
162
139
|
},
|
|
163
140
|
})
|
|
164
|
-
|
|
165
141
|
for (const r of resolved) {
|
|
166
142
|
this._sections.push(
|
|
167
|
-
singleLayout(
|
|
168
|
-
imaginePrimitive(
|
|
169
|
-
{ url: r.imagePreviewUrl, mime_type: 'image/png' },
|
|
170
|
-
ImagineType.IMAGE,
|
|
171
|
-
),
|
|
172
|
-
),
|
|
143
|
+
singleLayout(imaginePrimitive({ url: r.imagePreviewUrl, mime_type: 'image/png' }, ImagineType.IMAGE)),
|
|
173
144
|
)
|
|
174
145
|
}
|
|
175
146
|
return this
|
|
@@ -177,22 +148,15 @@ export class AIRichBuilder {
|
|
|
177
148
|
|
|
178
149
|
async video(source, opts = {}) {
|
|
179
150
|
const { autoFill = true } = opts
|
|
180
|
-
const
|
|
151
|
+
const isObjVideo = (v) => v && typeof v === 'object' && v.url
|
|
181
152
|
const items = Array.isArray(source) ? source : [source]
|
|
182
153
|
|
|
183
|
-
this._submessages.push({
|
|
184
|
-
messageType: MessageType.TEXT,
|
|
185
|
-
messageText: '[ CANNOT_LOAD_VIDEO ]',
|
|
186
|
-
})
|
|
154
|
+
this._submessages.push({ messageType: MessageType.TEXT, messageText: '[ CANNOT_LOAD_VIDEO ]' })
|
|
187
155
|
|
|
188
156
|
await Promise.all(
|
|
189
157
|
items.map(async (item) => {
|
|
190
|
-
const isObj =
|
|
191
|
-
const url = await this.#media.resolve(
|
|
192
|
-
isObj ? item.url : item,
|
|
193
|
-
{ mediaType: 'video', strategy: 'auto' },
|
|
194
|
-
)
|
|
195
|
-
|
|
158
|
+
const isObj = isObjVideo(item)
|
|
159
|
+
const url = await this.#media.resolve(isObj ? item.url : item, { mediaType: 'video', strategy: 'auto' })
|
|
196
160
|
let fileLength = isObj && item.file_length != null ? item.file_length : 0
|
|
197
161
|
let duration = isObj && item.duration != null ? item.duration : 0
|
|
198
162
|
let thumbnailB64 = null
|
|
@@ -204,26 +168,18 @@ export class AIRichBuilder {
|
|
|
204
168
|
fileLength = buf.length
|
|
205
169
|
duration = this.#media.duration(buf)
|
|
206
170
|
thumbnailB64 = await this.#media.videoThumbnail(buf, {
|
|
207
|
-
time: 0,
|
|
208
|
-
result: 'base64',
|
|
209
|
-
resizeOutput: true,
|
|
210
|
-
width: 300,
|
|
211
|
-
height: 300,
|
|
171
|
+
time: 0, result: 'base64', resizeOutput: true, width: 300, height: 300,
|
|
212
172
|
})
|
|
213
173
|
}
|
|
214
|
-
} catch {
|
|
215
|
-
}
|
|
174
|
+
} catch {}
|
|
216
175
|
}
|
|
217
176
|
|
|
218
177
|
const mimeType = isObj ? item.mime_type ?? 'video/mp4' : 'video/mp4'
|
|
219
|
-
|
|
220
178
|
this._sections.push(
|
|
221
179
|
singleLayout(
|
|
222
180
|
imaginePrimitive(
|
|
223
181
|
{ url, mime_type: mimeType, file_length: fileLength, duration },
|
|
224
|
-
ImagineType.ANIMATE,
|
|
225
|
-
'READY',
|
|
226
|
-
thumbnailB64 || undefined,
|
|
182
|
+
ImagineType.ANIMATE, 'READY', thumbnailB64 || undefined,
|
|
227
183
|
),
|
|
228
184
|
),
|
|
229
185
|
)
|
|
@@ -236,12 +192,7 @@ export class AIRichBuilder {
|
|
|
236
192
|
if (!Array.isArray(sources)) {
|
|
237
193
|
throw new ValidationError('sources must be an array', { code: 'LUMINA_VALIDATION_SOURCE' })
|
|
238
194
|
}
|
|
239
|
-
|
|
240
|
-
const normalised =
|
|
241
|
-
sources.length && typeof sources[0] === 'string'
|
|
242
|
-
? [sources]
|
|
243
|
-
: sources
|
|
244
|
-
|
|
195
|
+
const normalised = sources.length && typeof sources[0] === 'string' ? [sources] : sources
|
|
245
196
|
const entries = await Promise.all(
|
|
246
197
|
normalised.map(async (entry) => {
|
|
247
198
|
const [iconUrl, url, text] = entry
|
|
@@ -251,7 +202,6 @@ export class AIRichBuilder {
|
|
|
251
202
|
return shapeSourceEntry({ iconUrl: resolvedIcon, url, text })
|
|
252
203
|
}),
|
|
253
204
|
)
|
|
254
|
-
|
|
255
205
|
this._sections.push(singleLayout(searchResultPrimitive(entries)))
|
|
256
206
|
return this
|
|
257
207
|
}
|
|
@@ -259,27 +209,17 @@ export class AIRichBuilder {
|
|
|
259
209
|
async reels(items) {
|
|
260
210
|
ensureObjectOrArray(items, 'reels')
|
|
261
211
|
const list = Array.isArray(items) ? items : [items]
|
|
262
|
-
|
|
263
212
|
const resolved = await Promise.all(
|
|
264
213
|
list.map(async (item) => {
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
})
|
|
271
|
-
: Promise.resolve(''),
|
|
272
|
-
item.thumbnailUrl ?? item.thumbnail
|
|
273
|
-
? this.#media.resolve(item.thumbnailUrl ?? item.thumbnail, {
|
|
274
|
-
mediaType: 'image',
|
|
275
|
-
strategy: 'auto',
|
|
276
|
-
})
|
|
277
|
-
: Promise.resolve(''),
|
|
214
|
+
const avatarSrc = item.profileIconUrl ?? item.profile_url ?? item.profile
|
|
215
|
+
const thumbSrc = item.thumbnailUrl ?? item.thumbnail
|
|
216
|
+
const [avatar, thumbnail] = await Promise.all([
|
|
217
|
+
avatarSrc ? this.#media.resolve(avatarSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
|
|
218
|
+
thumbSrc ? this.#media.resolve(thumbSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
|
|
278
219
|
])
|
|
279
|
-
return { ...item, avatar, thumbnail
|
|
220
|
+
return { ...item, avatar, thumbnail }
|
|
280
221
|
}),
|
|
281
222
|
)
|
|
282
|
-
|
|
283
223
|
this._submessages.push({
|
|
284
224
|
messageType: MessageType.REELS,
|
|
285
225
|
contentItemsMetadata: {
|
|
@@ -294,7 +234,6 @@ export class AIRichBuilder {
|
|
|
294
234
|
})),
|
|
295
235
|
},
|
|
296
236
|
})
|
|
297
|
-
|
|
298
237
|
resolved.forEach((item, idx) => {
|
|
299
238
|
this._richResponseSources.push({
|
|
300
239
|
provider: 'LUMINA',
|
|
@@ -306,110 +245,62 @@ export class AIRichBuilder {
|
|
|
306
245
|
sourceTitle: item.username ?? '',
|
|
307
246
|
})
|
|
308
247
|
})
|
|
309
|
-
|
|
310
|
-
this._sections.push(
|
|
311
|
-
hscrollLayout(resolved.map((item) => reelPrimitive(shapeReelEntry(item)))),
|
|
312
|
-
)
|
|
248
|
+
this._sections.push(hscrollLayout(resolved.map((item) => reelPrimitive(shapeReelEntry(item)))))
|
|
313
249
|
return this
|
|
314
250
|
}
|
|
315
251
|
|
|
316
252
|
async product(item) {
|
|
317
253
|
ensureObjectOrArray(item, 'product')
|
|
318
254
|
const list = Array.isArray(item) ? item : [item]
|
|
319
|
-
|
|
320
|
-
this._submessages.push({
|
|
321
|
-
messageType: MessageType.TEXT,
|
|
322
|
-
messageText: '[ CANNOT_LOAD_PRODUCT ]',
|
|
323
|
-
})
|
|
324
|
-
|
|
255
|
+
this._submessages.push({ messageType: MessageType.TEXT, messageText: '[ CANNOT_LOAD_PRODUCT ]' })
|
|
325
256
|
const products = await Promise.all(
|
|
326
257
|
list.map(async (p) => {
|
|
258
|
+
const imgSrc = p.image_url ?? p.image
|
|
259
|
+
const iconSrc = p.icon_url ?? p.icon
|
|
327
260
|
const [imageUrl, iconUrl] = await Promise.all([
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
: Promise.resolve(''),
|
|
331
|
-
p.icon_url ?? p.icon
|
|
332
|
-
? this.#media.resolve(p.icon_url ?? p.icon, { mediaType: 'image', strategy: 'auto' })
|
|
333
|
-
: Promise.resolve(''),
|
|
261
|
+
imgSrc ? this.#media.resolve(imgSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
|
|
262
|
+
iconSrc ? this.#media.resolve(iconSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
|
|
334
263
|
])
|
|
335
264
|
return {
|
|
336
|
-
title: p.title,
|
|
337
|
-
brand: p.brand,
|
|
338
|
-
price: p.price,
|
|
339
|
-
sale_price: p.sale_price,
|
|
265
|
+
title: p.title, brand: p.brand, price: p.price, sale_price: p.sale_price,
|
|
340
266
|
product_url: p.product_url ?? p.url,
|
|
341
267
|
image: { url: imageUrl },
|
|
342
268
|
additional_images: [{ url: iconUrl }],
|
|
343
269
|
}
|
|
344
270
|
}),
|
|
345
271
|
)
|
|
346
|
-
|
|
347
272
|
const primitives = products.map((p) => productCardPrimitive(p))
|
|
348
|
-
this._sections.push(
|
|
349
|
-
list.length === 1 ? singleLayout(primitives[0]) : hscrollLayout(primitives),
|
|
350
|
-
)
|
|
273
|
+
this._sections.push(list.length === 1 ? singleLayout(primitives[0]) : hscrollLayout(primitives))
|
|
351
274
|
return this
|
|
352
275
|
}
|
|
353
276
|
|
|
354
277
|
async post(item) {
|
|
355
278
|
ensureObjectOrArray(item, 'post')
|
|
356
279
|
const list = Array.isArray(item) ? item : [item]
|
|
357
|
-
|
|
358
|
-
this._submessages.push({
|
|
359
|
-
messageType: MessageType.TEXT,
|
|
360
|
-
messageText: '[ CANNOT_LOAD_POST ]',
|
|
361
|
-
})
|
|
362
|
-
|
|
280
|
+
this._submessages.push({ messageType: MessageType.TEXT, messageText: '[ CANNOT_LOAD_POST ]' })
|
|
363
281
|
const isCarousel = list.length > 1
|
|
364
|
-
|
|
365
282
|
const primitives = await Promise.all(
|
|
366
283
|
list.map(async (p) => {
|
|
284
|
+
const profileSrc = p.profile_picture_url ?? p.profile_url ?? p.profile
|
|
285
|
+
const thumbSrc = p.thumbnail_url ?? p.thumbnail
|
|
286
|
+
const footerSrc = p.footer_icon ?? p.icon
|
|
367
287
|
const [profileUrl, thumbUrl, footerIcon] = await Promise.all([
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
strategy: 'auto',
|
|
372
|
-
})
|
|
373
|
-
: Promise.resolve(''),
|
|
374
|
-
p.thumbnail_url ?? p.thumbnail
|
|
375
|
-
? this.#media.resolve(p.thumbnail_url ?? p.thumbnail, {
|
|
376
|
-
mediaType: 'image',
|
|
377
|
-
strategy: 'auto',
|
|
378
|
-
})
|
|
379
|
-
: Promise.resolve(''),
|
|
380
|
-
p.footer_icon ?? p.icon
|
|
381
|
-
? this.#media.resolve(p.footer_icon ?? p.icon, {
|
|
382
|
-
mediaType: 'image',
|
|
383
|
-
strategy: 'auto',
|
|
384
|
-
})
|
|
385
|
-
: Promise.resolve(''),
|
|
288
|
+
profileSrc ? this.#media.resolve(profileSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
|
|
289
|
+
thumbSrc ? this.#media.resolve(thumbSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
|
|
290
|
+
footerSrc ? this.#media.resolve(footerSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
|
|
386
291
|
])
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
likes_count: p.likes_count ?? p.like ?? 0,
|
|
398
|
-
comments_count: p.comments_count ?? p.comment ?? 0,
|
|
399
|
-
shares_count: p.shares_count ?? p.share ?? 0,
|
|
400
|
-
post_url: p.post_url ?? p.url ?? '',
|
|
401
|
-
post_deeplink: p.post_deeplink ?? p.deeplink ?? '',
|
|
402
|
-
source_app: p.source_app ?? p.source ?? 'INSTAGRAM',
|
|
403
|
-
footer_label: p.footer_label ?? p.footer ?? '',
|
|
404
|
-
footer_icon: footerIcon,
|
|
405
|
-
orientation: p.orientation ?? 'LANDSCAPE',
|
|
406
|
-
post_type: p.post_type ?? 'VIDEO',
|
|
407
|
-
},
|
|
408
|
-
isCarousel,
|
|
409
|
-
)
|
|
292
|
+
return postPrimitive({
|
|
293
|
+
title: p.title ?? '', subtitle: p.subtitle ?? '', username: p.username ?? '',
|
|
294
|
+
profile_picture_url: profileUrl, is_verified: !!(p.is_verified ?? p.verified),
|
|
295
|
+
thumbnail_url: thumbUrl, post_caption: p.post_caption ?? p.caption ?? '',
|
|
296
|
+
likes_count: p.likes_count ?? p.like ?? 0, comments_count: p.comments_count ?? p.comment ?? 0,
|
|
297
|
+
shares_count: p.shares_count ?? p.share ?? 0, post_url: p.post_url ?? p.url ?? '',
|
|
298
|
+
post_deeplink: p.post_deeplink ?? p.deeplink ?? '', source_app: p.source_app ?? p.source ?? 'INSTAGRAM',
|
|
299
|
+
footer_label: p.footer_label ?? p.footer ?? '', footer_icon: footerIcon,
|
|
300
|
+
orientation: p.orientation ?? 'LANDSCAPE', post_type: p.post_type ?? 'VIDEO',
|
|
301
|
+
}, isCarousel)
|
|
410
302
|
}),
|
|
411
303
|
)
|
|
412
|
-
|
|
413
304
|
this._sections.push(hscrollLayout(primitives))
|
|
414
305
|
return this
|
|
415
306
|
}
|
|
@@ -423,23 +314,16 @@ export class AIRichBuilder {
|
|
|
423
314
|
|
|
424
315
|
async suggest(suggestion, opts = {}) {
|
|
425
316
|
const { scroll = true, layout } = opts
|
|
426
|
-
|
|
427
317
|
if (typeof suggestion === 'string') {
|
|
428
318
|
suggestion = [suggestion]
|
|
429
319
|
} else {
|
|
430
320
|
ensureStringArray(suggestion, 'suggestion')
|
|
431
321
|
}
|
|
432
|
-
|
|
433
322
|
const pills = suggestion.map((text) => followUpSuggestionPillPrimitive(text))
|
|
434
|
-
|
|
435
323
|
let kind
|
|
436
|
-
if (layout)
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
kind = LayoutKind.SINGLE
|
|
440
|
-
} else {
|
|
441
|
-
kind = scroll ? LayoutKind.HSCROLL : LayoutKind.ACTION_ROW
|
|
442
|
-
}
|
|
324
|
+
if (layout) kind = layout
|
|
325
|
+
else if (pills.length === 1) kind = LayoutKind.SINGLE
|
|
326
|
+
else kind = scroll ? LayoutKind.HSCROLL : LayoutKind.ACTION_ROW
|
|
443
327
|
|
|
444
328
|
if (kind === LayoutKind.SINGLE) {
|
|
445
329
|
this._sections.push(singleLayout(pills[0], { __typename: 'GenAIUnifiedResponseSection' }))
|
|
@@ -451,6 +335,31 @@ export class AIRichBuilder {
|
|
|
451
335
|
return this
|
|
452
336
|
}
|
|
453
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 ─────────────────────────────────────────────────────────────
|
|
454
363
|
|
|
455
364
|
async build(opts = {}) {
|
|
456
365
|
return assembleRichResponse({
|
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 }
|