@kyyinfinite/lumina 1.0.0 → 1.0.2

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.
Files changed (40) hide show
  1. package/package.json +1 -1
  2. package/src/builders/ai-rich.js +0 -165
  3. package/src/builders/base.js +4 -51
  4. package/src/builders/button-v2.js +13 -78
  5. package/src/builders/button.js +20 -234
  6. package/src/builders/card.js +9 -76
  7. package/src/builders/carousel.js +4 -61
  8. package/src/builders/index.js +1 -7
  9. package/src/builders/sticker.js +102 -0
  10. package/src/client/bot.js +28 -153
  11. package/src/client/connection.js +4 -111
  12. package/src/errors.js +0 -37
  13. package/src/index.d.ts +1 -28
  14. package/src/index.js +23 -121
  15. package/src/media/fetch.js +2 -33
  16. package/src/media/image.js +1 -41
  17. package/src/media/resolver.js +3 -55
  18. package/src/media/sticker.js +124 -0
  19. package/src/media/uploader.js +0 -30
  20. package/src/media/video.js +1 -39
  21. package/src/parsers/code-tokenizer-keywords.js +0 -12
  22. package/src/parsers/code-tokenizer.js +0 -42
  23. package/src/parsers/index.js +0 -7
  24. package/src/parsers/inline-entity.js +8 -117
  25. package/src/parsers/table-metadata.js +1 -35
  26. package/src/proto/enums.js +9 -65
  27. package/src/proto/layouts.js +3 -64
  28. package/src/proto/primitives.js +4 -91
  29. package/src/proto/relay-nodes.js +1 -32
  30. package/src/proto/rich-response.js +6 -57
  31. package/src/proto/updater.js +0 -85
  32. package/src/services/index.js +0 -7
  33. package/src/services/media-service.js +1 -102
  34. package/src/services/message-service.js +16 -158
  35. package/src/services/proto-service.js +3 -57
  36. package/src/utils/id.js +0 -25
  37. package/src/utils/logger.js +2 -39
  38. package/src/utils/mime.js +17 -73
  39. package/src/utils/promise.js +0 -26
  40. package/src/utils/validator.js +6 -71
@@ -1,102 +1,47 @@
1
- /**
2
- * @file message-service.js
3
- * @module lumina/services/message-service
4
- *
5
- * High-level message senders for every "basic" WhatsApp content type:
6
- * text, image, video, audio, document, sticker, contact, location, poll,
7
- * reply, react, delete, forward, copy, edit.
8
- *
9
- * Each method delegates to {@link Connection} so that the Bot wrapper stays
10
- * thin and the service layer never imports Baileys directly (Layer 3 must
11
- * not touch the adapter's internals).
12
- */
13
-
14
1
  import { coerceMediaSource } from '../utils/validator.js'
15
2
 
16
- /** @typedef {import('../client/connection.js').Connection} Connection */
17
- /** @typedef {import('./proto-service.js').ProtoService} ProtoService */
18
- /** @typedef {import('./media-service.js').MediaService} MediaService */
19
-
20
3
  export class MessageService {
21
- /**
22
- * @param {Connection} conn
23
- * @param {ProtoService} proto
24
- * @param {MediaService} media
25
- */
26
4
  constructor(conn, proto, media) {
27
5
  this.conn = conn
28
6
  this.proto = proto
29
7
  this.media = media
30
8
  }
31
9
 
32
- /**
33
- * @param {string} jid
34
- * @param {string} text
35
- * @param {object} [opts] Forwarded to generateWAMessageFromContent.
36
- * @returns {Promise<object>}
37
- */
38
- async text(jid, text, opts) {
39
- const msg = await this.conn.generateMessage(jid, { conversation: text }, opts ?? {})
40
- await this.conn.relayMessage(msg.key.remoteJid, msg.message, {
41
- messageId: msg.key.id,
42
- ...(opts ?? {}),
43
- })
10
+ async text(jid, text, opts = {}) {
11
+ const msg = await this.conn.generateMessage(jid, { conversation: text }, opts)
12
+ await this.conn.relayMessage(msg.key.remoteJid, msg.message, { messageId: msg.key.id, ...opts })
44
13
  return msg
45
14
  }
46
15
 
47
- /** @param {string | Buffer} source */
48
16
  async image(jid, source, caption, opts = {}) {
49
17
  return this.#sendMedia('image', jid, source, caption, opts)
50
18
  }
51
19
 
52
- /** @param {string | Buffer} source */
53
20
  async video(jid, source, caption, opts = {}) {
54
21
  return this.#sendMedia('video', jid, source, caption, opts)
55
22
  }
56
23
 
57
- /** @param {string | Buffer} source */
58
24
  async audio(jid, source, opts = {}) {
59
25
  return this.#sendMedia('audio', jid, source, undefined, { ...opts, ptt: opts.ptt ?? false })
60
26
  }
61
27
 
62
- /** @param {string | Buffer} source */
63
28
  async document(jid, source, opts = {}) {
64
29
  return this.#sendMedia('document', jid, source, undefined, opts)
65
30
  }
66
31
 
67
- /** @param {string | Buffer} source */
68
32
  async sticker(jid, source, opts = {}) {
69
33
  return this.#sendMedia('sticker', jid, source, undefined, opts)
70
34
  }
71
35
 
72
- /**
73
- * @param {'image'|'video'|'audio'|'document'|'sticker'} type
74
- * @param {string} jid
75
- * @param {string | Buffer} source
76
- * @param {string} [caption]
77
- * @param {object} opts
78
- */
79
36
  async #sendMedia(type, jid, source, caption, opts = {}) {
80
37
  const { raw } = coerceMediaSource(source)
81
- const msg = await this.conn.generateMessage(
82
- jid,
83
- { [type]: raw, caption, ...opts },
84
- opts,
85
- )
86
- await this.conn.relayMessage(msg.key.remoteJid, msg.message, {
87
- messageId: msg.key.id,
88
- ...opts,
89
- })
38
+ const content = { [type]: raw }
39
+ if (caption !== undefined) content.caption = caption
40
+ const msg = await this.conn.generateMessage(jid, content, opts)
41
+ await this.conn.relayMessage(msg.key.remoteJid, msg.message, { messageId: msg.key.id, ...opts })
90
42
  return msg
91
43
  }
92
44
 
93
- /**
94
- * Send one or more contacts.
95
- *
96
- * @param {string} jid
97
- * @param {Array<{ name: string, number: string }>} contacts
98
- * @param {object} [opts]
99
- */
100
45
  async contact(jid, contacts, opts = {}) {
101
46
  const vCard = (c) =>
102
47
  `BEGIN:VCARD\nVERSION:3.0\nFN:${c.name}\nTEL;type=CELL;type=VOICE;waid=${c.number}:${c.number}\nEND:VCARD`
@@ -104,22 +49,13 @@ export class MessageService {
104
49
  if (contacts.length === 1) {
105
50
  const msg = await this.conn.generateMessage(
106
51
  jid,
107
- {
108
- contactMessage: {
109
- displayName: contacts[0].name,
110
- vcard: vCard(contacts[0]),
111
- },
112
- },
52
+ { contactMessage: { displayName: contacts[0].name, vcard: vCard(contacts[0]) } },
113
53
  opts,
114
54
  )
115
- await this.conn.relayMessage(msg.key.remoteJid, msg.message, {
116
- messageId: msg.key.id,
117
- ...opts,
118
- })
55
+ await this.conn.relayMessage(msg.key.remoteJid, msg.message, { messageId: msg.key.id, ...opts })
119
56
  return msg
120
57
  }
121
58
 
122
- // Multi-contact: use contactsMessage
123
59
  const multi = await this.conn.generateMessage(
124
60
  jid,
125
61
  {
@@ -130,19 +66,10 @@ export class MessageService {
130
66
  },
131
67
  opts,
132
68
  )
133
- await this.conn.relayMessage(multi.key.remoteJid, multi.message, {
134
- messageId: multi.key.id,
135
- ...opts,
136
- })
69
+ await this.conn.relayMessage(multi.key.remoteJid, multi.message, { messageId: multi.key.id, ...opts })
137
70
  return multi
138
71
  }
139
72
 
140
- /**
141
- * @param {string} jid
142
- * @param {number} lat
143
- * @param {number} lng
144
- * @param {object} [opts] Optional name/address/urls.
145
- */
146
73
  async location(jid, lat, lng, opts = {}) {
147
74
  const msg = await this.conn.generateMessage(
148
75
  jid,
@@ -157,19 +84,10 @@ export class MessageService {
157
84
  },
158
85
  opts,
159
86
  )
160
- await this.conn.relayMessage(msg.key.remoteJid, msg.message, {
161
- messageId: msg.key.id,
162
- ...opts,
163
- })
87
+ await this.conn.relayMessage(msg.key.remoteJid, msg.message, { messageId: msg.key.id, ...opts })
164
88
  return msg
165
89
  }
166
90
 
167
- /**
168
- * @param {string} jid
169
- * @param {string} name
170
- * @param {string[]} options
171
- * @param {object} [opts] { selectableCount, public, messageSecret }
172
- */
173
91
  async poll(jid, name, options, opts = {}) {
174
92
  const msg = await this.conn.generatePoll(jid, {
175
93
  name,
@@ -177,21 +95,10 @@ export class MessageService {
177
95
  selectableCount: opts.selectableCount ?? 1,
178
96
  toJid: jid,
179
97
  })
180
- await this.conn.relayMessage(msg.key.remoteJid, msg.message, {
181
- messageId: msg.key.id,
182
- ...opts,
183
- })
98
+ await this.conn.relayMessage(msg.key.remoteJid, msg.message, { messageId: msg.key.id, ...opts })
184
99
  return msg
185
100
  }
186
101
 
187
- /**
188
- * Reply to a quoted message.
189
- *
190
- * @param {string} jid
191
- * @param {string} text
192
- * @param {object} quoted Quoted message object (must have `.key`).
193
- * @param {object} [opts]
194
- */
195
102
  async reply(jid, text, quoted, opts = {}) {
196
103
  const msg = await this.conn.generateMessage(
197
104
  jid,
@@ -207,81 +114,32 @@ export class MessageService {
207
114
  },
208
115
  { quoted, ...opts },
209
116
  )
210
- await this.conn.relayMessage(msg.key.remoteJid, msg.message, {
211
- messageId: msg.key.id,
212
- ...opts,
213
- })
117
+ await this.conn.relayMessage(msg.key.remoteJid, msg.message, { messageId: msg.key.id, ...opts })
214
118
  return msg
215
119
  }
216
120
 
217
- /**
218
- * React to a message.
219
- *
220
- * @param {string} jid
221
- * @param {object} key
222
- * @param {string} emoji
223
- * @param {object} [opts]
224
- */
225
121
  async react(jid, key, emoji, opts = {}) {
226
122
  const msg = await this.conn.generateReaction(jid, { key, text: emoji })
227
- await this.conn.relayMessage(msg.key.remoteJid, msg.message, {
228
- messageId: msg.key.id,
229
- ...opts,
230
- })
123
+ await this.conn.relayMessage(msg.key.remoteJid, msg.message, { messageId: msg.key.id, ...opts })
231
124
  return msg
232
125
  }
233
126
 
234
- /**
235
- * Delete a message (revoke).
236
- *
237
- * @param {string} jid
238
- * @param {object} key
239
- */
240
127
  async delete(jid, key) {
241
128
  return this.conn.raw.sendMessage(jid, { delete: key })
242
129
  }
243
130
 
244
- /**
245
- * Forward a message.
246
- *
247
- * @param {string} jid
248
- * @param {object} message The message object to forward.
249
- * @param {object} [opts] { quoted, additionalNodes }
250
- */
251
131
  async forward(jid, message, opts = {}) {
252
132
  return this.conn.raw.forwardMessage(jid, message, opts)
253
133
  }
254
134
 
255
- /**
256
- * Copy a message (re-send its content as a new message).
257
- *
258
- * @param {string} jid
259
- * @param {object} message
260
- * @param {object} [opts]
261
- */
262
135
  async copy(jid, message, opts = {}) {
263
136
  const msg = await this.conn.generateMessage(jid, message, opts)
264
- await this.conn.relayMessage(msg.key.remoteJid, msg.message, {
265
- messageId: msg.key.id,
266
- ...opts,
267
- })
137
+ await this.conn.relayMessage(msg.key.remoteJid, msg.message, { messageId: msg.key.id, ...opts })
268
138
  return msg
269
139
  }
270
140
 
271
- /**
272
- * Edit a previously sent text message.
273
- *
274
- * @param {string} jid
275
- * @param {object} key
276
- * @param {string} newText
277
- * @param {object} [opts]
278
- */
279
141
  async edit(jid, key, newText, opts = {}) {
280
- return this.conn.raw.sendMessage(jid, {
281
- edit: key,
282
- text: newText,
283
- ...opts,
284
- })
142
+ return this.conn.raw.sendMessage(jid, { edit: key, text: newText, ...opts })
285
143
  }
286
144
  }
287
145
 
@@ -1,87 +1,33 @@
1
- /**
2
- * @file proto-service.js
3
- * @module lumina/services/proto-service
4
- *
5
- * Wraps the lower-level `proto/*` modules with a {@link Connection} so that
6
- * callers don't have to thread the socket through every call.
7
- */
8
-
9
1
  import { createInteractiveNodes } from '../proto/relay-nodes.js'
10
2
 
11
- /** @typedef {import('../client/connection.js').Connection} Connection */
12
-
13
3
  export class ProtoService {
14
- /** @param {Connection} conn */
15
4
  constructor(conn) {
16
5
  if (!conn) throw new Error('Connection is required for ProtoService')
17
6
  this.conn = conn
18
7
  }
19
8
 
20
- /**
21
- * Generate a WA message object from a content descriptor.
22
- *
23
- * @param {string} jid
24
- * @param {object} content
25
- * @param {object} [opts]
26
- * @returns {Promise<object>}
27
- */
28
9
  async generate(jid, content, opts) {
29
10
  return this.conn.generateMessage(jid, content, opts ?? {})
30
11
  }
31
12
 
32
- /**
33
- * Relay a pre-built message. Returns the message ID.
34
- *
35
- * @param {string} jid
36
- * @param {object} message
37
- * @param {object} [opts]
38
- * @returns {Promise<string>}
39
- */
40
13
  async relay(jid, message, opts) {
41
14
  return this.conn.relayMessage(jid, message, opts ?? {})
42
15
  }
43
16
 
44
- /**
45
- * Relay a pre-built message WITH the biz/native_flow/mixed additional
46
- * nodes. This is the variant used by every interactive builder (Button,
47
- * ButtonV2, Carousel).
48
- *
49
- * @param {string} jid
50
- * @param {object} message
51
- * @param {object} [opts]
52
- * @returns {Promise<string>}
53
- */
54
17
  async relayInteractive(jid, message, opts = {}) {
55
- const nodes = opts.additionalNodes ?? createInteractiveNodes()
56
18
  return this.conn.relayMessage(jid, message, {
57
19
  ...opts,
58
- additionalNodes: nodes,
20
+ additionalNodes: opts.additionalNodes ?? createInteractiveNodes(),
59
21
  })
60
22
  }
61
23
 
62
- /**
63
- * Generate + relay in one shot. Returns the generated message object
64
- * (with `.key` populated by Baileys) so callers can keep a reference.
65
- *
66
- * @param {string} jid
67
- * @param {object} content
68
- * @param {object} [opts]
69
- * @param {boolean} [opts.interactive=false] If true, attach the biz/native_flow nodes.
70
- * @returns {Promise<object>}
71
- */
72
24
  async generateAndRelay(jid, content, opts = {}) {
73
25
  const { interactive = false, ...rest } = opts
74
26
  const msg = await this.conn.generateMessage(jid, content, rest)
75
27
  if (interactive) {
76
- await this.relayInteractive(msg.key.remoteJid, msg.message, {
77
- messageId: msg.key.id,
78
- ...rest,
79
- })
28
+ await this.relayInteractive(msg.key.remoteJid, msg.message, { messageId: msg.key.id, ...rest })
80
29
  } else {
81
- await this.relay(msg.key.remoteJid, msg.message, {
82
- messageId: msg.key.id,
83
- ...rest,
84
- })
30
+ await this.relay(msg.key.remoteJid, msg.message, { messageId: msg.key.id, ...rest })
85
31
  }
86
32
  return msg
87
33
  }
package/src/utils/id.js CHANGED
@@ -1,40 +1,15 @@
1
- /**
2
- * @file id.js
3
- * @module lumina/utils/id
4
- *
5
- * Stable, collision-resistant identifier generation. Replaces the obfuscated
6
- * `\u004E\u0049\u0058\u0045\u004C_…` prefix that was sprinkled across the
7
- * legacy extractIE function.
8
- */
9
-
10
1
  import crypto from 'node:crypto'
11
2
 
12
- /** Project-wide prefix for inline-entity keys. */
13
3
  export const ENTITY_PREFIX = 'LUMINA'
14
4
 
15
- /** Generate a fresh RFC-4122 v4 UUID. */
16
5
  export function uuid() {
17
6
  return crypto.randomUUID()
18
7
  }
19
8
 
20
- /**
21
- * Build a stable, scoped inline-entity key.
22
- *
23
- * @param {'HYPERLINK'|'CITATION'|'LATEX'} kind
24
- * @param {number} index Zero-based counter.
25
- * @returns {string} e.g. `LUMINA_HYPERLINK_0`
26
- */
27
9
  export function entityKey(kind, index) {
28
10
  return `${ENTITY_PREFIX}_${kind}_${index}`
29
11
  }
30
12
 
31
- /**
32
- * Hash a string with SHA-256, return hex digest (used by ProtoUpdater for
33
- * backup integrity verification).
34
- *
35
- * @param {string} content
36
- * @returns {string}
37
- */
38
13
  export function sha256(content) {
39
14
  return crypto.createHash('sha256').update(content).digest('hex')
40
15
  }
@@ -1,38 +1,5 @@
1
- /**
2
- * @file logger.js
3
- * @module lumina/utils/logger
4
- *
5
- * Minimal, zero-dependency logger with child scopes and level filtering.
6
- * Compatible with the pino/winston subset: { error, warn, info, debug, child }.
7
- *
8
- * Default logger is a no-op — zero overhead in production. Inject your own
9
- * logger via `new Bot(socket, { logger })` to enable output.
10
- */
1
+ const LEVEL_ORDER = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 }
11
2
 
12
- /** @typedef {'silent'|'error'|'warn'|'info'|'debug'} LogLevel */
13
-
14
- const LEVEL_ORDER = /** @type {const} */ ({
15
- silent: 0,
16
- error: 1,
17
- warn: 2,
18
- info: 3,
19
- debug: 4,
20
- })
21
-
22
- const NOOP = () => {}
23
-
24
- /**
25
- * Build a logger from a single options object.
26
- *
27
- * @param {object} [opts]
28
- * @param {LogLevel} [opts.level='warn'] Minimum level emitted.
29
- * @param {boolean} [opts.silent=false] Convenience: forces level 'silent'.
30
- * @param {(level: string, scope: string, args: any[]) => void} [opts.transport]
31
- * Custom transport. Receives the level name, the current scope, and the
32
- * raw arguments array. Default transport writes to console.
33
- * @param {string} [opts.scope='lumina'] Initial scope.
34
- * @returns {Logger}
35
- */
36
3
  export function createLogger(opts = {}) {
37
4
  const level = opts.silent ? 'silent' : opts.level ?? 'warn'
38
5
  const threshold = LEVEL_ORDER[level] ?? LEVEL_ORDER.warn
@@ -45,9 +12,7 @@ export function createLogger(opts = {}) {
45
12
  })
46
13
 
47
14
  const make = (scope) => {
48
- const emit = (lvl, args) => {
49
- if (LEVEL_ORDER[lvl] <= threshold) transport(lvl, scope, args)
50
- }
15
+ const emit = (lvl, args) => { if (LEVEL_ORDER[lvl] <= threshold) transport(lvl, scope, args) }
51
16
  return {
52
17
  error: (...a) => emit('error', a),
53
18
  warn: (...a) => emit('warn', a),
@@ -60,6 +25,4 @@ export function createLogger(opts = {}) {
60
25
  return make(scope0)
61
26
  }
62
27
 
63
- /** @typedef {ReturnType<createLogger>} Logger */
64
-
65
28
  export default createLogger
package/src/utils/mime.js CHANGED
@@ -1,99 +1,43 @@
1
- /**
2
- * @file mime.js
3
- * @module lumina/utils/mime
4
- *
5
- * Sniff & lookup MIME types from buffer magic bytes and file extensions.
6
- * Replaces the hardcoded `mime_type: 'image/png'` / `'video/mp4'` literals
7
- * sprinkled across the legacy AIRich builder.
8
- */
9
-
10
- /** @type {Record<string, string>} */
11
1
  const EXT_MAP = {
12
- png: 'image/png',
13
- jpg: 'image/jpeg',
14
- jpeg: 'image/jpeg',
15
- gif: 'image/gif',
16
- webp: 'image/webp',
17
- bmp: 'image/bmp',
18
- tiff: 'image/tiff',
19
- mp4: 'video/mp4',
20
- webm: 'video/webm',
21
- mov: 'video/quicktime',
22
- avi: 'video/x-msvideo',
23
- mkv: 'video/x-matroska',
24
- mp3: 'audio/mpeg',
25
- ogg: 'audio/ogg',
26
- wav: 'audio/wav',
27
- opus: 'audio/opus',
28
- m4a: 'audio/mp4',
29
- pdf: 'application/pdf',
30
- json: 'application/json',
31
- zip: 'application/zip',
32
- txt: 'text/plain',
33
- html: 'text/html',
34
- csv: 'text/csv',
2
+ png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif',
3
+ webp: 'image/webp', bmp: 'image/bmp', tiff: 'image/tiff', mp4: 'video/mp4',
4
+ webm: 'video/webm', mov: 'video/quicktime', avi: 'video/x-msvideo',
5
+ mkv: 'video/x-matroska', mp3: 'audio/mpeg', ogg: 'audio/ogg', wav: 'audio/wav',
6
+ opus: 'audio/opus', m4a: 'audio/mp4', pdf: 'application/pdf',
7
+ json: 'application/json', zip: 'application/zip', txt: 'text/plain',
8
+ html: 'text/html', csv: 'text/csv',
9
+ }
10
+
11
+ function fromExtension(filename) {
12
+ const ext = filename.split('.').pop()?.toLowerCase() ?? ''
13
+ return EXT_MAP[ext] ?? 'application/octet-stream'
35
14
  }
36
15
 
37
- /**
38
- * Sniff MIME type from buffer magic bytes. Falls back to extension lookup.
39
- *
40
- * @param {Buffer} buf
41
- * @param {string} [filename] Optional filename for extension fallback.
42
- * @returns {string} MIME type, or 'application/octet-stream' if unknown.
43
- */
44
16
  export function sniffMime(buf, filename) {
45
17
  if (!Buffer.isBuffer(buf) || buf.length < 4) {
46
18
  return filename ? fromExtension(filename) : 'application/octet-stream'
47
19
  }
48
- // PNG
49
20
  if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) return 'image/png'
50
- // JPEG (FFD8FF)
51
21
  if (buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) return 'image/jpeg'
52
- // GIF (GIF87a / GIF89a)
53
22
  if (buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46) return 'image/gif'
54
- // WebP RIFF....WEBP
55
- if (
56
- buf.length >= 12 &&
57
- buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 &&
58
- buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50
59
- ) return 'image/webp'
60
- // MP4 — ftyp box at offset 4
23
+ if (buf.length >= 12 && buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 &&
24
+ buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50) return 'image/webp'
61
25
  if (buf.length >= 12 && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70) {
62
26
  const brand = buf.toString('ascii', 8, 12)
63
27
  if (brand === 'qt ') return 'video/quicktime'
64
28
  if (brand === 'M4A ') return 'audio/mp4'
65
29
  return 'video/mp4'
66
30
  }
67
- // MP3 — ID3 tag or frame sync
68
31
  if (buf[0] === 0x49 && buf[1] === 0x44 && buf[2] === 0x33) return 'audio/mpeg'
69
32
  if (buf[0] === 0xff && (buf[1] & 0xe0) === 0xe0) return 'audio/mpeg'
70
- // OGG
71
33
  if (buf[0] === 0x4f && buf[1] === 0x67 && buf[2] === 0x67 && buf[3] === 0x53) return 'audio/ogg'
72
- // WAV RIFF....WAVE
73
- if (
74
- buf.length >= 12 &&
75
- buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 &&
76
- buf[8] === 0x57 && buf[9] === 0x41 && buf[10] === 0x56 && buf[11] === 0x45
77
- ) return 'audio/wav'
78
- // PDF
34
+ if (buf.length >= 12 && buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 &&
35
+ buf[8] === 0x57 && buf[9] === 0x41 && buf[10] === 0x56 && buf[11] === 0x45) return 'audio/wav'
79
36
  if (buf[0] === 0x25 && buf[1] === 0x50 && buf[2] === 0x44 && buf[3] === 0x46) return 'application/pdf'
80
- // ZIP
81
- if (buf[0] === 0x50 && buf[1] === 0x4b && (buf[2] === 0x03 || buf[2] === 0x05 || buf[2] === 0x07)) {
82
- return 'application/zip'
83
- }
37
+ if (buf[0] === 0x50 && buf[1] === 0x4b && (buf[2] === 0x03 || buf[2] === 0x05 || buf[2] === 0x07)) return 'application/zip'
84
38
  return filename ? fromExtension(filename) : 'application/octet-stream'
85
39
  }
86
40
 
87
- /** @param {string} filename */
88
- function fromExtension(filename) {
89
- const ext = filename.split('.').pop()?.toLowerCase() ?? ''
90
- return EXT_MAP[ext] ?? 'application/octet-stream'
91
- }
92
-
93
- /**
94
- * @param {string} mime
95
- * @returns {'image'|'video'|'audio'|'document'} WA media category.
96
- */
97
41
  export function mimeToCategory(mime) {
98
42
  if (mime.startsWith('image/')) return 'image'
99
43
  if (mime.startsWith('video/')) return 'video'
@@ -1,20 +1,3 @@
1
- /**
2
- * @file promise.js
3
- * @module lumina/utils/promise
4
- *
5
- * Promise helpers. The legacy `waitAllPromises` deep-resolver is preserved
6
- * here as `resolveDeep`, used only for backward-compat / lazy builder mode.
7
- * The default builder path uses eager await — see AIRichBuilder docs.
8
- */
9
-
10
- /**
11
- * Recursively walk an object/array tree, awaiting every Promise encountered.
12
- * Returns a structurally-identical tree with all Promises resolved.
13
- *
14
- * @template T
15
- * @param {T | Promise<T>} input
16
- * @returns {Promise<Awaited<T>>}
17
- */
18
1
  export async function resolveDeep(input) {
19
2
  const seen = await input
20
3
  if (seen && typeof seen.then === 'function') return resolveDeep(seen)
@@ -27,15 +10,6 @@ export async function resolveDeep(input) {
27
10
  return seen
28
11
  }
29
12
 
30
- /**
31
- * Run an async mapper over an array with a concurrency limit.
32
- *
33
- * @template T, R
34
- * @param {T[]} arr
35
- * @param {(item: T, i: number) => Promise<R>} mapper
36
- * @param {number} [concurrency=8]
37
- * @returns {Promise<R[]>}
38
- */
39
13
  export async function mapWithConcurrency(arr, mapper, concurrency = 8) {
40
14
  const results = new Array(arr.length)
41
15
  let cursor = 0