@kyyinfinite/lumina 1.0.0
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/LICENSE +21 -0
- package/README.md +629 -0
- package/examples/ai-rich.js +84 -0
- package/examples/button.js +57 -0
- package/examples/carousel.js +51 -0
- package/examples/interactive.js +102 -0
- package/examples/media.js +66 -0
- package/examples/simple-bot.js +56 -0
- package/package.json +86 -0
- package/src/builders/ai-rich.js +644 -0
- package/src/builders/base.js +109 -0
- package/src/builders/button-v2.js +159 -0
- package/src/builders/button.js +398 -0
- package/src/builders/card.js +168 -0
- package/src/builders/carousel.js +122 -0
- package/src/builders/index.d.ts +1 -0
- package/src/builders/index.js +13 -0
- package/src/client/bot.js +192 -0
- package/src/client/connection.js +180 -0
- package/src/errors.js +88 -0
- package/src/index.d.ts +458 -0
- package/src/index.js +152 -0
- package/src/media/fetch.js +67 -0
- package/src/media/image.js +86 -0
- package/src/media/index.d.ts +1 -0
- package/src/media/index.js +12 -0
- package/src/media/resolver.js +115 -0
- package/src/media/uploader.js +65 -0
- package/src/media/video.js +195 -0
- package/src/parsers/code-tokenizer-keywords.js +128 -0
- package/src/parsers/code-tokenizer.js +191 -0
- package/src/parsers/index.d.ts +1 -0
- package/src/parsers/index.js +11 -0
- package/src/parsers/inline-entity.js +231 -0
- package/src/parsers/table-metadata.js +69 -0
- package/src/proto/enums.js +170 -0
- package/src/proto/index.d.ts +1 -0
- package/src/proto/index.js +13 -0
- package/src/proto/layouts.js +89 -0
- package/src/proto/primitives.js +181 -0
- package/src/proto/relay-nodes.js +55 -0
- package/src/proto/rich-response.js +144 -0
- package/src/proto/updater.js +318 -0
- package/src/services/index.d.ts +1 -0
- package/src/services/index.js +10 -0
- package/src/services/media-service.js +184 -0
- package/src/services/message-service.js +288 -0
- package/src/services/proto-service.js +90 -0
- package/src/utils/id.js +42 -0
- package/src/utils/logger.js +65 -0
- package/src/utils/mime.js +104 -0
- package/src/utils/promise.js +52 -0
- package/src/utils/validator.js +129 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file layouts.js
|
|
3
|
+
* @module lumina/proto/layouts
|
|
4
|
+
*
|
|
5
|
+
* Layout view-model factories. Each layout wraps one or more UX primitives
|
|
6
|
+
* inside `view_model`, with the proper `GenAI{Kind}LayoutViewModel` typename.
|
|
7
|
+
*
|
|
8
|
+
* Replaces the implicit `Array.isArray(data) ? 'primitives' : 'primitive'`
|
|
9
|
+
* branching in the legacy `AIRich.newLayout` static method — explicit
|
|
10
|
+
* single/hscroll/action-row variants instead.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { LayoutKind, TYPENAME } from './enums.js'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Wrap a single primitive in a "Single" layout view-model.
|
|
17
|
+
*
|
|
18
|
+
* @param {object} primitive
|
|
19
|
+
* @param {object} [extra] Additional fields merged at top-level.
|
|
20
|
+
* @returns {object}
|
|
21
|
+
*/
|
|
22
|
+
export function singleLayout(primitive, extra = {}) {
|
|
23
|
+
return {
|
|
24
|
+
...extra,
|
|
25
|
+
view_model: {
|
|
26
|
+
primitive,
|
|
27
|
+
__typename: TYPENAME.layout(LayoutKind.SINGLE),
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Wrap multiple primitives in an "HScroll" (horizontal scroll) layout.
|
|
34
|
+
*
|
|
35
|
+
* @param {object[]} primitives
|
|
36
|
+
* @param {object} [extra]
|
|
37
|
+
* @returns {object}
|
|
38
|
+
*/
|
|
39
|
+
export function hscrollLayout(primitives, extra = {}) {
|
|
40
|
+
return {
|
|
41
|
+
...extra,
|
|
42
|
+
view_model: {
|
|
43
|
+
primitives,
|
|
44
|
+
__typename: TYPENAME.layout(LayoutKind.HSCROLL),
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Wrap multiple primitives in an "ActionRow" layout (used for follow-up
|
|
51
|
+
* suggestion pills that should appear as a horizontal row of buttons).
|
|
52
|
+
*
|
|
53
|
+
* @param {object[]} primitives
|
|
54
|
+
* @param {object} [extra]
|
|
55
|
+
* @returns {object}
|
|
56
|
+
*/
|
|
57
|
+
export function actionRowLayout(primitives, extra = {}) {
|
|
58
|
+
return {
|
|
59
|
+
...extra,
|
|
60
|
+
view_model: {
|
|
61
|
+
primitives,
|
|
62
|
+
__typename: TYPENAME.layout(LayoutKind.ACTION_ROW),
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Auto-pick helper. Picks `singleLayout` for a single primitive and
|
|
69
|
+
* `hscrollLayout` for arrays — useful for API surfaces that accept both.
|
|
70
|
+
*
|
|
71
|
+
* @param {LayoutKind} kind Forced kind; overrides auto-detection.
|
|
72
|
+
* @param {object|object[]} data
|
|
73
|
+
* @param {object} [extra]
|
|
74
|
+
* @returns {object}
|
|
75
|
+
*/
|
|
76
|
+
export function layoutFor(kind, data, extra = {}) {
|
|
77
|
+
switch (kind) {
|
|
78
|
+
case LayoutKind.SINGLE:
|
|
79
|
+
return singleLayout(Array.isArray(data) ? data[0] : data, extra)
|
|
80
|
+
case LayoutKind.HSCROLL:
|
|
81
|
+
return hscrollLayout(Array.isArray(data) ? data : [data], extra)
|
|
82
|
+
case LayoutKind.ACTION_ROW:
|
|
83
|
+
return actionRowLayout(Array.isArray(data) ? data : [data], extra)
|
|
84
|
+
default:
|
|
85
|
+
throw new TypeError(`Unknown layout kind: ${kind}`)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default { singleLayout, hscrollLayout, actionRowLayout, layoutFor }
|
|
@@ -0,0 +1,181 @@
|
|
|
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
|
+
*/
|
|
12
|
+
|
|
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
|
+
export function markdownTextPrimitive(text, inlineEntities) {
|
|
23
|
+
const primitive = { text, __typename: TYPENAME.MARKDOWN_TEXT }
|
|
24
|
+
if (inlineEntities?.length) primitive.inline_entities = inlineEntities
|
|
25
|
+
return primitive
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Code primitive.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} language
|
|
32
|
+
* @param {Array<{ content: string, type: string }>} unifiedBlocks
|
|
33
|
+
*/
|
|
34
|
+
export function codePrimitive(language, unifiedBlocks) {
|
|
35
|
+
return {
|
|
36
|
+
language,
|
|
37
|
+
code_blocks: unifiedBlocks,
|
|
38
|
+
__typename: TYPENAME.CODE,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Table primitive.
|
|
44
|
+
*
|
|
45
|
+
* @param {Array<object>} unifiedRows Output of `toTableMetadata().unifiedRows`.
|
|
46
|
+
*/
|
|
47
|
+
export function tablePrimitive(unifiedRows) {
|
|
48
|
+
return {
|
|
49
|
+
rows: unifiedRows,
|
|
50
|
+
__typename: TYPENAME.TABLE,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Search-result (sources) primitive.
|
|
56
|
+
*
|
|
57
|
+
* @param {Array<object>} sources Pre-shaped source objects.
|
|
58
|
+
*/
|
|
59
|
+
export function searchResultPrimitive(sources) {
|
|
60
|
+
return {
|
|
61
|
+
sources,
|
|
62
|
+
__typename: TYPENAME.SEARCH_RESULT,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Reel primitive (one per reel item).
|
|
68
|
+
*
|
|
69
|
+
* @param {object} item Pre-shaped reel descriptor.
|
|
70
|
+
*/
|
|
71
|
+
export function reelPrimitive(item) {
|
|
72
|
+
return { ...item, __typename: TYPENAME.REEL }
|
|
73
|
+
}
|
|
74
|
+
|
|
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
|
+
export function imaginePrimitive(media, kind, status = 'READY', thumbnailBase64) {
|
|
84
|
+
const primitive = {
|
|
85
|
+
media,
|
|
86
|
+
imagine_type: kind,
|
|
87
|
+
status: { status },
|
|
88
|
+
__typename: TYPENAME.IMAGINE,
|
|
89
|
+
}
|
|
90
|
+
if (thumbnailBase64) primitive.thumbnail = { raw_media: thumbnailBase64 }
|
|
91
|
+
return primitive
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Product-card primitive.
|
|
96
|
+
*
|
|
97
|
+
* @param {object} item Pre-shaped product descriptor (already media-resolved).
|
|
98
|
+
*/
|
|
99
|
+
export function productCardPrimitive(item) {
|
|
100
|
+
return { ...item, __typename: TYPENAME.PRODUCT_CARD }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Post primitive.
|
|
105
|
+
*
|
|
106
|
+
* @param {object} p Pre-shaped post descriptor (already media-resolved).
|
|
107
|
+
* @param {boolean} isCarousel
|
|
108
|
+
*/
|
|
109
|
+
export function postPrimitive(p, isCarousel) {
|
|
110
|
+
return { ...p, is_carousel: isCarousel, __typename: TYPENAME.POST }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Metadata-text primitive (used for footer / tip).
|
|
115
|
+
*
|
|
116
|
+
* @param {string} text
|
|
117
|
+
*/
|
|
118
|
+
export function metadataTextPrimitive(text) {
|
|
119
|
+
return { text, __typename: TYPENAME.METADATA_TEXT }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Follow-up suggestion pill primitive.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} text
|
|
126
|
+
*/
|
|
127
|
+
export function followUpSuggestionPillPrimitive(text) {
|
|
128
|
+
return {
|
|
129
|
+
prompt_text: text,
|
|
130
|
+
prompt_type: PromptType.SUGGESTED_PROMPT,
|
|
131
|
+
__typename: TYPENAME.FOLLOW_UP_PILL,
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Helper: shape a source entry for `searchResultPrimitive`.
|
|
137
|
+
*
|
|
138
|
+
* @param {{ iconUrl?: string, url?: string, text?: string, subtitle?: string }} src
|
|
139
|
+
*/
|
|
140
|
+
export function shapeSourceEntry({ iconUrl = '', url = '', text = '', subtitle = 'AI' } = {}) {
|
|
141
|
+
return {
|
|
142
|
+
source_type: SourceType.THIRD_PARTY,
|
|
143
|
+
source_display_name: text,
|
|
144
|
+
source_subtitle: subtitle,
|
|
145
|
+
source_url: url,
|
|
146
|
+
favicon: { url: iconUrl, mime_type: 'image/jpeg', width: 16, height: 16 },
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Helper: shape a single reel item for `reelPrimitive`.
|
|
152
|
+
*/
|
|
153
|
+
export function shapeReelEntry(item) {
|
|
154
|
+
return {
|
|
155
|
+
reels_url: item.videoUrl ?? item.url ?? '',
|
|
156
|
+
thumbnail_url: item.thumbnail ?? '',
|
|
157
|
+
creator: item.username ?? item.title ?? '',
|
|
158
|
+
avatar_url: item.avatar ?? '',
|
|
159
|
+
reels_title: item.reelsTitle ?? item.title ?? '',
|
|
160
|
+
likes_count: item.likes ?? item.like ?? 0,
|
|
161
|
+
shares_count: item.shares ?? item.share ?? 0,
|
|
162
|
+
view_count: item.views ?? item.view ?? 0,
|
|
163
|
+
reel_source: item.source ?? 'IG',
|
|
164
|
+
is_verified: !!(item.isVerified ?? item.verified),
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export default {
|
|
169
|
+
markdownTextPrimitive,
|
|
170
|
+
codePrimitive,
|
|
171
|
+
tablePrimitive,
|
|
172
|
+
searchResultPrimitive,
|
|
173
|
+
reelPrimitive,
|
|
174
|
+
imaginePrimitive,
|
|
175
|
+
productCardPrimitive,
|
|
176
|
+
postPrimitive,
|
|
177
|
+
metadataTextPrimitive,
|
|
178
|
+
followUpSuggestionPillPrimitive,
|
|
179
|
+
shapeSourceEntry,
|
|
180
|
+
shapeReelEntry,
|
|
181
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
import { NativeFlow } from './enums.js'
|
|
13
|
+
|
|
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
|
+
export function createInteractiveNodes(opts = {}) {
|
|
25
|
+
const outerVersion = opts.outerVersion ?? NativeFlow.OUTER_VERSION
|
|
26
|
+
const flowVersion = opts.flowVersion ?? NativeFlow.FLOW_VERSION
|
|
27
|
+
const flowName = opts.flowName ?? NativeFlow.FLOW_NAME_MIXED
|
|
28
|
+
const includeBiz = opts.includeBiz ?? true
|
|
29
|
+
|
|
30
|
+
const interactive = {
|
|
31
|
+
tag: NativeFlow.INTERACTIVE_TAG,
|
|
32
|
+
attrs: { type: NativeFlow.TYPE, v: outerVersion },
|
|
33
|
+
content: [{ tag: NativeFlow.TYPE, attrs: { v: flowVersion, name: flowName } }],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!includeBiz) return [interactive]
|
|
37
|
+
|
|
38
|
+
return [
|
|
39
|
+
{
|
|
40
|
+
tag: NativeFlow.BIZ_TAG,
|
|
41
|
+
attrs: {},
|
|
42
|
+
content: [interactive],
|
|
43
|
+
},
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Convenience: returns the same block but with `name: 'native_flow'` only
|
|
49
|
+
* (no biz wrapper). Useful for non-business accounts.
|
|
50
|
+
*/
|
|
51
|
+
export function createBareInteractiveNodes(opts = {}) {
|
|
52
|
+
return createInteractiveNodes({ ...opts, includeBiz: false })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default { createInteractiveNodes, createBareInteractiveNodes }
|
|
@@ -0,0 +1,144 @@
|
|
|
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
|
+
import crypto from 'node:crypto'
|
|
18
|
+
|
|
19
|
+
import { BOT_JID, ForwardOrigin } from './enums.js'
|
|
20
|
+
|
|
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
|
+
export async function assembleRichResponse({
|
|
45
|
+
title,
|
|
46
|
+
sections,
|
|
47
|
+
submessages = [],
|
|
48
|
+
richResponseSources = [],
|
|
49
|
+
contextInfo = {},
|
|
50
|
+
footer,
|
|
51
|
+
forwarded = true,
|
|
52
|
+
notification = false,
|
|
53
|
+
includesUnifiedResponse = true,
|
|
54
|
+
includesSubmessages = true,
|
|
55
|
+
quoted,
|
|
56
|
+
quotedParticipant,
|
|
57
|
+
extraPayload = {},
|
|
58
|
+
} = {}) {
|
|
59
|
+
const forwardMeta = forwarded
|
|
60
|
+
? {
|
|
61
|
+
forwardingScore: 1,
|
|
62
|
+
isForwarded: true,
|
|
63
|
+
forwardedAiBotMessageInfo: { botJid: BOT_JID },
|
|
64
|
+
forwardOrigin: ForwardOrigin.BOT,
|
|
65
|
+
}
|
|
66
|
+
: {}
|
|
67
|
+
|
|
68
|
+
const notificationMeta = notification
|
|
69
|
+
? {
|
|
70
|
+
sessionTransparencyMetadata: {
|
|
71
|
+
// User-supplied disclaimer (no personal leftovers).
|
|
72
|
+
disclaimerText: typeof notification === 'object' ? notification.disclaimerText ?? '' : '',
|
|
73
|
+
hcaId: typeof notification === 'object' && notification.hcaId
|
|
74
|
+
? notification.hcaId
|
|
75
|
+
: `hca_${crypto.randomUUID()}`,
|
|
76
|
+
sessionTransparencyType: notification.sessionTransparencyType ?? 1,
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
: {}
|
|
80
|
+
|
|
81
|
+
const quotedMeta = quoted
|
|
82
|
+
? {
|
|
83
|
+
stanzaId: quoted?.key?.id ?? quoted?.id,
|
|
84
|
+
participant: quotedParticipant ?? quoted?.key?.participant ?? quoted?.key?.remoteJid,
|
|
85
|
+
quotedType: 0,
|
|
86
|
+
quotedMessage:
|
|
87
|
+
typeof quoted === 'object' && quoted !== null ? quoted.message ?? quoted : undefined,
|
|
88
|
+
}
|
|
89
|
+
: {}
|
|
90
|
+
|
|
91
|
+
const finalSections = footer
|
|
92
|
+
? [
|
|
93
|
+
...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
|
+
{
|
|
99
|
+
view_model: {
|
|
100
|
+
primitive: { text: footer, __typename: 'GenAIMetadataTextPrimitive' },
|
|
101
|
+
__typename: 'GenAISingleLayoutViewModel',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
]
|
|
105
|
+
: sections
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
messageContextInfo: {
|
|
109
|
+
deviceListMetadata: {},
|
|
110
|
+
deviceListMetadataVersion: 2,
|
|
111
|
+
botMetadata: {
|
|
112
|
+
messageDisclaimerText: title,
|
|
113
|
+
richResponseSourcesMetadata: { sources: richResponseSources },
|
|
114
|
+
...notificationMeta,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
...extraPayload,
|
|
118
|
+
botForwardedMessage: {
|
|
119
|
+
message: {
|
|
120
|
+
richResponseMessage: {
|
|
121
|
+
messageType: 1,
|
|
122
|
+
submessages: includesSubmessages ? submessages : [],
|
|
123
|
+
unifiedResponse: {
|
|
124
|
+
data: includesUnifiedResponse
|
|
125
|
+
? Buffer.from(
|
|
126
|
+
JSON.stringify({
|
|
127
|
+
response_id: crypto.randomUUID(),
|
|
128
|
+
sections: finalSections,
|
|
129
|
+
}),
|
|
130
|
+
).toString('base64')
|
|
131
|
+
: '',
|
|
132
|
+
},
|
|
133
|
+
contextInfo: {
|
|
134
|
+
...forwardMeta,
|
|
135
|
+
...quotedMeta,
|
|
136
|
+
...contextInfo,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default assembleRichResponse
|