@kyyinfinite/lumina 1.0.4 → 1.0.6

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/README.md CHANGED
@@ -360,88 +360,6 @@ Type-checked features:
360
360
  - Enum-like catalogs (`MessageType`, `HeaderType`, `LayoutKind`, ...) are `Readonly`
361
361
  - `Bot.raw` escape hatch typed as `WASocket`
362
362
 
363
-
364
- ---
365
-
366
- ## Baileys Compatibility
367
-
368
- Lumina is not tied to a specific Baileys version or fork. When `new Bot(sock)` is called, Lumina automatically detects the available Baileys package in your project.
369
-
370
- ### Auto-Detect (default)
371
-
372
- No configuration is required. Lumina tries the following packages in order and uses the first one that can be successfully imported:
373
-
374
- ```
375
- @kyyinfinite/baileys
376
- @whiskeysockets/baileys
377
- baileys
378
- @adiwajshing/baileys
379
- @brunocgc/baileys
380
- @open-wa/baileys
381
- ```
382
-
383
- ```js
384
- // Lumina automatically detects @kyyinfinite/baileys (or any installed fork)
385
- const bot = new Bot(sock)
386
- ```
387
-
388
- ---
389
-
390
- ### Explicit Package Name
391
-
392
- If you're using a custom-named fork, specify it with `baileysPackage`:
393
-
394
- ```js
395
- const bot = new Bot(sock, {
396
- baileysPackage: '@namafork/baileys',
397
- })
398
- ```
399
-
400
- ---
401
-
402
- ### Module Injection
403
-
404
- The fastest option — zero dynamic imports. Simply inject the module directly:
405
-
406
- ```js
407
- import * as baileys from '@kyyinfinite/baileys'
408
-
409
- const bot = new Bot(sock, {
410
- baileys,
411
- })
412
- ```
413
-
414
- This is ideal if you've already imported Baileys elsewhere and want to avoid duplicate imports.
415
-
416
- ---
417
-
418
- ### Socket Fallback
419
-
420
- If no Baileys package is found, Lumina falls back to its internal socket-based implementation. Core features (text, buttons, carousel, stickers) continue to work. Media uploads require `sock.waUploadToServer` to be available.
421
-
422
- ---
423
-
424
- ### Debug: Check the Active Adapter
425
-
426
- ```js
427
- const bot = new Bot(sock)
428
- console.log(await bot.connection.adapterSource())
429
- // → '@kyyinfinite/baileys'
430
- // → '@whiskeysockets/baileys'
431
- // → 'injected-module'
432
- // → 'socket-fallback'
433
- ```
434
-
435
- ---
436
-
437
- ### Summary
438
-
439
- | Option | When to use |
440
- |---|---|
441
- | _(not set)_ | Auto-detect. Recommended for most use cases. |
442
- | `baileysPackage: 'name'` | For custom forks that are not included in the auto-detect list. |
443
- | `baileys: module` | Manual module injection for the best performance. |
444
-
445
363
  ---
446
364
 
447
365
  ## API Reference
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kyyinfinite/lumina",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Lumina — Modern WhatsApp framework built on top of Baileys. Hide the protocol complexity, expose a fluent API.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -1,61 +1,43 @@
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
- codePrimitive,
13
- tablePrimitive,
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
 
28
-
29
16
  export class AIRichBuilder {
17
+ #conn
18
+ #proto
19
+ #media
20
+
30
21
  constructor(conn, proto, media) {
31
22
  applyContentFields(this)
32
23
  this.#conn = conn
33
24
  this.#proto = proto
34
25
  this.#media = media
35
-
36
26
  this._submessages = []
37
27
  this._sections = []
38
28
  this._richResponseSources = []
39
-
40
29
  this._forwarded = true
41
30
  this._notification = false
42
31
  this._includesUnifiedResponse = true
43
32
  this._includesSubmessages = true
44
- this._quoted
45
- this._quotedParticipant
46
- }
47
-
48
-
49
-
50
- forwarded(v = true) {
51
- this._forwarded = v
52
- return this
33
+ this._quoted = undefined
34
+ this._quotedParticipant = undefined
53
35
  }
54
36
 
55
- notification(v) {
56
- this._notification = v
57
- return this
58
- }
37
+ forwarded(v = true) { this._forwarded = v; return this }
38
+ notification(v) { this._notification = v; return this }
39
+ includesUnifiedResponse(v = true) { this._includesUnifiedResponse = v; return this }
40
+ includesSubmessages(v = true) { this._includesSubmessages = v; return this }
59
41
 
60
42
  quoted(quoted, quotedParticipant) {
61
43
  this._quoted = quoted
@@ -63,16 +45,6 @@ export class AIRichBuilder {
63
45
  return this
64
46
  }
65
47
 
66
- includesUnifiedResponse(v = true) {
67
- this._includesUnifiedResponse = v
68
- return this
69
- }
70
-
71
- includesSubmessages(v = true) {
72
- this._includesSubmessages = v
73
- return this
74
- }
75
-
76
48
  submessage(msg) {
77
49
  const items = Array.isArray(msg) ? msg : [msg]
78
50
  items.forEach((m) => ensureObjectOrArray(m, 'submessage'))
@@ -80,42 +52,23 @@ export class AIRichBuilder {
80
52
  return this
81
53
  }
82
54
 
83
-
84
55
  async text(text, opts = {}) {
85
56
  ensureString(text, 'text')
86
57
  const { hyperlink = true, citation = true, latex = true } = opts
87
-
88
- const { text: rewritten, metadata } = extractInlineEntities(text, {
89
- hyperlink,
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
- )
58
+ const { text: rewritten, metadata } = extractInlineEntities(text, { hyperlink, citation, latex })
59
+ this._submessages.push({ messageType: MessageType.TEXT, messageText: rewritten })
60
+ this._sections.push(singleLayout(markdownTextPrimitive(rewritten, metadata.length ? metadata : undefined)))
102
61
  return this
103
62
  }
104
63
 
105
64
  async code(language, code) {
106
65
  ensureString(language, 'language')
107
66
  ensureString(code, 'code')
108
-
109
67
  const { codeBlock, unifiedBlocks } = tokenizeCode(code, language)
110
-
111
68
  this._submessages.push({
112
69
  messageType: MessageType.CODE,
113
- codeMetadata: {
114
- codeLanguage: language,
115
- codeBlocks: codeBlock,
116
- },
70
+ codeMetadata: { codeLanguage: language, codeBlocks: codeBlock },
117
71
  })
118
-
119
72
  this._sections.push(singleLayout(codePrimitive(language, unifiedBlocks)))
120
73
  return this
121
74
  }
@@ -126,34 +79,22 @@ export class AIRichBuilder {
126
79
  }
127
80
  const { title = '', hyperlink = true, citation = true, latex = true } = opts
128
81
  const meta = toTableMetadata(table, { title, hyperlink, citation, latex })
129
-
130
82
  this._submessages.push({
131
83
  messageType: MessageType.TABLE,
132
84
  tableMetadata: { title: meta.title, rows: meta.rows },
133
85
  })
134
-
135
86
  this._sections.push(singleLayout(tablePrimitive(meta.unifiedRows)))
136
87
  return this
137
88
  }
138
89
 
139
90
  async image(source, opts = {}) {
140
- const { resolveUrl = false } = opts
141
91
  const list = Array.isArray(source) ? source : [source]
142
-
143
92
  const resolved = await Promise.all(
144
93
  list.map(async (s) => {
145
- const url = await this.#media.resolve(s, {
146
- mediaType: 'image',
147
- strategy: resolveUrl ? 'auto' : 'auto',
148
- })
149
- return {
150
- imagePreviewUrl: url,
151
- imageHighResUrl: url,
152
- sourceUrl: url,
153
- }
94
+ const url = await this.#media.resolve(s, { mediaType: 'image', strategy: 'auto' })
95
+ return { imagePreviewUrl: url, imageHighResUrl: url, sourceUrl: url }
154
96
  }),
155
97
  )
156
-
157
98
  this._submessages.push({
158
99
  messageType: MessageType.RICH_RESPONSE,
159
100
  gridImageMetadata: {
@@ -161,15 +102,9 @@ export class AIRichBuilder {
161
102
  imageUrls: resolved,
162
103
  },
163
104
  })
164
-
165
105
  for (const r of resolved) {
166
106
  this._sections.push(
167
- singleLayout(
168
- imaginePrimitive(
169
- { url: r.imagePreviewUrl, mime_type: 'image/png' },
170
- ImagineType.IMAGE,
171
- ),
172
- ),
107
+ singleLayout(imaginePrimitive({ url: r.imagePreviewUrl, mime_type: 'image/png' }, ImagineType.IMAGE)),
173
108
  )
174
109
  }
175
110
  return this
@@ -177,22 +112,15 @@ export class AIRichBuilder {
177
112
 
178
113
  async video(source, opts = {}) {
179
114
  const { autoFill = true } = opts
180
- const isObjectVideo = (v) => v && typeof v === 'object' && v.url
115
+ const isObjVideo = (v) => v && typeof v === 'object' && v.url
181
116
  const items = Array.isArray(source) ? source : [source]
182
117
 
183
- this._submessages.push({
184
- messageType: MessageType.TEXT,
185
- messageText: '[ CANNOT_LOAD_VIDEO ]',
186
- })
118
+ this._submessages.push({ messageType: MessageType.TEXT, messageText: '[ CANNOT_LOAD_VIDEO ]' })
187
119
 
188
120
  await Promise.all(
189
121
  items.map(async (item) => {
190
- const isObj = isObjectVideo(item)
191
- const url = await this.#media.resolve(
192
- isObj ? item.url : item,
193
- { mediaType: 'video', strategy: 'auto' },
194
- )
195
-
122
+ const isObj = isObjVideo(item)
123
+ const url = await this.#media.resolve(isObj ? item.url : item, { mediaType: 'video', strategy: 'auto' })
196
124
  let fileLength = isObj && item.file_length != null ? item.file_length : 0
197
125
  let duration = isObj && item.duration != null ? item.duration : 0
198
126
  let thumbnailB64 = null
@@ -204,26 +132,18 @@ export class AIRichBuilder {
204
132
  fileLength = buf.length
205
133
  duration = this.#media.duration(buf)
206
134
  thumbnailB64 = await this.#media.videoThumbnail(buf, {
207
- time: 0,
208
- result: 'base64',
209
- resizeOutput: true,
210
- width: 300,
211
- height: 300,
135
+ time: 0, result: 'base64', resizeOutput: true, width: 300, height: 300,
212
136
  })
213
137
  }
214
- } catch {
215
- }
138
+ } catch {}
216
139
  }
217
140
 
218
141
  const mimeType = isObj ? item.mime_type ?? 'video/mp4' : 'video/mp4'
219
-
220
142
  this._sections.push(
221
143
  singleLayout(
222
144
  imaginePrimitive(
223
145
  { url, mime_type: mimeType, file_length: fileLength, duration },
224
- ImagineType.ANIMATE,
225
- 'READY',
226
- thumbnailB64 || undefined,
146
+ ImagineType.ANIMATE, 'READY', thumbnailB64 || undefined,
227
147
  ),
228
148
  ),
229
149
  )
@@ -236,12 +156,7 @@ export class AIRichBuilder {
236
156
  if (!Array.isArray(sources)) {
237
157
  throw new ValidationError('sources must be an array', { code: 'LUMINA_VALIDATION_SOURCE' })
238
158
  }
239
-
240
- const normalised =
241
- sources.length && typeof sources[0] === 'string'
242
- ? [sources]
243
- : sources
244
-
159
+ const normalised = sources.length && typeof sources[0] === 'string' ? [sources] : sources
245
160
  const entries = await Promise.all(
246
161
  normalised.map(async (entry) => {
247
162
  const [iconUrl, url, text] = entry
@@ -251,7 +166,6 @@ export class AIRichBuilder {
251
166
  return shapeSourceEntry({ iconUrl: resolvedIcon, url, text })
252
167
  }),
253
168
  )
254
-
255
169
  this._sections.push(singleLayout(searchResultPrimitive(entries)))
256
170
  return this
257
171
  }
@@ -259,27 +173,17 @@ export class AIRichBuilder {
259
173
  async reels(items) {
260
174
  ensureObjectOrArray(items, 'reels')
261
175
  const list = Array.isArray(items) ? items : [items]
262
-
263
176
  const resolved = await Promise.all(
264
177
  list.map(async (item) => {
265
- const [avatar, thumb] = await Promise.all([
266
- item.profileIconUrl ?? item.profile_url ?? item.profile
267
- ? this.#media.resolve(item.profileIconUrl ?? item.profile_url ?? item.profile, {
268
- mediaType: 'image',
269
- strategy: 'auto',
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(''),
178
+ const avatarSrc = item.profileIconUrl ?? item.profile_url ?? item.profile
179
+ const thumbSrc = item.thumbnailUrl ?? item.thumbnail
180
+ const [avatar, thumbnail] = await Promise.all([
181
+ avatarSrc ? this.#media.resolve(avatarSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
182
+ thumbSrc ? this.#media.resolve(thumbSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
278
183
  ])
279
- return { ...item, avatar, thumbnail: thumb }
184
+ return { ...item, avatar, thumbnail }
280
185
  }),
281
186
  )
282
-
283
187
  this._submessages.push({
284
188
  messageType: MessageType.REELS,
285
189
  contentItemsMetadata: {
@@ -294,7 +198,6 @@ export class AIRichBuilder {
294
198
  })),
295
199
  },
296
200
  })
297
-
298
201
  resolved.forEach((item, idx) => {
299
202
  this._richResponseSources.push({
300
203
  provider: 'LUMINA',
@@ -306,110 +209,62 @@ export class AIRichBuilder {
306
209
  sourceTitle: item.username ?? '',
307
210
  })
308
211
  })
309
-
310
- this._sections.push(
311
- hscrollLayout(resolved.map((item) => reelPrimitive(shapeReelEntry(item)))),
312
- )
212
+ this._sections.push(hscrollLayout(resolved.map((item) => reelPrimitive(shapeReelEntry(item)))))
313
213
  return this
314
214
  }
315
215
 
316
216
  async product(item) {
317
217
  ensureObjectOrArray(item, 'product')
318
218
  const list = Array.isArray(item) ? item : [item]
319
-
320
- this._submessages.push({
321
- messageType: MessageType.TEXT,
322
- messageText: '[ CANNOT_LOAD_PRODUCT ]',
323
- })
324
-
219
+ this._submessages.push({ messageType: MessageType.TEXT, messageText: '[ CANNOT_LOAD_PRODUCT ]' })
325
220
  const products = await Promise.all(
326
221
  list.map(async (p) => {
222
+ const imgSrc = p.image_url ?? p.image
223
+ const iconSrc = p.icon_url ?? p.icon
327
224
  const [imageUrl, iconUrl] = await Promise.all([
328
- p.image_url ?? p.image
329
- ? this.#media.resolve(p.image_url ?? p.image, { mediaType: 'image', strategy: 'auto' })
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(''),
225
+ imgSrc ? this.#media.resolve(imgSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
226
+ iconSrc ? this.#media.resolve(iconSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
334
227
  ])
335
228
  return {
336
- title: p.title,
337
- brand: p.brand,
338
- price: p.price,
339
- sale_price: p.sale_price,
229
+ title: p.title, brand: p.brand, price: p.price, sale_price: p.sale_price,
340
230
  product_url: p.product_url ?? p.url,
341
231
  image: { url: imageUrl },
342
232
  additional_images: [{ url: iconUrl }],
343
233
  }
344
234
  }),
345
235
  )
346
-
347
236
  const primitives = products.map((p) => productCardPrimitive(p))
348
- this._sections.push(
349
- list.length === 1 ? singleLayout(primitives[0]) : hscrollLayout(primitives),
350
- )
237
+ this._sections.push(list.length === 1 ? singleLayout(primitives[0]) : hscrollLayout(primitives))
351
238
  return this
352
239
  }
353
240
 
354
241
  async post(item) {
355
242
  ensureObjectOrArray(item, 'post')
356
243
  const list = Array.isArray(item) ? item : [item]
357
-
358
- this._submessages.push({
359
- messageType: MessageType.TEXT,
360
- messageText: '[ CANNOT_LOAD_POST ]',
361
- })
362
-
244
+ this._submessages.push({ messageType: MessageType.TEXT, messageText: '[ CANNOT_LOAD_POST ]' })
363
245
  const isCarousel = list.length > 1
364
-
365
246
  const primitives = await Promise.all(
366
247
  list.map(async (p) => {
248
+ const profileSrc = p.profile_picture_url ?? p.profile_url ?? p.profile
249
+ const thumbSrc = p.thumbnail_url ?? p.thumbnail
250
+ const footerSrc = p.footer_icon ?? p.icon
367
251
  const [profileUrl, thumbUrl, footerIcon] = await Promise.all([
368
- p.profile_picture_url ?? p.profile_url ?? p.profile
369
- ? this.#media.resolve(p.profile_picture_url ?? p.profile_url ?? p.profile, {
370
- mediaType: 'image',
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(''),
252
+ profileSrc ? this.#media.resolve(profileSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
253
+ thumbSrc ? this.#media.resolve(thumbSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
254
+ footerSrc ? this.#media.resolve(footerSrc, { mediaType: 'image', strategy: 'auto' }) : Promise.resolve(''),
386
255
  ])
387
-
388
- return postPrimitive(
389
- {
390
- title: p.title ?? '',
391
- subtitle: p.subtitle ?? '',
392
- username: p.username ?? '',
393
- profile_picture_url: profileUrl,
394
- is_verified: !!(p.is_verified ?? p.verified),
395
- thumbnail_url: thumbUrl,
396
- post_caption: p.post_caption ?? p.caption ?? '',
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
- )
256
+ return postPrimitive({
257
+ title: p.title ?? '', subtitle: p.subtitle ?? '', username: p.username ?? '',
258
+ profile_picture_url: profileUrl, is_verified: !!(p.is_verified ?? p.verified),
259
+ thumbnail_url: thumbUrl, post_caption: p.post_caption ?? p.caption ?? '',
260
+ likes_count: p.likes_count ?? p.like ?? 0, comments_count: p.comments_count ?? p.comment ?? 0,
261
+ shares_count: p.shares_count ?? p.share ?? 0, post_url: p.post_url ?? p.url ?? '',
262
+ post_deeplink: p.post_deeplink ?? p.deeplink ?? '', source_app: p.source_app ?? p.source ?? 'INSTAGRAM',
263
+ footer_label: p.footer_label ?? p.footer ?? '', footer_icon: footerIcon,
264
+ orientation: p.orientation ?? 'LANDSCAPE', post_type: p.post_type ?? 'VIDEO',
265
+ }, isCarousel)
410
266
  }),
411
267
  )
412
-
413
268
  this._sections.push(hscrollLayout(primitives))
414
269
  return this
415
270
  }
@@ -423,15 +278,12 @@ export class AIRichBuilder {
423
278
 
424
279
  async suggest(suggestion, opts = {}) {
425
280
  const { scroll = true, layout } = opts
426
-
427
281
  if (typeof suggestion === 'string') {
428
282
  suggestion = [suggestion]
429
283
  } else {
430
284
  ensureStringArray(suggestion, 'suggestion')
431
285
  }
432
-
433
286
  const pills = suggestion.map((text) => followUpSuggestionPillPrimitive(text))
434
-
435
287
  let kind
436
288
  if (layout) {
437
289
  kind = layout
@@ -440,7 +292,6 @@ export class AIRichBuilder {
440
292
  } else {
441
293
  kind = scroll ? LayoutKind.HSCROLL : LayoutKind.ACTION_ROW
442
294
  }
443
-
444
295
  if (kind === LayoutKind.SINGLE) {
445
296
  this._sections.push(singleLayout(pills[0], { __typename: 'GenAIUnifiedResponseSection' }))
446
297
  } else if (kind === LayoutKind.HSCROLL) {
@@ -451,7 +302,6 @@ export class AIRichBuilder {
451
302
  return this
452
303
  }
453
304
 
454
-
455
305
  async build(opts = {}) {
456
306
  return assembleRichResponse({
457
307
  title: this._title,
@@ -1,9 +1,4 @@
1
- *
2
- *
3
- *
4
-
5
1
  import { PassThrough, Readable } from 'node:stream'
6
-
7
2
  import { MediaError } from '../errors.js'
8
3
  import { resize } from './image.js'
9
4
 
@@ -16,7 +11,7 @@ async function loadFfmpeg() {
16
11
  return (await import('fluent-ffmpeg')).default
17
12
  } catch (err) {
18
13
  throw new MediaError(
19
- "fluent-ffmpeg is not installed. Install it with `npm i fluent-ffmpeg` (and ffmpeg itself) to use video thumbnails.",
14
+ 'fluent-ffmpeg is not installed. Run: npm i fluent-ffmpeg',
20
15
  { code: 'LUMINA_MEDIA_FFMPEG_MISSING', cause: err },
21
16
  )
22
17
  }
@@ -25,8 +20,6 @@ async function loadFfmpeg() {
25
20
  return ffmpegPromise
26
21
  }
27
22
 
28
- *
29
- *
30
23
  export function getMp4Duration(buffer, opts = {}) {
31
24
  const { silent = true } = opts
32
25
 
@@ -36,16 +29,12 @@ export function getMp4Duration(buffer, opts = {}) {
36
29
  }
37
30
 
38
31
  try {
39
- if (!Buffer.isBuffer(buffer) || buffer.length < 8) {
40
- return fail('invalid buffer: too short')
41
- }
32
+ if (!Buffer.isBuffer(buffer) || buffer.length < 8) return fail('invalid buffer: too short')
42
33
 
43
34
  let offset = 0
44
35
  while (offset < buffer.length - 8) {
45
36
  const size = buffer.readUInt32BE(offset)
46
- if (size < 8 || offset + size > buffer.length) {
47
- return fail('invalid atom size')
48
- }
37
+ if (size < 8 || offset + size > buffer.length) return fail('invalid atom size')
49
38
 
50
39
  const type = buffer.toString('ascii', offset + 4, offset + 8)
51
40
  if (type === 'moov') {
@@ -53,9 +42,7 @@ export function getMp4Duration(buffer, opts = {}) {
53
42
  const moovEnd = offset + size
54
43
  while (moovOffset < moovEnd - 8) {
55
44
  const childSize = buffer.readUInt32BE(moovOffset)
56
- if (childSize < 8 || moovOffset + childSize > moovEnd) {
57
- return fail('invalid child atom size')
58
- }
45
+ if (childSize < 8 || moovOffset + childSize > moovEnd) return fail('invalid child atom size')
59
46
  const childType = buffer.toString('ascii', moovOffset + 4, moovOffset + 8)
60
47
  if (childType === 'mvhd') {
61
48
  const version = buffer.readUInt8(moovOffset + 8)
@@ -87,8 +74,6 @@ export function getMp4Duration(buffer, opts = {}) {
87
74
  }
88
75
  }
89
76
 
90
-
91
- *
92
77
  export async function extractThumbnail(videoBuffer, opts = {}) {
93
78
  const {
94
79
  time,
@@ -105,10 +90,14 @@ export async function extractThumbnail(videoBuffer, opts = {}) {
105
90
  return new Promise((resolve, reject) => {
106
91
  const fail = (err) => {
107
92
  if (silent) return resolve(result === 'base64' ? '' : Buffer.alloc(0))
108
- reject(err instanceof MediaError ? err : new MediaError(err?.message ?? String(err), {
109
- code: 'LUMINA_MEDIA_FFMPEG_FAILED',
110
- cause: err,
111
- }))
93
+ reject(
94
+ err instanceof MediaError
95
+ ? err
96
+ : new MediaError(err?.message ?? String(err), {
97
+ code: 'LUMINA_MEDIA_FFMPEG_FAILED',
98
+ cause: err,
99
+ }),
100
+ )
112
101
  }
113
102
 
114
103
  if (!Buffer.isBuffer(videoBuffer) || videoBuffer.length === 0) {
@@ -130,9 +119,7 @@ export async function extractThumbnail(videoBuffer, opts = {}) {
130
119
  if (frame.length === 0) {
131
120
  return fail(new MediaError('ffmpeg produced empty output', { code: 'LUMINA_MEDIA_FFMPEG_EMPTY' }))
132
121
  }
133
- if (resizeOutput) {
134
- frame = await resize(frame, { width, height, fit: 'cover', format })
135
- }
122
+ if (resizeOutput) frame = await resize(frame, { width, height, fit: 'cover', format })
136
123
  resolve(result === 'base64' ? frame.toString('base64') : frame)
137
124
  } catch (err) {
138
125
  fail(err)
@@ -143,10 +130,12 @@ export async function extractThumbnail(videoBuffer, opts = {}) {
143
130
  try {
144
131
  ffmpeg(input)
145
132
  .outputOptions([`-ss ${seekTime}`, '-vframes 1', '-vcodec png', '-f image2pipe'])
146
- .on('error', (err) => fail(new MediaError(`ffmpeg error: ${err.message}`, {
147
- code: 'LUMINA_MEDIA_FFMPEG_FAILED',
148
- cause: err,
149
- })))
133
+ .on('error', (err) =>
134
+ fail(new MediaError(`ffmpeg error: ${err.message}`, {
135
+ code: 'LUMINA_MEDIA_FFMPEG_FAILED',
136
+ cause: err,
137
+ })),
138
+ )
150
139
  .pipe(output, { end: true })
151
140
  } catch (err) {
152
141
  fail(err)
@@ -1,5 +1,3 @@
1
- *
2
- *
3
1
 
4
2
  export const KEYWORDS = {
5
3
  javascript: new Set([
@@ -1,6 +1,3 @@
1
- *
2
- *
3
- *
4
1
 
5
2
  import { HighlightType, HighlightLabel } from '../proto/enums.js'
6
3
  import { KEYWORDS, SLASH_COMMENT_LANGS, HASH_COMMENT_LANGS, BLOCK_COMMENT_LANGS } from './code-tokenizer-keywords.js'
@@ -17,7 +14,6 @@ function identifierChar(lang) {
17
14
  }
18
15
  }
19
16
 
20
- *
21
17
  export function tokenizeCode(code, lang = 'javascript') {
22
18
  if (typeof code !== 'string' || code.length === 0) {
23
19
  return { codeBlock: [], unifiedBlocks: [] }
@@ -1,7 +1,3 @@
1
- *
2
- *
3
- *
4
- *
5
1
 
6
2
  import { promises as fs } from 'node:fs'
7
3
  import path from 'node:path'
@@ -47,7 +43,6 @@ const RE_CONST_REQUIRE =
47
43
  const RE_DESTRUCT_REQUIRE =
48
44
  /(?:const|let|var)\s*\{([^}]+)\}\s*=\s*require\(\s*(['"])([^'"]+)\2\s*\)/g
49
45
 
50
- *
51
46
  export function transformToESM(src) {
52
47
  let out = src
53
48
  out = out.replace(RE_CONST_REQUIRE, (_, name, _q, mod) => `import ${name} from '${mod}'`)
@@ -67,7 +62,6 @@ export function transformToESM(src) {
67
62
  return out
68
63
  }
69
64
 
70
- *
71
65
  export function applyKnownFixes(filename, content) {
72
66
  const applied = []
73
67
  let next = content
@@ -100,7 +94,6 @@ export class ProtoUpdater {
100
94
  }
101
95
  }
102
96
 
103
- *
104
97
  async #listJsFiles(dir) {
105
98
  const entries = await fs.readdir(dir, { withFileTypes: true })
106
99
  const out = []
@@ -112,7 +105,6 @@ export class ProtoUpdater {
112
105
  return out
113
106
  }
114
107
 
115
- *
116
108
  async backup() {
117
109
  await fs.mkdir(this.backupDir, { recursive: true })
118
110
  const timestamp = Date.now()
@@ -127,7 +119,6 @@ export class ProtoUpdater {
127
119
  return record
128
120
  }
129
121
 
130
- *
131
122
  async #hashTree(dir) {
132
123
  const files = (await this.#listJsFiles(dir)).sort()
133
124
  const hashes = await Promise.all(
@@ -139,7 +130,6 @@ export class ProtoUpdater {
139
130
  return sha256(hashes.join('\n'))
140
131
  }
141
132
 
142
- *
143
133
  async restore(id) {
144
134
  const record = this.history.find((h) => h.id === id)
145
135
  if (!record) {
@@ -160,7 +150,6 @@ export class ProtoUpdater {
160
150
  await this.restore(last.id)
161
151
  }
162
152
 
163
- *
164
153
  async validate() {
165
154
  const files = await this.#listJsFiles(this.protoPath)
166
155
  const errors = []
@@ -176,7 +165,6 @@ export class ProtoUpdater {
176
165
  return { ok: errors.length === 0, errors }
177
166
  }
178
167
 
179
- *
180
168
  async update(opts = {}) {
181
169
  const autoRollback = opts.autoRollback ?? true
182
170
  const dryRun = opts.dryRun ?? false