@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.
- package/package.json +1 -1
- package/src/builders/ai-rich.js +0 -165
- package/src/builders/base.js +4 -51
- package/src/builders/button-v2.js +13 -78
- package/src/builders/button.js +20 -234
- package/src/builders/card.js +9 -76
- package/src/builders/carousel.js +4 -61
- package/src/builders/index.js +1 -7
- package/src/builders/sticker.js +102 -0
- package/src/client/bot.js +28 -153
- package/src/client/connection.js +4 -111
- package/src/errors.js +0 -37
- package/src/index.d.ts +1 -28
- package/src/index.js +23 -121
- package/src/media/fetch.js +2 -33
- package/src/media/image.js +1 -41
- package/src/media/resolver.js +3 -55
- package/src/media/sticker.js +124 -0
- package/src/media/uploader.js +0 -30
- package/src/media/video.js +1 -39
- package/src/parsers/code-tokenizer-keywords.js +0 -12
- package/src/parsers/code-tokenizer.js +0 -42
- package/src/parsers/index.js +0 -7
- package/src/parsers/inline-entity.js +8 -117
- package/src/parsers/table-metadata.js +1 -35
- package/src/proto/enums.js +9 -65
- package/src/proto/layouts.js +3 -64
- package/src/proto/primitives.js +4 -91
- package/src/proto/relay-nodes.js +1 -32
- package/src/proto/rich-response.js +6 -57
- package/src/proto/updater.js +0 -85
- package/src/services/index.js +0 -7
- package/src/services/media-service.js +1 -102
- package/src/services/message-service.js +16 -158
- package/src/services/proto-service.js +3 -57
- package/src/utils/id.js +0 -25
- package/src/utils/logger.js +2 -39
- package/src/utils/mime.js +17 -73
- package/src/utils/promise.js +0 -26
- package/src/utils/validator.js +6 -71
|
@@ -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
|
-
|
|
34
|
-
|
|
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
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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:
|
|
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
|
}
|
package/src/utils/logger.js
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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'
|
package/src/utils/promise.js
CHANGED
|
@@ -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
|