@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
package/src/proto/primitives.js
CHANGED
|
@@ -1,85 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
* @file primitives.js
|
|
3
|
-
* @module lumina/proto/primitives
|
|
4
|
-
*
|
|
5
|
-
* Pure factory functions for every GenAI "UX primitive" used by the AI Rich
|
|
6
|
-
* Response envelope. Each factory returns a plain object ready to be wrapped
|
|
7
|
-
* by a layout view-model (see {@link module:proto/layouts}).
|
|
8
|
-
*
|
|
9
|
-
* All `__typename` strings are sourced from {@link module:proto/enums.TYPENAME}
|
|
10
|
-
* — never hardcode them here.
|
|
11
|
-
*/
|
|
1
|
+
import { TYPENAME, SourceType, PromptType } from './enums.js'
|
|
12
2
|
|
|
13
|
-
import { TYPENAME, ImagineType, SourceType, PromptType } from './enums.js'
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Markdown-text primitive. May carry inline entities extracted by
|
|
17
|
-
* `extractInlineEntities`.
|
|
18
|
-
*
|
|
19
|
-
* @param {string} text
|
|
20
|
-
* @param {Array<object>} [inlineEntities]
|
|
21
|
-
*/
|
|
22
3
|
export function markdownTextPrimitive(text, inlineEntities) {
|
|
23
4
|
const primitive = { text, __typename: TYPENAME.MARKDOWN_TEXT }
|
|
24
5
|
if (inlineEntities?.length) primitive.inline_entities = inlineEntities
|
|
25
6
|
return primitive
|
|
26
7
|
}
|
|
27
8
|
|
|
28
|
-
/**
|
|
29
|
-
* Code primitive.
|
|
30
|
-
*
|
|
31
|
-
* @param {string} language
|
|
32
|
-
* @param {Array<{ content: string, type: string }>} unifiedBlocks
|
|
33
|
-
*/
|
|
34
9
|
export function codePrimitive(language, unifiedBlocks) {
|
|
35
|
-
return {
|
|
36
|
-
language,
|
|
37
|
-
code_blocks: unifiedBlocks,
|
|
38
|
-
__typename: TYPENAME.CODE,
|
|
39
|
-
}
|
|
10
|
+
return { language, code_blocks: unifiedBlocks, __typename: TYPENAME.CODE }
|
|
40
11
|
}
|
|
41
12
|
|
|
42
|
-
/**
|
|
43
|
-
* Table primitive.
|
|
44
|
-
*
|
|
45
|
-
* @param {Array<object>} unifiedRows Output of `toTableMetadata().unifiedRows`.
|
|
46
|
-
*/
|
|
47
13
|
export function tablePrimitive(unifiedRows) {
|
|
48
|
-
return {
|
|
49
|
-
rows: unifiedRows,
|
|
50
|
-
__typename: TYPENAME.TABLE,
|
|
51
|
-
}
|
|
14
|
+
return { rows: unifiedRows, __typename: TYPENAME.TABLE }
|
|
52
15
|
}
|
|
53
16
|
|
|
54
|
-
/**
|
|
55
|
-
* Search-result (sources) primitive.
|
|
56
|
-
*
|
|
57
|
-
* @param {Array<object>} sources Pre-shaped source objects.
|
|
58
|
-
*/
|
|
59
17
|
export function searchResultPrimitive(sources) {
|
|
60
|
-
return {
|
|
61
|
-
sources,
|
|
62
|
-
__typename: TYPENAME.SEARCH_RESULT,
|
|
63
|
-
}
|
|
18
|
+
return { sources, __typename: TYPENAME.SEARCH_RESULT }
|
|
64
19
|
}
|
|
65
20
|
|
|
66
|
-
/**
|
|
67
|
-
* Reel primitive (one per reel item).
|
|
68
|
-
*
|
|
69
|
-
* @param {object} item Pre-shaped reel descriptor.
|
|
70
|
-
*/
|
|
71
21
|
export function reelPrimitive(item) {
|
|
72
22
|
return { ...item, __typename: TYPENAME.REEL }
|
|
73
23
|
}
|
|
74
24
|
|
|
75
|
-
/**
|
|
76
|
-
* Imagine primitive — used for both images and videos (animate).
|
|
77
|
-
*
|
|
78
|
-
* @param {{ url: string, mime_type: string, file_length?: number, duration?: number }} media
|
|
79
|
-
* @param {'IMAGE'|'ANIMATE'} kind
|
|
80
|
-
* @param {string} [status='READY']
|
|
81
|
-
* @param {string} [thumbnailBase64] Base64 JPEG thumbnail (video only).
|
|
82
|
-
*/
|
|
83
25
|
export function imaginePrimitive(media, kind, status = 'READY', thumbnailBase64) {
|
|
84
26
|
const primitive = {
|
|
85
27
|
media,
|
|
@@ -91,39 +33,18 @@ export function imaginePrimitive(media, kind, status = 'READY', thumbnailBase64)
|
|
|
91
33
|
return primitive
|
|
92
34
|
}
|
|
93
35
|
|
|
94
|
-
/**
|
|
95
|
-
* Product-card primitive.
|
|
96
|
-
*
|
|
97
|
-
* @param {object} item Pre-shaped product descriptor (already media-resolved).
|
|
98
|
-
*/
|
|
99
36
|
export function productCardPrimitive(item) {
|
|
100
37
|
return { ...item, __typename: TYPENAME.PRODUCT_CARD }
|
|
101
38
|
}
|
|
102
39
|
|
|
103
|
-
/**
|
|
104
|
-
* Post primitive.
|
|
105
|
-
*
|
|
106
|
-
* @param {object} p Pre-shaped post descriptor (already media-resolved).
|
|
107
|
-
* @param {boolean} isCarousel
|
|
108
|
-
*/
|
|
109
40
|
export function postPrimitive(p, isCarousel) {
|
|
110
41
|
return { ...p, is_carousel: isCarousel, __typename: TYPENAME.POST }
|
|
111
42
|
}
|
|
112
43
|
|
|
113
|
-
/**
|
|
114
|
-
* Metadata-text primitive (used for footer / tip).
|
|
115
|
-
*
|
|
116
|
-
* @param {string} text
|
|
117
|
-
*/
|
|
118
44
|
export function metadataTextPrimitive(text) {
|
|
119
45
|
return { text, __typename: TYPENAME.METADATA_TEXT }
|
|
120
46
|
}
|
|
121
47
|
|
|
122
|
-
/**
|
|
123
|
-
* Follow-up suggestion pill primitive.
|
|
124
|
-
*
|
|
125
|
-
* @param {string} text
|
|
126
|
-
*/
|
|
127
48
|
export function followUpSuggestionPillPrimitive(text) {
|
|
128
49
|
return {
|
|
129
50
|
prompt_text: text,
|
|
@@ -132,11 +53,6 @@ export function followUpSuggestionPillPrimitive(text) {
|
|
|
132
53
|
}
|
|
133
54
|
}
|
|
134
55
|
|
|
135
|
-
/**
|
|
136
|
-
* Helper: shape a source entry for `searchResultPrimitive`.
|
|
137
|
-
*
|
|
138
|
-
* @param {{ iconUrl?: string, url?: string, text?: string, subtitle?: string }} src
|
|
139
|
-
*/
|
|
140
56
|
export function shapeSourceEntry({ iconUrl = '', url = '', text = '', subtitle = 'AI' } = {}) {
|
|
141
57
|
return {
|
|
142
58
|
source_type: SourceType.THIRD_PARTY,
|
|
@@ -147,9 +63,6 @@ export function shapeSourceEntry({ iconUrl = '', url = '', text = '', subtitle =
|
|
|
147
63
|
}
|
|
148
64
|
}
|
|
149
65
|
|
|
150
|
-
/**
|
|
151
|
-
* Helper: shape a single reel item for `reelPrimitive`.
|
|
152
|
-
*/
|
|
153
66
|
export function shapeReelEntry(item) {
|
|
154
67
|
return {
|
|
155
68
|
reels_url: item.videoUrl ?? item.url ?? '',
|
package/src/proto/relay-nodes.js
CHANGED
|
@@ -1,26 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file relay-nodes.js
|
|
3
|
-
* @module lumina/proto/relay-nodes
|
|
4
|
-
*
|
|
5
|
-
* Single source of truth for the `additionalNodes` block that Baileys
|
|
6
|
-
* `relayMessage` expects when sending interactive (native-flow) messages.
|
|
7
|
-
*
|
|
8
|
-
* The legacy code duplicated this 12-line block three times (Button.send,
|
|
9
|
-
* ButtonV2.send, Carousel.send). Lumina centralises it here.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
1
|
import { NativeFlow } from './enums.js'
|
|
13
2
|
|
|
14
|
-
/**
|
|
15
|
-
* Build the standard biz/interactive/native_flow additional-nodes block.
|
|
16
|
-
*
|
|
17
|
-
* @param {object} [opts]
|
|
18
|
-
* @param {string} [opts.outerVersion=NativeFlow.OUTER_VERSION]
|
|
19
|
-
* @param {string} [opts.flowVersion=NativeFlow.FLOW_VERSION]
|
|
20
|
-
* @param {string} [opts.flowName=NativeFlow.FLOW_NAME_MIXED]
|
|
21
|
-
* @param {boolean} [opts.includeBiz=true] Whether to wrap in the `<biz>` node.
|
|
22
|
-
* @returns {Array<object>} Array suitable for `relayMessage`'s `additionalNodes`.
|
|
23
|
-
*/
|
|
24
3
|
export function createInteractiveNodes(opts = {}) {
|
|
25
4
|
const outerVersion = opts.outerVersion ?? NativeFlow.OUTER_VERSION
|
|
26
5
|
const flowVersion = opts.flowVersion ?? NativeFlow.FLOW_VERSION
|
|
@@ -35,19 +14,9 @@ export function createInteractiveNodes(opts = {}) {
|
|
|
35
14
|
|
|
36
15
|
if (!includeBiz) return [interactive]
|
|
37
16
|
|
|
38
|
-
return [
|
|
39
|
-
{
|
|
40
|
-
tag: NativeFlow.BIZ_TAG,
|
|
41
|
-
attrs: {},
|
|
42
|
-
content: [interactive],
|
|
43
|
-
},
|
|
44
|
-
]
|
|
17
|
+
return [{ tag: NativeFlow.BIZ_TAG, attrs: {}, content: [interactive] }]
|
|
45
18
|
}
|
|
46
19
|
|
|
47
|
-
/**
|
|
48
|
-
* Convenience: returns the same block but with `name: 'native_flow'` only
|
|
49
|
-
* (no biz wrapper). Useful for non-business accounts.
|
|
50
|
-
*/
|
|
51
20
|
export function createBareInteractiveNodes(opts = {}) {
|
|
52
21
|
return createInteractiveNodes({ ...opts, includeBiz: false })
|
|
53
22
|
}
|
|
@@ -1,46 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file rich-response.js
|
|
3
|
-
* @module lumina/proto/rich-response
|
|
4
|
-
*
|
|
5
|
-
* Assembles the `botForwardedMessage.richResponseMessage` envelope used by
|
|
6
|
-
* the AI Rich Response feature. Pure function — no socket access. The
|
|
7
|
-
* {@link AIRichBuilder} calls this from its `build()` method.
|
|
8
|
-
*
|
|
9
|
-
* Replaces the legacy `AIRich.build()` method (1436–1503) which:
|
|
10
|
-
* - Hardcoded `botJid: '0@bot'`
|
|
11
|
-
* - Hardcoded `forwardOrigin: 4`
|
|
12
|
-
* - Hardcoded `forwardingScore: 1`
|
|
13
|
-
* - Hardcoded personal leftover `disclaimerText: '~ Ahmad tumbuh kembang'`
|
|
14
|
-
* All four are now sourced from {@link module:proto/enums} / parameters.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
1
|
import crypto from 'node:crypto'
|
|
18
|
-
|
|
19
2
|
import { BOT_JID, ForwardOrigin } from './enums.js'
|
|
20
3
|
|
|
21
|
-
/**
|
|
22
|
-
* @typedef {object} RichResponseOptions
|
|
23
|
-
* @property {string} [title] Sets `botMetadata.messageDisclaimerText`.
|
|
24
|
-
* @property {Array<object>} sections Pre-resolved layout sections.
|
|
25
|
-
* @property {Array<object>} [submessages=[]] Pre-resolved submessage objects.
|
|
26
|
-
* @property {Array<object>} [richResponseSources=[]]
|
|
27
|
-
* @property {object} [contextInfo={}]
|
|
28
|
-
* @property {string} [footer] Appended as a metadata-text section.
|
|
29
|
-
* @property {boolean} [forwarded=true] Attach forward metadata.
|
|
30
|
-
* @property {boolean|object} [notification=false] Attach session-transparency metadata.
|
|
31
|
-
* @property {boolean} [includesUnifiedResponse=true]
|
|
32
|
-
* @property {boolean} [includesSubmessages=true]
|
|
33
|
-
* @property {object} [quoted] Quoted-message descriptor.
|
|
34
|
-
* @property {string} [quotedParticipant]
|
|
35
|
-
* @property {object} [extraPayload] Merged at top-level.
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Assemble a `botForwardedMessage` envelope.
|
|
40
|
-
*
|
|
41
|
-
* @param {RichResponseOptions} opts
|
|
42
|
-
* @returns {Promise<object>} The envelope, ready to pass to `relayMessage`.
|
|
43
|
-
*/
|
|
44
4
|
export async function assembleRichResponse({
|
|
45
5
|
title,
|
|
46
6
|
sections,
|
|
@@ -68,11 +28,11 @@ export async function assembleRichResponse({
|
|
|
68
28
|
const notificationMeta = notification
|
|
69
29
|
? {
|
|
70
30
|
sessionTransparencyMetadata: {
|
|
71
|
-
// User-supplied disclaimer (no personal leftovers).
|
|
72
31
|
disclaimerText: typeof notification === 'object' ? notification.disclaimerText ?? '' : '',
|
|
73
|
-
hcaId:
|
|
74
|
-
|
|
75
|
-
|
|
32
|
+
hcaId:
|
|
33
|
+
typeof notification === 'object' && notification.hcaId
|
|
34
|
+
? notification.hcaId
|
|
35
|
+
: `hca_${crypto.randomUUID()}`,
|
|
76
36
|
sessionTransparencyType: notification.sessionTransparencyType ?? 1,
|
|
77
37
|
},
|
|
78
38
|
}
|
|
@@ -91,10 +51,6 @@ export async function assembleRichResponse({
|
|
|
91
51
|
const finalSections = footer
|
|
92
52
|
? [
|
|
93
53
|
...sections,
|
|
94
|
-
// Footer is appended as a metadata-text primitive, wrapped in a
|
|
95
|
-
// Single layout view-model. Built inline to avoid a circular import
|
|
96
|
-
// with primitives/layouts — those are pure helpers, but this envelope
|
|
97
|
-
// assembler must remain dependency-light for testability.
|
|
98
54
|
{
|
|
99
55
|
view_model: {
|
|
100
56
|
primitive: { text: footer, __typename: 'GenAIMetadataTextPrimitive' },
|
|
@@ -123,18 +79,11 @@ export async function assembleRichResponse({
|
|
|
123
79
|
unifiedResponse: {
|
|
124
80
|
data: includesUnifiedResponse
|
|
125
81
|
? Buffer.from(
|
|
126
|
-
JSON.stringify({
|
|
127
|
-
response_id: crypto.randomUUID(),
|
|
128
|
-
sections: finalSections,
|
|
129
|
-
}),
|
|
82
|
+
JSON.stringify({ response_id: crypto.randomUUID(), sections: finalSections }),
|
|
130
83
|
).toString('base64')
|
|
131
84
|
: '',
|
|
132
85
|
},
|
|
133
|
-
contextInfo: {
|
|
134
|
-
...forwardMeta,
|
|
135
|
-
...quotedMeta,
|
|
136
|
-
...contextInfo,
|
|
137
|
-
},
|
|
86
|
+
contextInfo: { ...forwardMeta, ...quotedMeta, ...contextInfo },
|
|
138
87
|
},
|
|
139
88
|
},
|
|
140
89
|
},
|
package/src/proto/updater.js
CHANGED
|
@@ -1,24 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file updater.js
|
|
3
|
-
* @module lumina/proto/updater
|
|
4
1
|
*
|
|
5
|
-
* ProtoUpdater — automatic WAProto maintenance utility.
|
|
6
2
|
*
|
|
7
|
-
* WhatsApp constantly ships new protobuf definitions to its Web client, and
|
|
8
|
-
* the community `@whiskeysockets/baileys` package vendors a snapshot of those
|
|
9
|
-
* definitions as `WAProto/`. Lumina ships a small utility that:
|
|
10
3
|
*
|
|
11
|
-
* 1. Backs up the current WAProto tree before any mutation.
|
|
12
|
-
* 2. Transforms the generated CommonJS files into ESM (`require` → `import`,
|
|
13
|
-
* `module.exports` → `export`).
|
|
14
|
-
* 3. Fixes known upstream issues (e.g. the `HistorySyncType` enum that the
|
|
15
|
-
* `pbjs` generator sometimes emits with the wrong numeric tag).
|
|
16
|
-
* 4. Validates the result by `import()`-ing each module file.
|
|
17
|
-
* 5. Rolls back automatically on validation failure (configurable).
|
|
18
4
|
*
|
|
19
|
-
* The updater is opt-in: it never runs at import time. Users invoke it from
|
|
20
|
-
* the CLI or programmatically when they want to refresh the proto bundle.
|
|
21
|
-
*/
|
|
22
5
|
|
|
23
6
|
import { promises as fs } from 'node:fs'
|
|
24
7
|
import path from 'node:path'
|
|
@@ -28,19 +11,12 @@ import { ProtocolError } from '../errors.js'
|
|
|
28
11
|
import { sha256 } from '../utils/id.js'
|
|
29
12
|
import { createLogger } from '../utils/logger.js'
|
|
30
13
|
|
|
31
|
-
/** @typedef {import('../utils/logger.js').Logger} Logger */
|
|
32
14
|
|
|
33
|
-
/**
|
|
34
|
-
* Known WAProto fixes. Each entry: { file: RegExp, apply: (content: string) => string }.
|
|
35
|
-
* Keeping these in one place makes them trivial to audit & extend.
|
|
36
|
-
*/
|
|
37
15
|
const KNOWN_FIXES = [
|
|
38
16
|
{
|
|
39
17
|
name: 'history-sync-type-enum',
|
|
40
18
|
file: /HistorySync\.js$/,
|
|
41
19
|
apply: (c) =>
|
|
42
|
-
// Ensure `HistorySyncType.INITIAL_BOOTSTRAP = 0` exists — some pbjs runs
|
|
43
|
-
// emit it as `= 1` which breaks downstream comparison logic.
|
|
44
20
|
c.replace(
|
|
45
21
|
/(HistorySyncType\s*=\s*\{)[\s\S]*?\}/,
|
|
46
22
|
(m) =>
|
|
@@ -60,32 +36,21 @@ const KNOWN_FIXES = [
|
|
|
60
36
|
},
|
|
61
37
|
]
|
|
62
38
|
|
|
63
|
-
/** Pattern for CommonJS `require('...')` — used by CJS→ESM transform. */
|
|
64
39
|
const RE_REQUIRE = /require\(\s*(['"])([^'"]+)\1\s*\)/g
|
|
65
40
|
|
|
66
|
-
/** Pattern for `module.exports = ...` / `exports.x = ...`. */
|
|
67
41
|
const RE_MODULE_EXPORTS_ASSIGN = /module\.exports\s*=\s*/g
|
|
68
42
|
const RE_EXPORTS_DOT = /exports\.(\w+)\s*=\s*/g
|
|
69
43
|
|
|
70
|
-
/** Pattern for `const x = require('...')` — convert to `import`. */
|
|
71
44
|
const RE_CONST_REQUIRE =
|
|
72
45
|
/(?:const|let|var)\s+(\w+)\s*=\s*require\(\s*(['"])([^'"]+)\2\s*\)/g
|
|
73
46
|
|
|
74
|
-
/** Pattern for destructured `const { a, b } = require('...')`. */
|
|
75
47
|
const RE_DESTRUCT_REQUIRE =
|
|
76
48
|
/(?:const|let|var)\s*\{([^}]+)\}\s*=\s*require\(\s*(['"])([^'"]+)\2\s*\)/g
|
|
77
49
|
|
|
78
|
-
/**
|
|
79
|
-
* Convert a CommonJS module's source to ESM.
|
|
80
50
|
*
|
|
81
|
-
* @param {string} src Original CommonJS source.
|
|
82
|
-
* @returns {string} ESM source.
|
|
83
|
-
*/
|
|
84
51
|
export function transformToESM(src) {
|
|
85
52
|
let out = src
|
|
86
|
-
// 1. const X = require('mod') → import X from 'mod'
|
|
87
53
|
out = out.replace(RE_CONST_REQUIRE, (_, name, _q, mod) => `import ${name} from '${mod}'`)
|
|
88
|
-
// 2. const { a, b } = require('mod') → import { a, b } from 'mod'
|
|
89
54
|
out = out.replace(RE_DESTRUCT_REQUIRE, (_, names, _q, mod) => {
|
|
90
55
|
const cleaned = names
|
|
91
56
|
.split(',')
|
|
@@ -95,24 +60,14 @@ export function transformToESM(src) {
|
|
|
95
60
|
.join(', ')
|
|
96
61
|
return `import { ${cleaned} } from '${mod}'`
|
|
97
62
|
})
|
|
98
|
-
// 3. Bare require('mod') → import 'mod'
|
|
99
63
|
out = out.replace(RE_REQUIRE, (_, _q, mod) => `import '${mod}'`)
|
|
100
|
-
// 4. module.exports = X → export default X
|
|
101
64
|
out = out.replace(RE_MODULE_EXPORTS_ASSIGN, 'export default ')
|
|
102
|
-
// 5. exports.x = ... → export const x = ...
|
|
103
65
|
out = out.replace(RE_EXPORTS_DOT, (_, name) => `export const ${name} = `)
|
|
104
|
-
// 6. 'use strict' is implicit in ESM — drop it.
|
|
105
66
|
out = out.replace(/^['"]use strict['"];?\s*\n/m, '')
|
|
106
67
|
return out
|
|
107
68
|
}
|
|
108
69
|
|
|
109
|
-
/**
|
|
110
|
-
* Apply every known WAProto fix to a single file's content.
|
|
111
70
|
*
|
|
112
|
-
* @param {string} filename Absolute path of the file being patched.
|
|
113
|
-
* @param {string} content Current file content.
|
|
114
|
-
* @returns {{ content: string, applied: string[] }} New content + list of fix names that fired.
|
|
115
|
-
*/
|
|
116
71
|
export function applyKnownFixes(filename, content) {
|
|
117
72
|
const applied = []
|
|
118
73
|
let next = content
|
|
@@ -126,20 +81,14 @@ export function applyKnownFixes(filename, content) {
|
|
|
126
81
|
return { content: next, applied }
|
|
127
82
|
}
|
|
128
83
|
|
|
129
|
-
/**
|
|
130
|
-
* ProtoUpdater — high-level orchestration.
|
|
131
|
-
*/
|
|
132
84
|
export class ProtoUpdater {
|
|
133
|
-
/** @param {object} [opts] */
|
|
134
85
|
constructor(opts = {}) {
|
|
135
86
|
this.protoPath = path.resolve(opts.protoPath ?? this.#defaultProtoPath())
|
|
136
87
|
this.backupDir = path.resolve(opts.backupDir ?? path.join(this.protoPath, '..', 'WAProto.backups'))
|
|
137
88
|
this.logger = (opts.logger ?? createLogger({ level: 'warn' })).child('proto-updater')
|
|
138
|
-
/** @type {Array<{ id: string, timestamp: number, path: string, hash: string }>} */
|
|
139
89
|
this.history = []
|
|
140
90
|
}
|
|
141
91
|
|
|
142
|
-
/** Resolve the default WAProto directory inside the installed Baileys package. */
|
|
143
92
|
#defaultProtoPath() {
|
|
144
93
|
try {
|
|
145
94
|
const baileysPkg = require.resolve('@whiskeysockets/baileys/package.json', {
|
|
@@ -147,17 +96,11 @@ export class ProtoUpdater {
|
|
|
147
96
|
})
|
|
148
97
|
return path.join(path.dirname(baileysPkg), 'WAProto')
|
|
149
98
|
} catch {
|
|
150
|
-
// Fallback: assume CWD/WAProto
|
|
151
99
|
return path.join(process.cwd(), 'WAProto')
|
|
152
100
|
}
|
|
153
101
|
}
|
|
154
102
|
|
|
155
|
-
/**
|
|
156
|
-
* Walk a directory recursively, returning every `.js` file path.
|
|
157
103
|
*
|
|
158
|
-
* @param {string} dir
|
|
159
|
-
* @returns {Promise<string[]>}
|
|
160
|
-
*/
|
|
161
104
|
async #listJsFiles(dir) {
|
|
162
105
|
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
163
106
|
const out = []
|
|
@@ -169,11 +112,7 @@ export class ProtoUpdater {
|
|
|
169
112
|
return out
|
|
170
113
|
}
|
|
171
114
|
|
|
172
|
-
/**
|
|
173
|
-
* Create a timestamped backup of the current WAProto tree.
|
|
174
115
|
*
|
|
175
|
-
* @returns {Promise<{ id: string, timestamp: number, path: string, hash: string }>}
|
|
176
|
-
*/
|
|
177
116
|
async backup() {
|
|
178
117
|
await fs.mkdir(this.backupDir, { recursive: true })
|
|
179
118
|
const timestamp = Date.now()
|
|
@@ -188,13 +127,7 @@ export class ProtoUpdater {
|
|
|
188
127
|
return record
|
|
189
128
|
}
|
|
190
129
|
|
|
191
|
-
/**
|
|
192
|
-
* Recursively hash every `.js` file in a directory. Used to detect
|
|
193
|
-
* whether a transform actually changed anything.
|
|
194
130
|
*
|
|
195
|
-
* @param {string} dir
|
|
196
|
-
* @returns {Promise<string>}
|
|
197
|
-
*/
|
|
198
131
|
async #hashTree(dir) {
|
|
199
132
|
const files = (await this.#listJsFiles(dir)).sort()
|
|
200
133
|
const hashes = await Promise.all(
|
|
@@ -206,11 +139,7 @@ export class ProtoUpdater {
|
|
|
206
139
|
return sha256(hashes.join('\n'))
|
|
207
140
|
}
|
|
208
141
|
|
|
209
|
-
/**
|
|
210
|
-
* Restore a specific backup by id.
|
|
211
142
|
*
|
|
212
|
-
* @param {string} id
|
|
213
|
-
*/
|
|
214
143
|
async restore(id) {
|
|
215
144
|
const record = this.history.find((h) => h.id === id)
|
|
216
145
|
if (!record) {
|
|
@@ -221,9 +150,6 @@ export class ProtoUpdater {
|
|
|
221
150
|
this.logger.warn(`restored backup: ${id}`)
|
|
222
151
|
}
|
|
223
152
|
|
|
224
|
-
/**
|
|
225
|
-
* Rollback to the most recent successful backup.
|
|
226
|
-
*/
|
|
227
153
|
async rollback() {
|
|
228
154
|
if (this.history.length === 0) {
|
|
229
155
|
throw new ProtocolError('no backup available for rollback', {
|
|
@@ -234,11 +160,7 @@ export class ProtoUpdater {
|
|
|
234
160
|
await this.restore(last.id)
|
|
235
161
|
}
|
|
236
162
|
|
|
237
|
-
/**
|
|
238
|
-
* Validate the current WAProto tree by importing every `.js` file.
|
|
239
163
|
*
|
|
240
|
-
* @returns {Promise<{ ok: boolean, errors: Array<{ file: string, message: string }> }>}
|
|
241
|
-
*/
|
|
242
164
|
async validate() {
|
|
243
165
|
const files = await this.#listJsFiles(this.protoPath)
|
|
244
166
|
const errors = []
|
|
@@ -254,14 +176,7 @@ export class ProtoUpdater {
|
|
|
254
176
|
return { ok: errors.length === 0, errors }
|
|
255
177
|
}
|
|
256
178
|
|
|
257
|
-
/**
|
|
258
|
-
* Run the full update pipeline: backup → transform → fix → validate → commit/rollback.
|
|
259
179
|
*
|
|
260
|
-
* @param {object} [opts]
|
|
261
|
-
* @param {boolean} [opts.autoRollback=true] Rollback automatically on validation failure.
|
|
262
|
-
* @param {boolean} [opts.dryRun=false] Don't write to disk — return a diff summary.
|
|
263
|
-
* @returns {Promise<{ success: boolean, fromHash: string, toHash: string, backupId: string|null, appliedFixes: string[], errors: any[] }>}
|
|
264
|
-
*/
|
|
265
180
|
async update(opts = {}) {
|
|
266
181
|
const autoRollback = opts.autoRollback ?? true
|
|
267
182
|
const dryRun = opts.dryRun ?? false
|
package/src/services/index.js
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file services/index.js
|
|
3
|
-
* @module lumina/services
|
|
4
|
-
*
|
|
5
|
-
* Barrel re-export for the services layer.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
export { MediaService } from './media-service.js'
|
|
9
2
|
export { ProtoService } from './proto-service.js'
|
|
10
3
|
export { MessageService } from './message-service.js'
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file media-service.js
|
|
3
|
-
* @module lumina/services/media-service
|
|
4
|
-
*
|
|
5
|
-
* High-level orchestrator for every media operation. Composes the lower-level
|
|
6
|
-
* `media/*` modules with a Connection instance and (optionally) a small LRU
|
|
7
|
-
* cache for repeated URL fetches.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
import { fetchBuffer } from '../media/fetch.js'
|
|
11
2
|
import { resize, thumbnail } from '../media/image.js'
|
|
12
3
|
import { getMp4Duration, extractThumbnail } from '../media/video.js'
|
|
@@ -14,24 +5,9 @@ import { uploadToWhatsApp } from '../media/uploader.js'
|
|
|
14
5
|
import { resolveMedia } from '../media/resolver.js'
|
|
15
6
|
import { sniffMime, mimeToCategory } from '../utils/mime.js'
|
|
16
7
|
|
|
17
|
-
/** @typedef {import('../client/connection.js').Connection} Connection */
|
|
18
|
-
/** @typedef {import('../utils/logger.js').Logger} Logger */
|
|
19
|
-
|
|
20
8
|
const LRU_MAX = 128
|
|
21
9
|
|
|
22
|
-
/**
|
|
23
|
-
* @typedef {object} MediaServiceOptions
|
|
24
|
-
* @property {boolean} [cache=true] Enable URL→buffer LRU cache.
|
|
25
|
-
* @property {number} [cacheMax=128] Max cache entries.
|
|
26
|
-
* @property {Logger} [logger]
|
|
27
|
-
* @property {string} [uploadJid]
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
10
|
export class MediaService {
|
|
31
|
-
/**
|
|
32
|
-
* @param {Connection} conn
|
|
33
|
-
* @param {MediaServiceOptions} [opts]
|
|
34
|
-
*/
|
|
35
11
|
constructor(conn, opts = {}) {
|
|
36
12
|
if (!conn) throw new Error('Connection is required for MediaService')
|
|
37
13
|
this.conn = conn
|
|
@@ -39,51 +15,30 @@ export class MediaService {
|
|
|
39
15
|
this.uploadJid = opts.uploadJid
|
|
40
16
|
this.#cacheEnabled = opts.cache ?? true
|
|
41
17
|
this.#cacheMax = opts.cacheMax ?? LRU_MAX
|
|
42
|
-
/** @type {Map<string, { buf: Buffer, ts: number }>} */
|
|
43
18
|
this.#cache = new Map()
|
|
44
19
|
}
|
|
45
20
|
|
|
46
|
-
/** @type {boolean} */
|
|
47
21
|
#cacheEnabled
|
|
48
|
-
/** @type {number} */
|
|
49
22
|
#cacheMax
|
|
50
|
-
/** @type {Map<string, { buf: Buffer, ts: number }>} */
|
|
51
23
|
#cache
|
|
52
24
|
|
|
53
|
-
/**
|
|
54
|
-
* @param {string} url
|
|
55
|
-
* @returns {Buffer | undefined}
|
|
56
|
-
*/
|
|
57
25
|
#cacheGet(url) {
|
|
58
26
|
if (!this.#cacheEnabled) return undefined
|
|
59
27
|
const entry = this.#cache.get(url)
|
|
60
28
|
if (!entry) return undefined
|
|
61
|
-
// Refresh LRU order.
|
|
62
29
|
this.#cache.delete(url)
|
|
63
30
|
this.#cache.set(url, entry)
|
|
64
31
|
return entry.buf
|
|
65
32
|
}
|
|
66
33
|
|
|
67
|
-
/**
|
|
68
|
-
* @param {string} url
|
|
69
|
-
* @param {Buffer} buf
|
|
70
|
-
*/
|
|
71
34
|
#cacheSet(url, buf) {
|
|
72
35
|
if (!this.#cacheEnabled) return
|
|
73
36
|
if (this.#cache.size >= this.#cacheMax) {
|
|
74
|
-
|
|
75
|
-
this.#cache.delete(oldest)
|
|
37
|
+
this.#cache.delete(this.#cache.keys().next().value)
|
|
76
38
|
}
|
|
77
39
|
this.#cache.set(url, { buf, ts: Date.now() })
|
|
78
40
|
}
|
|
79
41
|
|
|
80
|
-
/**
|
|
81
|
-
* Fetch a URL into a Buffer. Uses the cache when enabled.
|
|
82
|
-
*
|
|
83
|
-
* @param {string} url
|
|
84
|
-
* @param {object} [opts] Forwarded to fetchBuffer.
|
|
85
|
-
* @returns {Promise<Buffer>}
|
|
86
|
-
*/
|
|
87
42
|
async fetch(url, opts) {
|
|
88
43
|
const cached = this.#cacheGet(url)
|
|
89
44
|
if (cached) return cached
|
|
@@ -92,90 +47,34 @@ export class MediaService {
|
|
|
92
47
|
return buf
|
|
93
48
|
}
|
|
94
49
|
|
|
95
|
-
/**
|
|
96
|
-
* Resize an image buffer.
|
|
97
|
-
*
|
|
98
|
-
* @param {Buffer} buf
|
|
99
|
-
* @param {object} opts
|
|
100
|
-
* @returns {Promise<Buffer>}
|
|
101
|
-
*/
|
|
102
50
|
async resize(buf, opts) {
|
|
103
51
|
return resize(buf, opts)
|
|
104
52
|
}
|
|
105
53
|
|
|
106
|
-
/**
|
|
107
|
-
* Generate a square thumbnail.
|
|
108
|
-
*
|
|
109
|
-
* @param {Buffer} buf
|
|
110
|
-
* @param {number} [size=300]
|
|
111
|
-
* @param {'png'|'jpeg'|'webp'} [format]
|
|
112
|
-
* @returns {Promise<Buffer>}
|
|
113
|
-
*/
|
|
114
54
|
async thumbnail(buf, size, format) {
|
|
115
55
|
return thumbnail(buf, size, format)
|
|
116
56
|
}
|
|
117
57
|
|
|
118
|
-
/**
|
|
119
|
-
* Get MP4 duration in seconds. Returns 0 on failure (silent default).
|
|
120
|
-
*
|
|
121
|
-
* @param {Buffer} buf
|
|
122
|
-
* @returns {number}
|
|
123
|
-
*/
|
|
124
58
|
duration(buf) {
|
|
125
59
|
return getMp4Duration(buf)
|
|
126
60
|
}
|
|
127
61
|
|
|
128
|
-
/**
|
|
129
|
-
* Extract a thumbnail frame from a video buffer.
|
|
130
|
-
*
|
|
131
|
-
* @param {Buffer} buf
|
|
132
|
-
* @param {object} [opts]
|
|
133
|
-
* @returns {Promise<Buffer|string>}
|
|
134
|
-
*/
|
|
135
62
|
async videoThumbnail(buf, opts) {
|
|
136
63
|
return extractThumbnail(buf, opts)
|
|
137
64
|
}
|
|
138
65
|
|
|
139
|
-
/**
|
|
140
|
-
* Upload a buffer to WhatsApp.
|
|
141
|
-
*
|
|
142
|
-
* @param {Buffer | { url: string }} source
|
|
143
|
-
* @param {'image'|'video'|'audio'|'document'} mediaType
|
|
144
|
-
* @param {object} [opts]
|
|
145
|
-
* @returns {Promise<string>}
|
|
146
|
-
*/
|
|
147
66
|
async upload(source, mediaType, opts) {
|
|
148
67
|
return uploadToWhatsApp(this.conn, source, mediaType, { jid: this.uploadJid, ...opts })
|
|
149
68
|
}
|
|
150
69
|
|
|
151
|
-
/**
|
|
152
|
-
* Resolve a media source (URL | base64 | Buffer | array) per strategy.
|
|
153
|
-
*
|
|
154
|
-
* @param {string | Buffer | Array} source
|
|
155
|
-
* @param {object} [opts]
|
|
156
|
-
* @returns {Promise<string | Buffer | Array<string | Buffer>>}
|
|
157
|
-
*/
|
|
158
70
|
async resolve(source, opts) {
|
|
159
71
|
return resolveMedia(this.conn, source, opts)
|
|
160
72
|
}
|
|
161
73
|
|
|
162
|
-
/**
|
|
163
|
-
* Convenience: sniff MIME from a buffer.
|
|
164
|
-
*
|
|
165
|
-
* @param {Buffer} buf
|
|
166
|
-
* @param {string} [filename]
|
|
167
|
-
* @returns {string}
|
|
168
|
-
*/
|
|
169
74
|
sniff(buf, filename) {
|
|
170
75
|
return sniffMime(buf, filename)
|
|
171
76
|
}
|
|
172
77
|
|
|
173
|
-
/**
|
|
174
|
-
* Convenience: derive WA media category from MIME.
|
|
175
|
-
*
|
|
176
|
-
* @param {string} mime
|
|
177
|
-
* @returns {'image'|'video'|'audio'|'document'}
|
|
178
|
-
*/
|
|
179
78
|
category(mime) {
|
|
180
79
|
return mimeToCategory(mime)
|
|
181
80
|
}
|