@zervonex/baileys 0.0.0-stage → 3.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/LICENSE +21 -0
- package/README.md +3206 -3
- package/WAProto/WAProtoCompile.js +291 -0
- package/WAProto/fetcher.js +69 -0
- package/WAProto/index.js +88 -0
- package/WAProto/parser.js +313 -0
- package/engine-requirements.js +10 -0
- package/lib/Builders/AIRich.js +643 -0
- package/lib/Builders/base.js +130 -0
- package/lib/Builders/builders.js +449 -0
- package/lib/Builders/index.js +3 -0
- package/lib/Defaults/index.js +225 -0
- package/lib/Defaults/phonenumber-mcc.json +223 -0
- package/lib/Signal/index.js +2 -0
- package/lib/Signal/libsignal.js +523 -0
- package/lib/Signal/lid-mapping.js +283 -0
- package/lib/Socket/Client/index.js +2 -0
- package/lib/Socket/Client/types.js +10 -0
- package/lib/Socket/Client/websocket.js +84 -0
- package/lib/Socket/business.js +152 -0
- package/lib/Socket/chats.js +855 -0
- package/lib/Socket/communities.js +147 -0
- package/lib/Socket/groups.js +294 -0
- package/lib/Socket/index.js +15 -0
- package/lib/Socket/messages-recv.js +981 -0
- package/lib/Socket/messages-send.js +708 -0
- package/lib/Socket/mex.js +34 -0
- package/lib/Socket/newsletter.js +781 -0
- package/lib/Socket/registration.js +155 -0
- package/lib/Socket/socket.js +797 -0
- package/lib/Socket/spam-report.js +65 -0
- package/lib/Socket/username.js +82 -0
- package/lib/Socket/zervonex-handler.js +55 -0
- package/lib/Store/index.js +6 -0
- package/lib/Store/make-cache-manager-store.js +81 -0
- package/lib/Store/make-in-memory-store.js +425 -0
- package/lib/Store/make-ordered-dictionary.js +82 -0
- package/lib/Store/object-repository.js +31 -0
- package/lib/Store/reporting-info-store.js +38 -0
- package/lib/Types/Auth.js +2 -0
- package/lib/Types/Bussines.js +2 -0
- package/lib/Types/Call.js +2 -0
- package/lib/Types/Chat.js +8 -0
- package/lib/Types/Contact.js +2 -0
- package/lib/Types/Events.js +2 -0
- package/lib/Types/GroupMetadata.js +2 -0
- package/lib/Types/Label.js +25 -0
- package/lib/Types/LabelAssociation.js +7 -0
- package/lib/Types/Message.js +15 -0
- package/lib/Types/Newsletter.js +100 -0
- package/lib/Types/Product.js +2 -0
- package/lib/Types/Signal.js +2 -0
- package/lib/Types/Socket.js +14 -0
- package/lib/Types/State.js +26 -0
- package/lib/Types/USync.js +2 -0
- package/lib/Types/index.js +30 -0
- package/lib/Utils/auth-utils.js +142 -0
- package/lib/Utils/baileys-event-stream.js +33 -0
- package/lib/Utils/bot-capabilities.js +40 -0
- package/lib/Utils/bot-planning-replay.js +44 -0
- package/lib/Utils/browser-utils.js +26 -0
- package/lib/Utils/business.js +75 -0
- package/lib/Utils/chat-utils.js +317 -0
- package/lib/Utils/companion-reg-client-utils.js +20 -0
- package/lib/Utils/consumer-application.js +38 -0
- package/lib/Utils/crypto.js +52 -0
- package/lib/Utils/decode-wa-message.js +197 -0
- package/lib/Utils/event-buffer.js +273 -0
- package/lib/Utils/extras.js +186 -0
- package/lib/Utils/generics.js +195 -0
- package/lib/Utils/history.js +51 -0
- package/lib/Utils/identity-change-handler.js +19 -0
- package/lib/Utils/index.js +38 -0
- package/lib/Utils/link-preview.js +51 -0
- package/lib/Utils/logger.js +2 -0
- package/lib/Utils/lt-hash.js +3 -0
- package/lib/Utils/make-mutex.js +16 -0
- package/lib/Utils/message-composer.js +59 -0
- package/lib/Utils/message-retry-manager.js +48 -0
- package/lib/Utils/messages-media.js +520 -0
- package/lib/Utils/messages.js +752 -0
- package/lib/Utils/meta-ai-msmsg.js +122 -0
- package/lib/Utils/meta-compositing.js +75 -0
- package/lib/Utils/modern-messages.js +235 -0
- package/lib/Utils/native-flow.js +55 -0
- package/lib/Utils/noise-handler.js +161 -0
- package/lib/Utils/pre-key-manager.js +76 -0
- package/lib/Utils/process-message.js +399 -0
- package/lib/Utils/reporting-utils.js +171 -0
- package/lib/Utils/signal.js +167 -0
- package/lib/Utils/spam-report-utils.js +135 -0
- package/lib/Utils/sync-action-utils.js +33 -0
- package/lib/Utils/tc-token-utils.js +154 -0
- package/lib/Utils/use-multi-file-auth-state.js +307 -0
- package/lib/Utils/validate-connection.js +181 -0
- package/lib/WABinary/constants.js +1321 -0
- package/lib/WABinary/decode.js +152 -0
- package/lib/WABinary/encode.js +140 -0
- package/lib/WABinary/generic-utils.js +179 -0
- package/lib/WABinary/index.js +6 -0
- package/lib/WABinary/jid-utils.js +235 -0
- package/lib/WABinary/types.js +4 -0
- package/lib/WAM/BinaryInfo.js +10 -0
- package/lib/WAM/constants.js +22853 -0
- package/lib/WAM/encode.js +150 -0
- package/lib/WAM/index.js +3 -0
- package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +30 -0
- package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +48 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +23 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +28 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +15 -0
- package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +24 -0
- package/lib/WAUSync/Protocols/USyncLIDProtocol.js +9 -0
- package/lib/WAUSync/Protocols/USyncPictureProtocol.js +21 -0
- package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +19 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +19 -0
- package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +22 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +16 -0
- package/lib/WAUSync/Protocols/index.js +12 -0
- package/lib/WAUSync/USyncQuery.js +100 -0
- package/lib/WAUSync/USyncUser.js +20 -0
- package/lib/WAUSync/index.js +3 -0
- package/lib/index.js +67 -0
- package/package.json +220 -5
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import { proto } from '../../WAProto/index.js'
|
|
3
|
+
import {
|
|
4
|
+
generateMessageIDV2, generateVerificationMetadata, waitAllPromises, extractIE, tokenizeCode, generateWAMessageFromContent, generateWAMessage,
|
|
5
|
+
makeWhatsAppFlowButton, metaTyping, sendMetaComposited, PlanningStepStatus, buildSteps, replayPlanning, replayPlanningOnly, buildReasoningSteps, buildSearchSteps,
|
|
6
|
+
mixedSteps, buildBotRichResponse, BotCapabilityType, generateTableContent, generateListContent, generateCodeBlockContent,
|
|
7
|
+
generateLatexContent, captureUnifiedResponse, generateUnifiedResponseContent
|
|
8
|
+
} from '../Utils/index.js'
|
|
9
|
+
import { isJidGroup, isJidMetaAI, jidNormalizedUser } from '../WABinary/index.js'
|
|
10
|
+
import { BOT_RENDERING_CONFIG_METADATA } from '../Defaults/index.js'
|
|
11
|
+
import { randomBytes } from 'crypto'
|
|
12
|
+
|
|
13
|
+
const DEFAULT_BOT_JID = '867051314767696@bot'
|
|
14
|
+
const META_AI_BOT_JID = '867051314767696@bot'
|
|
15
|
+
const STATUS_JID = 'status@broadcast'
|
|
16
|
+
const BIZ_BOT_SUPPORT = '{"version":1,"is_ai_message":true,"should_upload_client_logs":false,"should_show_system_message":false,"ticket_id":"7004947587700716","citation_items":[],"ticket_locale":"us"}'
|
|
17
|
+
|
|
18
|
+
const mkSection = vm => ({ __typename: 'GenAIUnifiedResponseSection', view_model: vm })
|
|
19
|
+
const single = p => mkSection({ __typename: 'GenAISingleLayoutViewModel', primitive: p })
|
|
20
|
+
const vstack = ps => mkSection({ __typename: 'GenAIVStackLayoutViewModel', primitives: ps })
|
|
21
|
+
const hscroll = ps => mkSection({ __typename: 'GenAIHScrollLayoutViewModel', primitives: ps })
|
|
22
|
+
const grid = ps => mkSection({ __typename: 'GenAIGridLayoutViewModel', primitives: ps })
|
|
23
|
+
const actionRow = ps => ({ view_model: { __typename: 'GenAIActionRowLayoutViewModel', primitives: ps } })
|
|
24
|
+
const addonAction = (type, ps, alignment = 'END') => mkSection({ __typename: 'GenAIAddonActionLayoutViewModel', addon_action_type: type, addon_action_alignment: alignment, primitives: ps })
|
|
25
|
+
|
|
26
|
+
const mkMarkdown = (text, entities = []) => { const { text: t, inline_entities } = extractIE(text, entities); return { __typename: 'GenAIMarkdownTextUXPrimitive', text: t, inline_entities } }
|
|
27
|
+
const mkCode = (content, language = '', unified_codeBlock = []) => ({ __typename: 'GenAICodeUXPrimitive', language, code_blocks: unified_codeBlock.length ? unified_codeBlock : [{ content }] })
|
|
28
|
+
|
|
29
|
+
const MAX_TABLE_ROWS = 5000, MAX_TABLE_COLUMNS = 200, MAX_TABLE_CELLS = 100_000
|
|
30
|
+
const mkTable = (rows, title = '') => {
|
|
31
|
+
if (!Array.isArray(rows) || rows.length === 0) throw new TypeError('mkTable requires a non-empty rows array')
|
|
32
|
+
if (rows.length > MAX_TABLE_ROWS) throw new RangeError(`mkTable: too many rows (${rows.length} > ${MAX_TABLE_ROWS})`)
|
|
33
|
+
const firstRow = Array.isArray(rows[0]) ? rows[0] : (rows[0].cells ?? rows[0].items ?? [])
|
|
34
|
+
if (firstRow.length > MAX_TABLE_COLUMNS) throw new RangeError(`mkTable: too many columns (${firstRow.length} > ${MAX_TABLE_COLUMNS})`)
|
|
35
|
+
if (rows.length * firstRow.length > MAX_TABLE_CELLS) throw new RangeError(`mkTable: too many cells (${rows.length * firstRow.length} > ${MAX_TABLE_CELLS})`)
|
|
36
|
+
const normalized = Array.isArray(rows[0])
|
|
37
|
+
? rows.map((r, i) => ({ is_header: i === 0, cells: r.map(c => (typeof c === 'string' ? c : String(c))), markdown_cells: r.map(c => ({ text: typeof c === 'string' ? c : String(c) })) }))
|
|
38
|
+
: rows.map(r => ({ is_header: !!r.is_header, cells: (r.cells ?? r.items ?? []).map(c => (typeof c === 'string' ? c : String(c))), markdown_cells: (r.cells ?? r.items ?? []).map(c => ({ text: typeof c === 'string' ? c : String(c) })) }))
|
|
39
|
+
return { __typename: 'GenATableUXPrimitive', title, rows: normalized }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const mkImage = (input, expiration) => {
|
|
43
|
+
const img = typeof input === 'string' ? { url: input } : input
|
|
44
|
+
const exp = expiration ?? String(Date.now() + 30 * 24 * 60 * 60 * 1000)
|
|
45
|
+
const first = (...vals) => vals.find(v => v != null && String(v).length > 0)
|
|
46
|
+
const previewUrl = first(img.previewUrl, img.preview_url, img.imagePreviewUrl, img.url) ?? ''
|
|
47
|
+
const fullUrl = first(img.highResUrl, img.high_res_url, img.imageHighResUrl, img.fullUrl, img.full_url, img.url, previewUrl)
|
|
48
|
+
const darkPreview = first(img.darkModePreviewUrl, img.dark_mode_preview_url, previewUrl)
|
|
49
|
+
const darkFull = first(img.darkModeHighResUrl, img.dark_mode_high_res_url, darkPreview)
|
|
50
|
+
const mime = img.mimeType ?? img.mime_type ?? (/\.png$/i.test(img.url ?? '') ? 'image/png' : 'image/jpeg')
|
|
51
|
+
const w = Number(img.width ?? 600), h = Number(img.height ?? 400)
|
|
52
|
+
const media = (url, mw, mh) => ({ url, url_fallback: url, mime_type: mime, expiration_timestamp_ms: exp, width: mw, height: mh })
|
|
53
|
+
return { __typename: 'GenAIImagePrimitive', preview_image: media(previewUrl, w, h), full_image: media(fullUrl, w, h), dark_mode_preview_image: media(darkPreview, w, h), dark_mode_full_image: media(darkFull, w, h), asset_query_status: 'FETCHED' }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const mkVideo = url => ({ __typename: 'GenAIVideoPrimitive', media: { __typename: 'GenAIMediaItem', mime_type: 'video/mp4', url } })
|
|
57
|
+
const mkReel = ({ creator, avatar_url, thumbnail_url, reels_url, title, likes_count, shares_count, view_count, reel_source, is_verified }) => ({ __typename: 'GenAIReelPrimitive', creator: creator ?? '', avatar_url: avatar_url ?? '', thumbnail_url: thumbnail_url ?? '', reels_url: reels_url ?? '', reels_title: title ?? '', likes_count: likes_count ?? 0, shares_count: shares_count ?? 0, view_count: view_count ?? 0, reel_source: reel_source ?? 'IG', is_verified: !!is_verified })
|
|
58
|
+
const mkPost = d => ({ __typename: 'GenAIPostPrimitive', title: d.title ?? '', username: d.username ?? '', subtitle: d.subtitle ?? '', thumbnail_url: d.thumbnail_url ?? '', post_url: d.post_url ?? d.url ?? '', post_caption: d.post_caption ?? d.caption ?? '', post_type: d.post_type ?? 'photo', source_app: d.source_app ?? 'instagram', likes_count: d.likes_count ?? 0, comments_count: d.comments_count ?? 0, shares_count: d.shares_count ?? 0, is_verified: d.is_verified ?? false, is_carousel: d.is_carousel ?? false, orientation: d.orientation ?? 'portrait', profile_picture_url: d.profile_picture_url ?? null, footer_label: d.footer_label ?? null, footer_icon: d.footer_icon ?? null, additional_images: d.additional_images ?? [] })
|
|
59
|
+
const mkProduct = d => ({ __typename: 'GenAIProductItemCardPrimitive', title: d.title ?? '', brand: d.brand ?? '', price: d.price ?? '', sale_price: d.sale_price ?? null, product_url: d.product_url ?? d.url ?? '', image: { url: d.image_url ?? d.image ?? '' }, additional_images: [] })
|
|
60
|
+
const mkSearchResult = d => ({ __typename: 'GenAISearchResultPrimitive', source_url: d.url ?? d.source_url ?? '', source_display_name: d.title ?? d.source_display_name ?? '', source_type: d.source_type ?? 'web', source_subtitle: d.subtitle ?? d.source_subtitle ?? '', favicon: d.favicon ?? null })
|
|
61
|
+
const mkMap = ({ latitude, longitude, name = '', address = '' }) => ({ __typename: 'GenAIMapPrimitive', latitude, longitude, name, address })
|
|
62
|
+
|
|
63
|
+
const mkRichMap = ({ centerLatitude, centerLongitude, latitudeDelta, longitudeDelta, annotations = [], showInfoList = false }) => {
|
|
64
|
+
if (centerLatitude == null || centerLongitude == null) throw new TypeError('addRichMap requires centerLatitude and centerLongitude')
|
|
65
|
+
const submessage = { messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_MAP, mapMetadata: { centerLatitude, centerLongitude, ...(latitudeDelta != null ? { latitudeDelta } : {}), ...(longitudeDelta != null ? { longitudeDelta } : {}), showInfoList: !!showInfoList, annotations: annotations.map((a, i) => ({ annotationNumber: a.annotationNumber ?? i + 1, latitude: a.latitude, longitude: a.longitude, title: a.title ?? '', body: a.body ?? '' })) } }
|
|
66
|
+
const displayLat = annotations[0]?.latitude ?? centerLatitude, displayLng = annotations[0]?.longitude ?? centerLongitude
|
|
67
|
+
return { submessage, section: single({ __typename: 'GenAIMapPrimitive', latitude: displayLat, longitude: displayLng, name: annotations[0]?.title ?? '', address: annotations[0]?.body ?? '' }) }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const mkRichMenu = ({ header = {}, body = {}, footer = {} }) => {
|
|
71
|
+
const sections = []
|
|
72
|
+
if (header?.title) sections.push(single({ __typename: 'FOATextPrimitive', text: '# ' + header.title }))
|
|
73
|
+
if (header?.image?.url) sections.push(single(header.image.inline ? mkInlineImage(header.image.url) : mkImage(header.image)))
|
|
74
|
+
if (header?.disclaimer) sections.push(single(mkMarkdown(header.disclaimerText || ' ')))
|
|
75
|
+
const cards = Array.isArray(body?.cards) ? body.cards : []
|
|
76
|
+
const buttons = Array.isArray(body?.buttons) ? body.buttons : []
|
|
77
|
+
if (cards.length) sections.push({ view_model: { primitives: cards.map(card => mkWidget(Array.isArray(card?.buttons) ? card.buttons : [], card?.title || '')), __typename: body.carousel ? 'GenAIHScrollLayoutViewModel' : 'GenAIActionRowLayoutViewModel' } })
|
|
78
|
+
else if (buttons.length) sections.push(single(mkWidget(buttons, body.title || '')))
|
|
79
|
+
if (footer?.text || footer?.url) sections.push({ view_model: { primitives: [mkFooterAction({ text: footer.text, url: footer.url })], __typename: 'GenAIActionRowLayoutViewModel' } })
|
|
80
|
+
return sections
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const mkDynamic = ({ url, type = 'GIF', version, loopCount } = {}) => {
|
|
84
|
+
if (!url) throw new TypeError('addDynamic requires a url')
|
|
85
|
+
const dynType = String(type).toUpperCase() === 'IMAGE' ? proto.AIRichResponseDynamicMetadata.AIRichResponseDynamicMetadataType.AI_RICH_RESPONSE_DYNAMIC_METADATA_TYPE_IMAGE : proto.AIRichResponseDynamicMetadata.AIRichResponseDynamicMetadataType.AI_RICH_RESPONSE_DYNAMIC_METADATA_TYPE_GIF
|
|
86
|
+
const submessage = { messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_DYNAMIC, dynamicMetadata: { type: dynType, url, ...(version != null ? { version } : {}), ...(loopCount != null ? { loopCount } : {}) } }
|
|
87
|
+
const mimeType = dynType === proto.AIRichResponseDynamicMetadata.AIRichResponseDynamicMetadataType.AI_RICH_RESPONSE_DYNAMIC_METADATA_TYPE_IMAGE ? 'image/jpeg' : 'image/gif'
|
|
88
|
+
return { submessage, section: single({ __typename: 'GenAIDynamicMediaPrimitive', url, mime_type: mimeType, ...(loopCount != null ? { loop_count: loopCount } : {}) }) }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const mkWidget = (ctas, title = '') => ({ __typename: 'GenAI3PExtWidgetPrimitive', header: { __typename: 'GenAI3PExtWidgetStandardHeader', title }, body: { __typename: 'GenAI3PExtCalendarEventList', ctas: ctas.map(c => ({ __typename: 'GenAI3PExtWidgetCTA', label: c.label, state: 'PENDING', kind: c.kind ?? 'OTHER', tool_call_id: c.tool_call_id ?? c.id ?? crypto.randomBytes(8).toString('hex'), toast: { __typename: 'GenAI3PExtWidgetToast', label: c.toast ?? '' } })), sections: [] } })
|
|
92
|
+
const mkFooterAction = d => ({ __typename: 'GenAIFooterActionPrimitive', cta_text: d.cta_text ?? d.text ?? '', cta_type: d.cta_type ?? 'OPEN_URL', cta_url: d.cta_url ?? d.url ?? '' })
|
|
93
|
+
|
|
94
|
+
const mkSocialProfile = ({ username = '', platform = 'GENERIC', title = '', subtitle = '', imageUrl = '', entityId, entityUrl, entityType, fullName = '', isVerified = false, resultText = 'See results' }) => {
|
|
95
|
+
if (!username || typeof username !== 'string') throw new TypeError('addSocialProfile requires a username')
|
|
96
|
+
const normalizedPlatform = String(platform).toUpperCase()
|
|
97
|
+
const profileUrl = entityUrl ?? (username ? `https://www.${platform.toLowerCase()}.com/${encodeURIComponent(username)}` : '')
|
|
98
|
+
const resolvedType = entityType ?? (normalizedPlatform === 'INSTAGRAM' ? 'IG_PROFILE' : 'SOCIAL_PROFILE')
|
|
99
|
+
const image = imageUrl ? { url: imageUrl, mime_type: 'image/png' } : undefined
|
|
100
|
+
return [
|
|
101
|
+
{ view_model: { primitives: [{ __typename: 'GenAICompactEntityPrimitive', title, subtitle, ...(image ? { image } : {}), entity_id: String(entityId ?? username ?? ''), entity_url: profileUrl, entity_type: 'WEBSITE', action_type: 'OPEN_URL', is_verified: !!isVerified }], __typename: 'GenAIActionRowLayoutViewModel' } },
|
|
102
|
+
{ view_model: { primitives: [{ type: 'HORIZONTAL_LINE', __typename: 'GenAIDividerPrimitive' }], __typename: 'GenAIVStackLayoutViewModel' } },
|
|
103
|
+
{ view_model: { primitives: [{ __typename: 'GenAISpacerPrimitive' }, { __typename: 'GenAIMarkdownTextUXPrimitive', text: `# {{social_entity_1}}${resultText}\0{{/social_entity_1}} `, inline_entities: [{ key: 'social_entity_1', metadata: { __typename: 'GenAISocialEntityItem', entity_id: String(entityId ?? username ?? ''), entity_name: username, entity_full_name: fullName, platform: normalizedPlatform, entity_picture_url: imageUrl, entity_url: profileUrl, entity_type: resolvedType, is_verified: !!isVerified } }] }, { __typename: 'GenAISpacerPrimitive' }], __typename: 'GenAIActionRowLayoutViewModel' } }
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const mkInlineImage = (url, alignment = 'center') => ({ __typename: 'GenAIInlineImageUXPrimitive', image: { __typename: 'GenAIMediaItem', mime_type: 'image/jpeg', url }, alignment })
|
|
108
|
+
const mkInlineLink = (links = []) => { let text = ''; const entities = []; links.forEach((link, i) => { const key = `INLINE_HYPERLINK_${i}`; const display = link.text || link.displayName || link.url || ''; text += `{{${key}}}${display}{{/${key}}}`; entities.push({ key, metadata: { display_name: display, is_trusted: link.isTrusted !== false, url: link.url || '', __typename: 'GenAIInlineLinkItem' } }) }); return { __typename: 'GenAIMarkdownTextUXPrimitive', text, inline_entities: entities } }
|
|
109
|
+
const mkMeta = text => ({ __typename: 'GenAIMetadataTextPrimitive', text })
|
|
110
|
+
const mkFOA = text => ({ __typename: 'FOATextPrimitive', text })
|
|
111
|
+
const mkTip = text => ({ __typename: 'GenAITipUXPrimitive', text })
|
|
112
|
+
const mkDivider = (type = 'HORIZONTAL_LINE') => ({ __typename: 'GenAIDividerPrimitive', divider_type: type })
|
|
113
|
+
const mkSpacer = (spacing = 2) => ({ __typename: 'GenAISpacerPrimitive', spacing })
|
|
114
|
+
const mkThinking = (title, icon = 'THINKING') => ({ __typename: 'GenAIBotThinkingStatusPrimitive', title, icon })
|
|
115
|
+
const mkProgress = (title, inProgress = true) => ({ __typename: 'GenAIBotProgressStatusPrimitive', title, is_in_progress: inProgress, icon: null, meta_search_apps: null, target_secondary_screen_id: null, target_secondary_screen_tab_id: null })
|
|
116
|
+
const mkTask = ({ taskId, title, subtitle = '', status = 'PENDING' }) => ({ __typename: 'GenAITaskPrimitive', task_id: taskId, title, subtitle, status })
|
|
117
|
+
const mkLatex = ({ expression, imageUrl, width = 400, height = 200, fontHeight = 83.333, padding = 15 }) => ({ __typename: 'GenAITextInlineEntity', key: `latex_${crypto.randomUUID().slice(0, 8)}`, metadata: { __typename: 'GenAILatexItem', latex_expression: expression, font_height: fontHeight, padding, latex_image: { __typename: 'GenAIMediaItem', mime_type: 'image/png', url: imageUrl, url_fallback: imageUrl, width, height, expiration_timestamp_ms: Date.now() + 86400000 } } })
|
|
118
|
+
const mkLabel = (text, variant = 'POSITIVE', backgroundColor = '#FF0000FF', textColor = '#000000FF') => ({ __typename: 'GenAILabelPrimitive', text: String(text), variant, background_color_override: backgroundColor, text_color_override: textColor })
|
|
119
|
+
const mkPill = prompt_text => ({ __typename: 'GenAIFollowUpSuggestionPillPrimitive', prompt_text })
|
|
120
|
+
const mkHtml = (html, trustedSources = []) => ({ __typename: 'FOAHtmlPrimitiveDemoDONOTUSE', trusted_sources: trustedSources, payload: String(html).trim() })
|
|
121
|
+
const mkRichHtml = (html, url = '', trustedSources = []) => ({ __typename: 'GenAIaeacdsnwHtmlPrimitive', payload: String(html).trim(), url, trusted_sources: trustedSources })
|
|
122
|
+
|
|
123
|
+
// validates proofs exist before attaching — never fabricate provenance
|
|
124
|
+
export const hasVerifiableProofs = v => Array.isArray(v?.proofs) && v.proofs.length > 0
|
|
125
|
+
|
|
126
|
+
export const wrapToBotForwardedMessage = (richResponseMessage, verificationMetadata) => ({
|
|
127
|
+
messageContextInfo: { botMetadata: { pluginMetadata: {}, ...(hasVerifiableProofs(verificationMetadata) ? { verificationMetadata } : {}), botRenderingConfigMetadata: BOT_RENDERING_CONFIG_METADATA } },
|
|
128
|
+
botForwardedMessage: { message: { richResponseMessage } }
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
export const attachBotVerificationProofs = (envelope, verificationMetadata) => {
|
|
132
|
+
if (!envelope || !hasVerifiableProofs(verificationMetadata)) return envelope
|
|
133
|
+
const botMetadata = envelope?.messageContextInfo?.botMetadata
|
|
134
|
+
if (!botMetadata || botMetadata.verificationMetadata) return envelope
|
|
135
|
+
botMetadata.verificationMetadata = verificationMetadata
|
|
136
|
+
return envelope
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// only ships previewMedia when all required crypto fields are present — drops incomplete descriptors
|
|
140
|
+
const normalizePreviewMedia = media => {
|
|
141
|
+
if (!media || typeof media !== 'object') return null
|
|
142
|
+
const { fileSha256, mediaKey, fileEncSha256, directPath, mimetype, mediaKeyTimestamp } = media
|
|
143
|
+
if (!fileSha256 || !mediaKey || !fileEncSha256 || !directPath) return null
|
|
144
|
+
return { fileSha256, mediaKey, fileEncSha256, directPath, mediaKeyTimestamp: mediaKeyTimestamp ?? Math.floor(Date.now() / 1000), mimetype: mimetype || 'image/jpeg' }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const buildAdditionalBotMetadata = (submessages) => {
|
|
148
|
+
const sources = [], mediaDetailsMetadataList = []
|
|
149
|
+
for (const sub of submessages) {
|
|
150
|
+
if (sub?.contentItemsMetadata?.itemsMetadata) {
|
|
151
|
+
for (const item of sub.contentItemsMetadata.itemsMetadata) {
|
|
152
|
+
const r = item?.reelItem ?? item ?? {}
|
|
153
|
+
sources.push({ provider: 0, thumbnailCdnUrl: r.thumbnailUrl ?? r.thumbnail_url ?? '', sourceProviderUrl: r.videoUrl ?? r.reels_url ?? r.url ?? '', sourceQuery: '', faviconCdnUrl: r.profileIconUrl ?? r.avatar_url ?? '', citationNumber: sources.length + 1, sourceTitle: r.title ?? r.reels_title ?? r.creator ?? '' })
|
|
154
|
+
const previewMedia = normalizePreviewMedia(r.previewMedia)
|
|
155
|
+
if (previewMedia) mediaDetailsMetadataList.push({ id: r.mediaId ?? r.previewMedia?.mediaId ?? crypto.randomBytes(32).toString('hex'), previewMedia })
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (sub?.latexMetadata?.expressions) {
|
|
159
|
+
for (const expr of sub.latexMetadata.expressions) {
|
|
160
|
+
const previewMedia = normalizePreviewMedia(expr?.previewMedia)
|
|
161
|
+
if (previewMedia) mediaDetailsMetadataList.push({ id: expr.mediaId ?? expr.previewMedia?.mediaId ?? crypto.randomBytes(32).toString('hex'), previewMedia })
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { sources, mediaDetailsMetadataList }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const buildImaginePrimitive = ({ mediaType = 'video', url = '', thumbnail, mimeType, fileLength = 0, duration = 0, status = 'GENERATING', estimatedMs, imagineType } = {}) => {
|
|
169
|
+
const type = String(mediaType).toLowerCase() === 'image' ? 'IMAGE' : 'ANIMATE'
|
|
170
|
+
const prim = { __typename: 'GenAIImaginePrimitive', media: { url: url ?? '', mime_type: mimeType ?? (type === 'IMAGE' ? 'image/jpeg' : 'video/mp4'), file_length: fileLength ?? 0, duration: duration ?? 0 }, imagine_type: imagineType ?? type, status: { status: String(status).toUpperCase() } }
|
|
171
|
+
if (estimatedMs != null) prim.status.estimated_completion_time = Math.floor((Date.now() + Number(estimatedMs)) / 1000)
|
|
172
|
+
if (thumbnail) prim.thumbnail = { raw_media: thumbnail }
|
|
173
|
+
return prim
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export const prepareRichGenerationMessage = ({ text = '', mediaType = 'video', url = '', thumbnail, mimeType, fileLength = 0, duration = 0, status = 'GENERATING', estimatedMs, itemId = `imagine_${crypto.randomUUID()}`, responseId = crypto.randomUUID(), verificationMetadata } = {}) => {
|
|
177
|
+
const sections = []
|
|
178
|
+
if (text) sections.push({ view_model: { primitive: { text: String(text), inline_entities: [], __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } })
|
|
179
|
+
sections.push({ view_model: { primitive: buildImaginePrimitive({ mediaType, url, thumbnail, mimeType, fileLength, duration, status, estimatedMs }), __typename: 'GenAISingleLayoutViewModel', id: itemId } })
|
|
180
|
+
const submessages = text ? [{ messageType: 2, messageText: String(text) }] : []
|
|
181
|
+
const richResponseMessage = proto.AIRichResponseMessage.create({ submessages, messageType: 1, unifiedResponse: { data: Buffer.from(JSON.stringify({ response_id: responseId, sections })) }, contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: DEFAULT_BOT_JID }, forwardOrigin: 4 } })
|
|
182
|
+
const message = wrapToBotForwardedMessage(richResponseMessage, verificationMetadata)
|
|
183
|
+
message.messageContextInfo.botMetadata.botResponseId = responseId
|
|
184
|
+
return { message, responseId, itemId }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export class AIRich {
|
|
188
|
+
#client; #nodes = []; #idIndex = {}; #title = ''; #footer = ''; #botJid = DEFAULT_BOT_JID
|
|
189
|
+
#responseId = null; #dynamic = true; #signedVerificationMetadata = null
|
|
190
|
+
#contextInfoExtra = {}; #embeddedScreens = []; #suggestedPrompts = null
|
|
191
|
+
#botSources = null; #pttTranscript = null
|
|
192
|
+
|
|
193
|
+
constructor(client, opts = {}) {
|
|
194
|
+
if (!client) throw new Error('Socket client is required')
|
|
195
|
+
this.#client = client
|
|
196
|
+
if (opts.botJid) this.#botJid = opts.botJid
|
|
197
|
+
if (opts.dynamic !== undefined) this.#dynamic = opts.dynamic
|
|
198
|
+
if (opts.responseId) { this.#responseId = opts.responseId; this.#dynamic = false }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#push(section, submessage = null, id = null) { const nodeId = id ?? `node_${this.#nodes.length}`; this.#nodes.push({ id: nodeId, section, submessage }); this.#idIndex[nodeId] = this.#nodes.length - 1; return this }
|
|
202
|
+
#replace(id, section, submessage = null) { const idx = this.#idIndex[id]; if (idx === undefined) throw new Error(`Node id "${id}" not found`); this.#nodes[idx] = { id, section, submessage }; return this }
|
|
203
|
+
#insertAt(pos, section, submessage = null, id = null) { const nodeId = id ?? `node_${this.#nodes.length}`; this.#nodes.splice(pos, 0, { id: nodeId, section, submessage }); this.#idIndex = {}; this.#nodes.forEach((n, i) => { this.#idIndex[n.id] = i }); return this }
|
|
204
|
+
#add(section, submessage, opts = {}) { if (opts.replace) return this.#replace(opts.replace, section, submessage); if (opts.insertAt !== undefined) return this.#insertAt(opts.insertAt, section, submessage, opts.id); return this.#push(section, submessage, opts.id) }
|
|
205
|
+
|
|
206
|
+
async #uploadMedia(input, mediaType = 'image') { if (typeof input === 'string' && !input.startsWith('http')) { const { readFileSync } = await import('fs'); input = readFileSync(input) } if (Buffer.isBuffer(input) || input instanceof Uint8Array) { const uploaded = await this.#client.waUploadToServer({ [mediaType]: input }, { mediaType, upload: this.#client.waUploadToServer }); return uploaded.url } return typeof input === 'string' ? input : input }
|
|
207
|
+
|
|
208
|
+
setTitle(title) { this.#title = title; return this }
|
|
209
|
+
setFooter(footer) { this.#footer = footer; return this }
|
|
210
|
+
setBotJid(jid) { this.#botJid = jid; return this }
|
|
211
|
+
setResponseId(id) { this.#responseId = id; this.#dynamic = false; return this }
|
|
212
|
+
setContextInfo(ci) { this.#contextInfoExtra = ci; return this }
|
|
213
|
+
|
|
214
|
+
addText(text, opts = {}) { const { text: t, inline_entities } = extractIE(text); return this.#add(single(mkMarkdown(t, inline_entities)), null, opts) }
|
|
215
|
+
addCode(language, content, opts = {}) { const { codeBlocks, unified_codeBlock } = tokenizeCode(content, language); const submessage = { messageType: 5, codeMetadata: { codeLanguage: language, codeBlocks } }; return this.#add(single(mkCode(content, language, unified_codeBlock)), submessage, opts) }
|
|
216
|
+
addTable(rows, title = '', opts = {}) { return this.#add(single(mkTable(rows, title)), null, opts) }
|
|
217
|
+
addImage(input, opts = {}) { const resolved = this.#uploadMedia(input, 'image').then(url => single(mkImage(url))); return this.#add(resolved, null, opts) }
|
|
218
|
+
addVideo(url, opts = {}) { const resolved = this.#uploadMedia(url, 'video').then(u => single(mkVideo(u))); return this.#add(resolved, null, opts) }
|
|
219
|
+
addGrid(items, opts = {}) { if (!Array.isArray(items) || items.length < 2) throw new Error('addGrid requires at least 2 items'); return this.#add(grid(items.map(i => mkImage(i))), null, opts) }
|
|
220
|
+
|
|
221
|
+
addVideoGrid(videos, opts = {}) {
|
|
222
|
+
if (!Array.isArray(videos) || videos.length < 2) throw new Error('addVideoGrid requires at least 2 videos')
|
|
223
|
+
const prims = videos.map(v => { const url = typeof v === 'string' ? v : (v?.url ?? v?.videoUrl ?? ''); const item = typeof v === 'string' ? {} : v; return { __typename: 'GenAIVideoPrimitive', reels_url: url, reels_title: item.title, thumbnail_url: item.thumbnailUrl ?? item.thumbnail, creator: item.creator, avatar_url: item.avatarUrl, likes_count: item.likes, comments_count: item.comments, shares_count: item.shares, is_verified: item.isVerified, video_delivery_response: { progressive_urls: (item.progressiveUrls ?? []).map(u => ({ progressive_url: u, __typename: 'GenAIProgressiveUrlResponse' })), dash_manifests: (item.dashManifests ?? []).map(m => ({ manifest_xml: m, __typename: 'GenAIDashManifestResponse' })), __typename: 'GenAIVideoDeliveryResponse' } } })
|
|
224
|
+
return this.#add(grid(prims), null, opts)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
addReels(reels, opts = {}) { const prims = reels.map(mkReel); return this.#add(prims.length === 1 ? single(prims[0]) : hscroll(prims), null, opts) }
|
|
228
|
+
addImagine(input = {}, opts = {}) { return this.#add(single(buildImaginePrimitive(input)), null, opts) }
|
|
229
|
+
addInlineImage(url, alignment = 'center', opts = {}) { return this.#add(single(mkInlineImage(url, alignment)), null, opts) }
|
|
230
|
+
addHtml(html, trustedSources = [], opts = {}) { return this.#add(single(mkHtml(html, trustedSources)), null, opts) }
|
|
231
|
+
|
|
232
|
+
addRichHtml(tabs, title = 'Preview', opts = {}) {
|
|
233
|
+
const tabItems = typeof tabs === 'string' ? [{ title: 'HTML', html: tabs }] : Array.isArray(tabs) ? tabs : [tabs]
|
|
234
|
+
const embeddedTabs = tabItems.map((tab, i) => ({ id: tab.id ?? `tab_${i}`, tab_header: tab.title ?? tab.tab_header ?? `HTML ${i + 1}`, sections: [{ __typename: 'GenAIUnifiedResponseSection', view_model: { __typename: 'GenAISingleLayoutViewModel', primitive: mkRichHtml(tab.html ?? '', tab.url ?? '', tab.trustedSources ?? []) } }], step_entries: [] }))
|
|
235
|
+
this.#embeddedScreens.push({ title, content: [{ __typename: 'FOAIDNixelButtonSheets', tabs: embeddedTabs }] })
|
|
236
|
+
return this.#add(single(mkMarkdown(`> ${title}`)), null, opts)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
addPost(data, opts = {}) { return this.#add(single(mkPost(data)), null, opts) }
|
|
240
|
+
addProduct(data, opts = {}) { return this.#add(single(mkProduct(data)), null, opts) }
|
|
241
|
+
addSource(sources, opts = {}) { const prims = sources.map(mkSearchResult); return this.#add(prims.length === 1 ? single(prims[0]) : vstack(prims), null, opts) }
|
|
242
|
+
addMap(locations, opts = {}) { const prims = locations.map(mkMap); return this.#add(prims.length === 1 ? single(prims[0]) : vstack(prims), null, opts) }
|
|
243
|
+
addWidget(ctas, title = '', opts = {}) { return this.#add(single(mkWidget(ctas, title)), null, opts) }
|
|
244
|
+
addFooterAction(data, opts = {}) { return this.#add(single(mkFooterAction(data)), null, opts) }
|
|
245
|
+
addSocialProfile(data, opts = {}) { const sections = mkSocialProfile(data); for (const s of sections) this.#push(s, null, opts.id ? `${opts.id}_${this.#nodes.length}` : null); return this }
|
|
246
|
+
addInstagramProfile(data, opts = {}) { return this.addSocialProfile({ platform: 'INSTAGRAM', entityType: 'IG_PROFILE', entityUrl: data.entityUrl ?? (data.username ? `https://www.instagram.com/${encodeURIComponent(data.username)}` : ''), ...data }, opts) }
|
|
247
|
+
addCopyAction(text, label, alignment = 'END', opts = {}) { return this.#add(addonAction('COPY_TO_CLIPBOARD', [mkMarkdown(String(label ?? text))], alignment), null, opts) }
|
|
248
|
+
addAddonAction(type, primitives, alignment = 'END', opts = {}) { return this.#add(addonAction(type, primitives, alignment), null, opts) }
|
|
249
|
+
addMetadata(text, opts = {}) { return this.#add(single(mkMeta(text)), null, opts) }
|
|
250
|
+
addFOAText(text, opts = {}) { return this.#add(single(mkFOA(text)), null, opts) }
|
|
251
|
+
addTip(text, opts = {}) { return this.#add(single(mkTip(text)), null, opts) }
|
|
252
|
+
addDivider(type = 'HORIZONTAL_LINE', opts = {}) { return this.#add(single(mkDivider(type)), null, opts) }
|
|
253
|
+
addSpacer(spacing = 2, opts = {}) { return this.#add(single(mkSpacer(spacing)), null, opts) }
|
|
254
|
+
addThinkingStatus(title, icon = 'THINKING', opts = {}) { return this.#add(single(mkThinking(title, icon)), null, opts) }
|
|
255
|
+
addProgressStatus(title, inProgress = true, opts = {}) { return this.#add(single(mkProgress(title, inProgress)), null, opts) }
|
|
256
|
+
addTask(data, opts = {}) { return this.#add(single(mkTask(data)), null, opts) }
|
|
257
|
+
addLatex(data, opts = {}) { const entity = mkLatex(data); const prim = mkMarkdown(`{{${entity.key}}}.{{/${entity.key}}}`, [entity]); return this.#add(single(prim), null, opts) }
|
|
258
|
+
addSuggest(prompts, scroll = false, opts = {}) { const prims = prompts.map(p => mkPill(typeof p === 'string' ? p : (p.text ?? p))); return this.#add(scroll ? hscroll(prims) : actionRow(prims), null, opts) }
|
|
259
|
+
addRichMap(mapOpts, opts = {}) { const { submessage, section } = mkRichMap(mapOpts); return this.#add(section, submessage, opts) }
|
|
260
|
+
addRichMenu(menuOpts, opts = {}) { const sections = mkRichMenu(menuOpts); sections.forEach(s => this.#push(s, null, null)); return this }
|
|
261
|
+
addDynamic(dynOpts, opts = {}) { const { submessage, section } = mkDynamic(dynOpts); return this.#add(section, submessage, opts) }
|
|
262
|
+
addLabel(text, variant = 'POSITIVE', opts = {}) { return this.#add(single(mkLabel(text, opts.backgroundColor, opts.textColor)), null, opts) }
|
|
263
|
+
addInlineLinks(links, opts = {}) { return this.#add(single(mkInlineLink(links)), null, opts) }
|
|
264
|
+
addSection(section, opts = {}) { return this.#add(section, null, opts) }
|
|
265
|
+
addEmbeddedScreen(screen) { this.#embeddedScreens.push(screen); return this }
|
|
266
|
+
// push raw sections + submessages directly — escape hatch for new primitives not yet wrapped
|
|
267
|
+
addRawSections(sections, submessages = []) { sections.forEach((s, i) => this.#push(s, submessages[i] ?? null)); return this }
|
|
268
|
+
|
|
269
|
+
addSuggestPrompts(suggestions, opts = {}) {
|
|
270
|
+
if (!Array.isArray(suggestions) || !suggestions.length) throw new Error('addSuggestPrompts requires at least one suggestion')
|
|
271
|
+
this.#suggestedPrompts = { suggestedPrompts: suggestions.map(String), ...(opts.selectedPromptIndex != null ? { selectedPromptIndex: opts.selectedPromptIndex } : {}), ...(opts.selectedPromptId != null ? { selectedPromptId: String(opts.selectedPromptId) } : {}) }
|
|
272
|
+
return this.#add(actionRow(suggestions.map(s => mkPill(s))), null, opts)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
addSources(sources, opts = {}) {
|
|
276
|
+
if (!Array.isArray(sources) || !sources.length) throw new Error('addSources requires at least one source')
|
|
277
|
+
this.#botSources = sources.map((s, i) => ({ provider: s.provider ?? 0, thumbnailCdnUrl: s.thumbnailCdnUrl ?? s.thumbnail ?? '', sourceProviderUrl: s.sourceProviderUrl ?? s.url ?? '', sourceQuery: s.sourceQuery ?? s.query ?? '', faviconCdnUrl: s.faviconCdnUrl ?? s.favicon ?? '', citationNumber: s.citationNumber ?? i + 1, sourceTitle: s.sourceTitle ?? s.title ?? '' }))
|
|
278
|
+
const prims = sources.map(mkSearchResult)
|
|
279
|
+
return this.#add(prims.length === 1 ? single(prims[0]) : vstack(prims), null, opts)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
addVerticalAlbum(items, opts = {}) {
|
|
283
|
+
if (!Array.isArray(items) || items.length < 2) throw new Error('addVerticalAlbum requires at least 2 items')
|
|
284
|
+
const expiration = String(Date.now() + 30 * 24 * 60 * 60 * 1000)
|
|
285
|
+
const primitives = items.map(item => {
|
|
286
|
+
const kind = item?.type || (item?.video ? 'video' : 'image')
|
|
287
|
+
if (kind === 'video') {
|
|
288
|
+
const v = item.video || item
|
|
289
|
+
return buildImaginePrimitive({ mediaType: 'video', url: typeof v === 'string' ? v : (v?.url || ''), mimeType: v?.mimeType || 'video/mp4', fileLength: v?.fileLength || 0, duration: v?.duration || 0, status: 'READY' })
|
|
290
|
+
}
|
|
291
|
+
const img = typeof (item?.image || item) === 'string' ? { url: item?.image || item } : (item?.image || item || {})
|
|
292
|
+
const url = img.url || ''
|
|
293
|
+
if (!url) throw new Error('Each vertical album image item requires a URL')
|
|
294
|
+
return { __typename: 'GenAIImagePrimitive', preview_image: { url, url_fallback: url, mime_type: img.mimeType || 'image/jpeg', expiration_timestamp_ms: expiration, width: img.width || 600, height: img.height || 400 }, full_image: { url, url_fallback: url, mime_type: img.mimeType || 'image/jpeg', expiration_timestamp_ms: expiration, width: img.width || 600, height: img.height || 400 }, dark_mode_preview_image: { url, url_fallback: url, mime_type: img.mimeType || 'image/jpeg', expiration_timestamp_ms: expiration, width: img.width || 600, height: img.height || 400 }, dark_mode_full_image: { url, url_fallback: url, mime_type: img.mimeType || 'image/jpeg', expiration_timestamp_ms: expiration, width: img.width || 600, height: img.height || 400 }, asset_query_status: 'FETCHED', display_mode: 'FULL_WIDTH', full_width: true }
|
|
295
|
+
})
|
|
296
|
+
return this.#add({ view_model: { primitives, display_mode: 'FULL_WIDTH', full_width: true, __typename: 'GenAIVStackLayoutViewModel' } }, null, opts)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
addPttTranscript(transcript, opts = {}) {
|
|
300
|
+
if (!transcript) throw new Error('addPttTranscript requires a transcript string')
|
|
301
|
+
this.#pttTranscript = String(transcript)
|
|
302
|
+
return this.#add(single(mkMeta(`🎤 _${transcript}_`)), null, opts)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
loadFrom(msg) {
|
|
306
|
+
const rich = msg?.botForwardedMessage?.message?.richResponseMessage ?? msg?.richResponseMessage
|
|
307
|
+
if (!rich) throw new Error('Not an AIRich message')
|
|
308
|
+
const mci = msg?.messageContextInfo
|
|
309
|
+
if (mci?.botMetadata?.messageDisclaimerText) this.#title = mci.botMetadata.messageDisclaimerText
|
|
310
|
+
if (mci?.botMetadata?.verificationMetadata) this.#signedVerificationMetadata = mci.botMetadata.verificationMetadata
|
|
311
|
+
if (rich.unifiedResponse?.data) {
|
|
312
|
+
try {
|
|
313
|
+
const parsed = JSON.parse(Buffer.isBuffer(rich.unifiedResponse.data) ? rich.unifiedResponse.data.toString() : Buffer.from(rich.unifiedResponse.data, 'base64').toString())
|
|
314
|
+
; (parsed.sections ?? []).forEach((s, i) => { const sub = rich.submessages?.[i] ?? null; this.#nodes.push({ id: `loaded_${i}`, section: s, submessage: sub }); this.#idIndex[`loaded_${i}`] = i })
|
|
315
|
+
} catch { }
|
|
316
|
+
}
|
|
317
|
+
return this
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async build(jid, opts = {}) {
|
|
321
|
+
const { botJid = this.#botJid, messageId = generateMessageIDV2(), quoted, quotedParticipant } = opts
|
|
322
|
+
const resolved = await waitAllPromises(this.#nodes)
|
|
323
|
+
const sections = resolved.map(n => n.section)
|
|
324
|
+
const submessages = resolved.map(n => n.submessage).filter(Boolean)
|
|
325
|
+
const embeddedScreens = await waitAllPromises(this.#embeddedScreens)
|
|
326
|
+
const responseId = this.#dynamic ? crypto.randomUUID() : (this.#responseId ?? crypto.randomUUID())
|
|
327
|
+
const unifiedData = JSON.stringify({ response_id: responseId, sections, ...(embeddedScreens.length ? { embedded_screens: embeddedScreens } : {}) })
|
|
328
|
+
const contextInfo = { isForwarded: true, forwardOrigin: 4, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid }, participant: '13135550002@s.whatsapp.net', remoteJid: 'status@broadcast', quotedMessage: { protocolMessage: { type: 25 } }, ...this.#contextInfoExtra, ...(quoted ? { quotedMessage: quoted.message, stanzaId: quoted.key?.id, participant: quotedParticipant ?? quoted.key?.participant ?? quoted.key?.remoteJid, remoteJid: quoted.key?.remoteJid } : {}) }
|
|
329
|
+
const verification = this.#signedVerificationMetadata ?? generateVerificationMetadata()
|
|
330
|
+
const { sources, mediaDetailsMetadataList } = buildAdditionalBotMetadata(submessages)
|
|
331
|
+
const richResponseMessage = proto.AIRichResponseMessage.create({ messageType: 1, submessages, unifiedResponse: { data: Buffer.from(unifiedData) }, originalRecipientMetadata: { data: Buffer.from(unifiedData) }, contextInfo })
|
|
332
|
+
const message = wrapToBotForwardedMessage(richResponseMessage, verification)
|
|
333
|
+
const botMeta = message.messageContextInfo.botMetadata
|
|
334
|
+
botMeta.botResponseId = responseId
|
|
335
|
+
botMeta.deviceListMetadata = {}
|
|
336
|
+
botMeta.deviceListMetadataVersion = 2
|
|
337
|
+
botMeta.supportPayload = BIZ_BOT_SUPPORT
|
|
338
|
+
if (this.#title) botMeta.messageDisclaimerText = this.#title
|
|
339
|
+
if (sources.length || this.#botSources?.length) botMeta.richResponseSourcesMetadata = { sources: this.#botSources ?? sources }
|
|
340
|
+
if (mediaDetailsMetadataList.length) botMeta.unifiedResponseMutation = { mediaDetailsMetadataList }
|
|
341
|
+
if (this.#suggestedPrompts) botMeta.suggestedPromptMetadata = this.#suggestedPrompts
|
|
342
|
+
if (this.#pttTranscript) botMeta.pttPromptMetadata = { transcript: this.#pttTranscript }
|
|
343
|
+
botMeta.capabilities = { richResponseUnifiedResponse: true, richResponseEmbeddedScreens: true, richResponseInlineLinksEnabled: true, richResponseUrBloksEnabled: true, richResponseUrImagine: true, richResponseUrReasoning: true }
|
|
344
|
+
return generateWAMessageFromContent(jid, message, { userJid: this.#client.user?.id, messageId })
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async send(jid, opts = {}) {
|
|
348
|
+
const { additionalNodes = [], ...buildOpts } = opts
|
|
349
|
+
const msg = await this.build(jid, buildOpts)
|
|
350
|
+
const relayFn = this.#client.relayRichMessage ?? this.#client.relayMessage
|
|
351
|
+
await relayFn(jid, msg, {
|
|
352
|
+
...opts,
|
|
353
|
+
additionalNodes: [
|
|
354
|
+
{ tag: 'bot', attrs: { biz_bot: '1' }, content: undefined },
|
|
355
|
+
{ tag: 'biz', attrs: {}, content: [{ tag: 'interactive', attrs: { type: 'native_flow', v: '1' }, content: [{ tag: 'native_flow', attrs: { v: '9', name: 'mixed' } }] }] },
|
|
356
|
+
...(opts.additionalNodes ?? [])
|
|
357
|
+
]
|
|
358
|
+
})
|
|
359
|
+
return msg
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async sendEdit(jid, targetId, opts = {}) {
|
|
363
|
+
if (!targetId) throw new Error('targetId is required for sendEdit')
|
|
364
|
+
const msg = await this.build(jid, opts)
|
|
365
|
+
const editMsg = { protocolMessage: { key: { remoteJid: jid, fromMe: true, id: targetId }, type: 14, editedMessage: msg.message } }
|
|
366
|
+
const waMsg = await generateWAMessageFromContent(jid, editMsg, { userJid: this.#client.user?.id })
|
|
367
|
+
await this.#client.relayMessage(jid, waMsg.message, { messageId: waMsg.key.id })
|
|
368
|
+
return waMsg
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export function aiRichFromObject(client, jid, data, opts = {}) {
|
|
373
|
+
const r = new AIRich(client, { botJid: data.botJid })
|
|
374
|
+
if (data.title) r.setTitle(data.title)
|
|
375
|
+
if (data.parts) {
|
|
376
|
+
for (const p of data.parts) {
|
|
377
|
+
if (p.type === 'text') r.addText(p.content ?? p.text)
|
|
378
|
+
if (p.type === 'code') r.addCode(p.language ?? '', p.content ?? p.code)
|
|
379
|
+
if (p.type === 'table') r.addTable(p.table, p.title)
|
|
380
|
+
if (p.type === 'image') r.addImage(p.url ?? p.image)
|
|
381
|
+
if (p.type === 'grid') r.addGrid(p.items ?? p.images)
|
|
382
|
+
if (p.type === 'videoGrid') r.addVideoGrid(p.items ?? p.videos)
|
|
383
|
+
if (p.type === 'imagine') r.addImagine(p)
|
|
384
|
+
if (p.type === 'sources') r.addSource(p.sources)
|
|
385
|
+
if (p.type === 'divider') r.addDivider()
|
|
386
|
+
if (p.type === 'spacer') r.addSpacer(p.spacing)
|
|
387
|
+
if (p.type === 'tip') r.addTip(p.content ?? p.text)
|
|
388
|
+
if (p.type === 'meta') r.addMetadata(p.content ?? p.text)
|
|
389
|
+
if (p.type === 'foa') r.addFOAText(p.content ?? p.text)
|
|
390
|
+
if (p.type === 'html') r.addHtml(p.html, p.trustedSources)
|
|
391
|
+
if (p.type === 'richHtml') r.addRichHtml(p.tabs ?? p.html, p.title)
|
|
392
|
+
if (p.type === 'copy') r.addCopyAction(p.text, p.label, p.alignment)
|
|
393
|
+
if (p.type === 'social') r.addSocialProfile(p)
|
|
394
|
+
if (p.type === 'imagine') r.addImagine(p)
|
|
395
|
+
if (p.type === 'rawSections') r.addRawSections(p.sections, p.submessages)
|
|
396
|
+
}
|
|
397
|
+
return r.send(jid, opts)
|
|
398
|
+
}
|
|
399
|
+
if (data.texts) data.texts.forEach(t => r.addText(t))
|
|
400
|
+
else if (data.text) r.addText(data.text)
|
|
401
|
+
if (data.codes) data.codes.forEach(c => r.addCode(c.language ?? data.language ?? '', c.code ?? c.content))
|
|
402
|
+
else if (data.code) { const c = typeof data.code === 'string' ? data.code : (data.code.code ?? data.code.content); const lang = typeof data.code === 'string' ? (data.language ?? '') : (data.code.language ?? data.language ?? ''); r.addCode(lang, c) }
|
|
403
|
+
if (data.table) r.addTable(data.table, data.tableTitle)
|
|
404
|
+
else if (data.headers && data.rows) r.addTable([data.headers, ...data.rows], data.tableTitle)
|
|
405
|
+
if (data.images) data.images.forEach(img => r.addImage(img.url ?? img))
|
|
406
|
+
if (data.image) r.addImage(data.image.url ?? data.image)
|
|
407
|
+
if (data.grid) r.addGrid(data.grid)
|
|
408
|
+
if (data.videoGrid) r.addVideoGrid(data.videoGrid)
|
|
409
|
+
if (data.reels) r.addReels(data.reels)
|
|
410
|
+
if (data.imagine) r.addImagine(typeof data.imagine === 'object' ? data.imagine : { url: data.imagine })
|
|
411
|
+
if (data.sources) r.addSource(data.sources)
|
|
412
|
+
if (data.richMapSources) r.addSources(data.richMapSources)
|
|
413
|
+
else if (data.botSources) r.addSources(data.botSources)
|
|
414
|
+
if (data.richMap) r.addRichMap(data.richMap)
|
|
415
|
+
if (data.dynamic) r.addDynamic(data.dynamic)
|
|
416
|
+
if (data.suggestPrompts) r.addSuggestPrompts(Array.isArray(data.suggestPrompts) ? data.suggestPrompts : data.suggestPrompts.suggestions, data.suggestPrompts)
|
|
417
|
+
if (data.pttTranscript) r.addPttTranscript(data.pttTranscript)
|
|
418
|
+
if (data.instagramProfile) r.addInstagramProfile(data.instagramProfile)
|
|
419
|
+
if (data.latex) { if (data.latexText) r.addText(data.latexText); (Array.isArray(data.latex) ? data.latex : [data.latex]).forEach(l => r.addLatex({ expression: l.expression ?? l.latexExpression ?? '', imageUrl: l.url ?? l.imageUrl ?? '', width: l.width ?? 400, height: l.height ?? 200 })) }
|
|
420
|
+
if (data.html) r.addHtml(data.html, data.trustedSources)
|
|
421
|
+
if (data.richHtml) r.addRichHtml(data.richHtml, data.richHtmlTitle)
|
|
422
|
+
if (data.copyText) r.addCopyAction(data.copyText, data.copyLabel, data.copyAlignment)
|
|
423
|
+
if (data.social) r.addSocialProfile(data.social)
|
|
424
|
+
if (data.suggest) r.addSuggest(data.suggest)
|
|
425
|
+
if (data.tip) r.addTip(data.tip)
|
|
426
|
+
if (data.metadata ?? data.meta) r.addMetadata(data.metadata ?? data.meta)
|
|
427
|
+
if (data.footer) r.addFOAText(data.footer)
|
|
428
|
+
if (data.rawSections) r.addRawSections(data.rawSections, data.rawSubmessages)
|
|
429
|
+
return r.send(jid, opts)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const stripDevice = jid => String(jid || '').replace(/:\d+$/, '')
|
|
433
|
+
|
|
434
|
+
const collectMetaAIBotJids = async (sock, jid, botUser) => {
|
|
435
|
+
const candidates = new Set([botUser])
|
|
436
|
+
if (!isJidGroup(jid)) return candidates
|
|
437
|
+
const botNumber = jidNormalizedUser(botUser).split('@')[0]
|
|
438
|
+
try {
|
|
439
|
+
const meta = await sock.groupMetadata(jid)
|
|
440
|
+
for (const p of meta.participants || []) {
|
|
441
|
+
const idNum = jidNormalizedUser(p.id).split('@')[0]
|
|
442
|
+
const pnNum = p.phoneNumber ? jidNormalizedUser(p.phoneNumber).split('@')[0] : undefined
|
|
443
|
+
if (pnNum === botNumber || idNum === botNumber || isJidMetaAI(p.id)) candidates.add(p.id)
|
|
444
|
+
}
|
|
445
|
+
} catch { }
|
|
446
|
+
return candidates
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const isMetaAIResponse = (msg, chatJid, promptId, botJids) => {
|
|
450
|
+
if (!msg?.key || msg.key.fromMe || msg.key.remoteJid !== chatJid) return false
|
|
451
|
+
const sender = stripDevice(msg.key.participant || msg.key.remoteJid)
|
|
452
|
+
if (isJidMetaAI(sender)) return true
|
|
453
|
+
for (const b of botJids) if (jidNormalizedUser(stripDevice(b)) === jidNormalizedUser(sender)) return true
|
|
454
|
+
if (promptId) { const inner = msg.message ? Object.values(msg.message)[0] : undefined; const stanzaId = inner?.contextInfo?.stanzaId || msg.message?.contextInfo?.stanzaId; if (stanzaId === promptId) return true }
|
|
455
|
+
return false
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const q = quoted => quoted ? { quoted } : {}
|
|
459
|
+
|
|
460
|
+
// ─── attachAIRich ─────────────────────────────────────────────────────────────
|
|
461
|
+
// Attaches all AIRich, meta, bot, and relay methods to result, then returns the
|
|
462
|
+
// fuzzy-proxy-wrapped result. This is the only export zervonex-handler.js needs.
|
|
463
|
+
export function attachAIRich(result, config) {
|
|
464
|
+
const sock = result
|
|
465
|
+
|
|
466
|
+
const relayRichMessage = async (jid, fullMsg, options = {}) => {
|
|
467
|
+
const relayOpts = { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, statusJidList: options.statusJidList, additionalNodes: options.additionalNodes, ...(options.relayOptions || {}) }
|
|
468
|
+
await sock.relayMessage(jid, fullMsg.message, relayOpts)
|
|
469
|
+
const hasRich = !!fullMsg.message?.botForwardedMessage?.message?.richResponseMessage || !!fullMsg.message?.interactiveMessage?.bloksWidget
|
|
470
|
+
const shouldEdit = options.bypassDownload !== false && (options.forceRichEdit || hasRich)
|
|
471
|
+
if (shouldEdit) {
|
|
472
|
+
try {
|
|
473
|
+
const editContent = proto.Message.fromObject({ botForwardedMessage: { message: { protocolMessage: { key: { remoteJid: jid, fromMe: true, id: fullMsg.key.id }, type: proto.Message.ProtocolMessage.Type.MESSAGE_EDIT, editedMessage: fullMsg.message } } } })
|
|
474
|
+
const editMsg = generateWAMessageFromContent(jid, editContent, { userJid: sock.user?.id, messageId: generateMessageIDV2(sock.user?.id || jid) })
|
|
475
|
+
await sock.relayMessage(jid, editMsg.message, { ...relayOpts, messageId: editMsg.key.id })
|
|
476
|
+
} catch { /* card already arrived, just behind download prompt */ }
|
|
477
|
+
}
|
|
478
|
+
return fullMsg
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
result.relayRichMessage = relayRichMessage
|
|
482
|
+
result.airich = opts => new AIRich(result, opts)
|
|
483
|
+
|
|
484
|
+
result.captureAiRich = msg => {
|
|
485
|
+
const rich = msg?.botForwardedMessage?.message?.richResponseMessage ?? msg?.richResponseMessage
|
|
486
|
+
if (!rich?.unifiedResponse?.data) return null
|
|
487
|
+
return { submessages: rich.submessages ?? [], sections: JSON.parse(Buffer.from(rich.unifiedResponse.data, 'base64').toString()), contextInfo: rich.contextInfo ?? {}, messageType: rich.messageType ?? 1 }
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
result.relayAiRich = (jid, captured, opts = {}) => {
|
|
491
|
+
const r = new AIRich(result)
|
|
492
|
+
if (captured.sections?.response_id) r.setResponseId(captured.sections.response_id)
|
|
493
|
+
return r.loadFrom({ botForwardedMessage: { message: { richResponseMessage: { submessages: captured.submessages, unifiedResponse: { data: Buffer.from(JSON.stringify(captured.sections)) }, contextInfo: captured.contextInfo, messageType: captured.messageType } } } }).send(jid, opts)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
result.sendRichFromObject = (jid, data, opts = {}) => aiRichFromObject(result, jid, data, opts)
|
|
497
|
+
result.sendRichMessage = (jid, data, quoted, opts = {}) => result.sendMessage(jid, { aiRich: data }, { ...q(quoted), ...opts })
|
|
498
|
+
result.sendCodeBlock = (jid, code, quoted, opts = {}) => result.sendMessage(jid, { aiRich: { code, language: opts.language ?? 'javascript', title: opts.title } }, q(quoted))
|
|
499
|
+
result.sendCodeBlockV2 = (jid, code, quoted, opts = {}) => result.sendMessage(jid, { aiRich: { text: opts.text, code, language: opts.language ?? '', title: opts.title } }, q(quoted))
|
|
500
|
+
result.sendTable = (jid, title, headers, rows, quoted, opts = {}) => result.sendMessage(jid, { aiRich: { title, table: [headers, ...rows] } }, q(quoted))
|
|
501
|
+
result.sendTableV2 = (jid, tableArray, quoted, opts = {}) => result.sendMessage(jid, { aiRich: { text: opts.text, title: opts.headerText, table: tableArray } }, q(quoted))
|
|
502
|
+
result.sendList = (jid, title, items, quoted, opts = {}) => result.sendMessage(jid, { aiRich: { title, table: [['Key', 'Value'], ...items] } }, q(quoted))
|
|
503
|
+
result.sendLink = (jid, text, links, quoted, opts = {}) => result.sendMessage(jid, { aiRich: { title: opts.headerText, text, sources: links.map(url => ({ url, title: url, subtitle: new URL(url).hostname })) } }, q(quoted))
|
|
504
|
+
result.sendLinkV2 = (jid, text, links, quoted, opts = {}) => result.sendMessage(jid, { aiRich: { title: opts.headerText, text, sources: links } }, q(quoted))
|
|
505
|
+
result.sendLatex = (jid, expressions, quoted, opts = {}) => result.sendMessage(jid, { aiRich: { text: opts.text, title: opts.headerText, latex: expressions } }, q(quoted))
|
|
506
|
+
result.sendRichTable = async (jid, title, headers, rows, quoted, opts = {}) => { const { message, messageId } = generateTableContent(title, headers, rows, quoted, opts); return relayRichMessage(jid, generateWAMessageFromContent(jid, message, { userJid: sock.user?.id, messageId: opts.messageId || messageId }), opts) }
|
|
507
|
+
result.sendRichList = async (jid, title, items, quoted, opts = {}) => { const { message, messageId } = generateListContent(title, items, quoted, opts); return relayRichMessage(jid, generateWAMessageFromContent(jid, message, { userJid: sock.user?.id, messageId: opts.messageId || messageId }), opts) }
|
|
508
|
+
result.sendRichCode = async (jid, code, quoted, opts = {}) => { const { message, messageId } = generateCodeBlockContent(code, quoted, opts); return relayRichMessage(jid, generateWAMessageFromContent(jid, message, { userJid: sock.user?.id, messageId: opts.messageId || messageId }), opts) }
|
|
509
|
+
result.sendRichLatex = async (jid, quoted, opts = {}) => { const { message, messageId } = generateLatexContent(quoted, { expressions: [], ...opts }); return relayRichMessage(jid, generateWAMessageFromContent(jid, message, { userJid: sock.user?.id, messageId: opts.messageId || messageId }), opts) }
|
|
510
|
+
result.sendRichLabel = (jid, text, options = {}) => { const r = new AIRich(result); r.addLabel(text, options.variant, options); return r.send(jid, options) }
|
|
511
|
+
result.sendInlineLinks = (jid, links, options = {}) => { const r = new AIRich(result); r.addInlineLinks(links); return r.send(jid, options) }
|
|
512
|
+
result.sendVerticalAlbum = (jid, items, options = {}) => { const r = new AIRich(result); r.addVerticalAlbum(items); return r.send(jid, options) }
|
|
513
|
+
result.sendRichLatexImage = async (jid, opts = {}) => {
|
|
514
|
+
const { text, expressions = [], headerText, footer } = opts
|
|
515
|
+
const rendered = expressions.map(expr => { const latex = expr.latexExpression || expr.expression || expr; const url = `https://latex.codecogs.com/png.latex?${encodeURIComponent(latex).replace(/'/g, '%27')}`; return { latexExpression: latex, url, width: expr.width || 400, height: expr.height || 100 } })
|
|
516
|
+
const { message, messageId } = generateLatexContent(null, { text, expressions: rendered, headerText, footer })
|
|
517
|
+
return relayRichMessage(jid, generateWAMessageFromContent(jid, message, { userJid: sock.user?.id, messageId: opts.messageId || messageId }), opts)
|
|
518
|
+
}
|
|
519
|
+
result.captureAndResendRichResponse = async (jid, metaAiMsg, quoted, opts = {}) => {
|
|
520
|
+
const captured = captureUnifiedResponse(metaAiMsg)
|
|
521
|
+
if (!captured) throw new Error('captureAndResendRichResponse: no unifiedResponse data in message')
|
|
522
|
+
const { message, messageId } = generateUnifiedResponseContent(quoted, captured)
|
|
523
|
+
return relayRichMessage(jid, generateWAMessageFromContent(jid, message, { userJid: sock.user?.id, messageId: opts.messageId || messageId }), opts)
|
|
524
|
+
}
|
|
525
|
+
result.sendBotPlanning = async (jid, content = {}, opts = {}) => {
|
|
526
|
+
const { text = 'Execution plan', steps = [], queryPlan = true, botJid = META_AI_BOT_JID } = content
|
|
527
|
+
const submessages = [{ messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_TEXT, messageText: text }, ...steps.map(s => ({ messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_TEXT, messageText: typeof s === 'string' ? s : `${s.title || s.name || 'Step'}${s.body ? `: ${s.body}` : ''}` }))]
|
|
528
|
+
const wrapped = buildBotRichResponse({ botJid, submessages, capabilities: [BotCapabilityType.AGENTIC_PLANNING, ...(queryPlan ? [BotCapabilityType.QUERY_PLAN] : [])] })
|
|
529
|
+
const full = await generateWAMessageFromContent(jid, wrapped, { userJid: sock.user?.id, messageId: opts.messageId || generateMessageIDV2(sock.user?.id) })
|
|
530
|
+
return relayRichMessage(jid, full, opts)
|
|
531
|
+
}
|
|
532
|
+
result.sendBotPlan = async (jid, content = {}, opts = {}) => {
|
|
533
|
+
const { text = 'Execution plan', title = 'Execution plan', steps = [], estimatedCompletionTime, botJid = META_AI_BOT_JID } = content
|
|
534
|
+
if (!Array.isArray(steps) || steps.length === 0) throw new Error('sendBotPlan requires at least one step')
|
|
535
|
+
const statusEnum = proto.BotProgressIndicatorMetadata.BotPlanningStepMetadata.PlanningStepStatus
|
|
536
|
+
const normalizedSteps = steps.map((s, i) => ({ statusTitle: String(s.title || s.name || `Step ${i + 1}`), statusBody: String(s.body || s.description || ''), status: s.status === 'executing' ? statusEnum.EXECUTING : (s.status === 'finished' || s.status === 'completed') ? statusEnum.FINISHED : statusEnum.PLANNED, isReasoning: s.isReasoning === true, isEnhancedSearch: s.isEnhancedSearch === true }))
|
|
537
|
+
const wrapped = { messageContextInfo: { botMetadata: { capabilityMetadata: { capabilities: [proto.BotCapabilityMetadata.BotCapabilityType.AGENTIC_PLANNING, proto.BotCapabilityMetadata.BotCapabilityType.QUERY_PLAN] }, progressIndicatorMetadata: { progressDescription: title, stepsMetadata: normalizedSteps, ...(estimatedCompletionTime ? { estimatedCompletionTime } : {}) } } }, botForwardedMessage: { message: { richResponseMessage: { messageType: 1, submessages: [{ messageType: 2, messageText: text }], contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid }, forwardOrigin: 4 } } } } }
|
|
538
|
+
const full = await generateWAMessageFromContent(jid, wrapped, { userJid: sock.user?.id, messageId: opts.messageId || generateMessageIDV2(sock.user?.id) })
|
|
539
|
+
await sock.relayMessage(jid, full.message, { messageId: full.key.id })
|
|
540
|
+
return full
|
|
541
|
+
}
|
|
542
|
+
result.replayPlanning = (jid, steps, finalContent, opts) => replayPlanning(result, jid, steps, finalContent, opts)
|
|
543
|
+
result.replayPlanningOnly = (jid, steps, opts) => replayPlanningOnly(result, jid, steps, opts)
|
|
544
|
+
result.metaTyping = (jid, opts) => metaTyping(result, jid, opts)
|
|
545
|
+
result.sendMetaComposited = (jid, content, opts) => sendMetaComposited(result, jid, content, opts)
|
|
546
|
+
result.buildReasoningSteps = buildReasoningSteps
|
|
547
|
+
result.buildSearchSteps = buildSearchSteps
|
|
548
|
+
result.mixedSteps = mixedSteps
|
|
549
|
+
result.buildSteps = buildSteps
|
|
550
|
+
result.PlanningStepStatus = PlanningStepStatus
|
|
551
|
+
result.sendMetaAI = async (a, b, c = {}) => {
|
|
552
|
+
let text, opts = c, yourJid
|
|
553
|
+
if (typeof b === 'string') { text = b; yourJid = a } else { text = a; opts = b || {}; yourJid = opts.yourJid || '' }
|
|
554
|
+
const jid = opts.jid || META_AI_BOT_JID
|
|
555
|
+
const threadId = opts.threadId || generateMessageIDV2(yourJid)
|
|
556
|
+
const now = Date.now()
|
|
557
|
+
const senderKeyHash = opts.senderKeyHash || randomBytes(8).toString('base64')
|
|
558
|
+
const message = {
|
|
559
|
+
extendedTextMessage: proto.Message.ExtendedTextMessage.fromObject({ text, previewType: 'NONE', contextInfo: proto.ContextInfo.fromObject({ botMessageSharingInfo: { botEntryPointOrigin: 'FAVICON', forwardScore: 0 } }), inviteLinkGroupTypeV2: 'DEFAULT' }),
|
|
560
|
+
messageContextInfo: proto.MessageContextInfo.fromObject({ deviceListMetadata: { senderKeyHash, senderTimestamp: opts.senderTimestamp || String(Math.floor(now / 1000)) }, deviceListMetadataVersion: 2, messageSecret: opts.messageSecret || randomBytes(32), botMetadata: { botModeSelectionMetadata: { overrideMode: [0] }, botThreadInfo: { serverInfo: { title: text.substring(0, 50) }, clientInfo: { type: 'DEFAULT' } }, botRenderingConfigMetadata: BOT_RENDERING_CONFIG_METADATA, ...(opts.conversationContext?.length ? { aiConversationContext: opts.conversationContext } : {}) }, threadId: [{ threadType: 'AI_THREAD', threadKey: { remoteJid: '0002@s.whatsapp.net', fromMe: true, id: threadId } }] })
|
|
561
|
+
}
|
|
562
|
+
const msgId = generateMessageIDV2(yourJid)
|
|
563
|
+
await sock.relayMessage(jid, message, { messageId: msgId, ...(opts.quoted ? { quoted: opts.quoted } : {}) })
|
|
564
|
+
return msgId
|
|
565
|
+
}
|
|
566
|
+
result.aiPrompt = async (jid, prompt, options = {}) => {
|
|
567
|
+
const { timeout = 60_000, onPartial, botUser = META_AI_BOT_JID, mentions = [], ...sendOpts } = options
|
|
568
|
+
if (!jid || typeof prompt !== 'string' || !prompt.trim()) throw new Error('aiPrompt requires a chat JID and a non-empty prompt')
|
|
569
|
+
const botJids = await collectMetaAIBotJids(result, jid, botUser)
|
|
570
|
+
const allMentions = [...new Set([...mentions, ...botJids])]
|
|
571
|
+
return new Promise((resolve, reject) => {
|
|
572
|
+
let promptId, settled = false
|
|
573
|
+
const cleanup = () => { clearTimeout(timer); sock.ev.off('messages.upsert', onUpsert); if (onPartial) sock.ev.off('messages.update', onUpdate) }
|
|
574
|
+
const settle = (fn, val) => { if (settled) return; settled = true; cleanup(); fn(val) }
|
|
575
|
+
const timer = setTimeout(() => settle(reject, new Error(`aiPrompt timed out after ${timeout}ms in ${jid}`)), timeout)
|
|
576
|
+
const onUpsert = ({ messages }) => { for (const msg of messages) if (isMetaAIResponse(msg, jid, promptId, botJids)) { settle(resolve, msg); return } }
|
|
577
|
+
const onUpdate = ({ updates }) => { if (!onPartial) return; for (const u of updates) { if (!u.key || u.key.fromMe || u.key.remoteJid !== jid) continue; const sender = jidNormalizedUser(stripDevice(u.key.participant || u.key.remoteJid)); const isBot = isJidMetaAI(sender) || [...botJids].some(b => jidNormalizedUser(stripDevice(b)) === sender); if (isBot && u.message) onPartial(u.message, u.key) } }
|
|
578
|
+
sock.ev.on('messages.upsert', onUpsert)
|
|
579
|
+
if (onPartial) sock.ev.on('messages.update', onUpdate)
|
|
580
|
+
; (async () => {
|
|
581
|
+
try {
|
|
582
|
+
const content = { text: prompt, ...(isJidGroup(jid) ? { mentions: allMentions } : {}), ...sendOpts }
|
|
583
|
+
const full = await generateWAMessage(jid, content, { userJid: sock.user?.id, messageId: sendOpts.messageId || generateMessageIDV2(sock.user?.id) })
|
|
584
|
+
await sock.relayMessage(jid, full.message, { messageId: full.key.id })
|
|
585
|
+
if (config.emitOwnEvents) process.nextTick(() => sock.ev.emit('messages.upsert', { messages: [full], type: 'append' }))
|
|
586
|
+
promptId = full.key.id
|
|
587
|
+
} catch (err) { settle(reject, err) }
|
|
588
|
+
})()
|
|
589
|
+
})
|
|
590
|
+
}
|
|
591
|
+
result.sendAsMimic = async (jid, content, mimicJid, options = {}) => {
|
|
592
|
+
if (!options.admin && !options.mimicPermission) throw new Error('sendAsMimic requires admin: true')
|
|
593
|
+
const msgId = generateMessageIDV2(sock.user?.id)
|
|
594
|
+
const full = await generateWAMessageFromContent(jid, content, { userJid: sock.user?.id, ...options })
|
|
595
|
+
full.key = { remoteJid: jid, fromMe: false, id: msgId, participant: mimicJid }
|
|
596
|
+
await sock.relayMessage(jid, full.message, { messageId: msgId, ...(options.additionalAttributes ? { additionalAttributes: options.additionalAttributes } : {}) })
|
|
597
|
+
return full
|
|
598
|
+
}
|
|
599
|
+
result.sendStatus = async (content, options = {}) => {
|
|
600
|
+
const provided = options.statusJidList || content?.statusJidList || sock.statusJidList || []
|
|
601
|
+
if (!provided.length) throw new Error('sendStatus requires statusJidList')
|
|
602
|
+
return result.sendMessage(STATUS_JID, { ...content, status: true }, { ...options, broadcast: true, statusJidList: provided })
|
|
603
|
+
}
|
|
604
|
+
result.sendBotToolResult = async (jid, content = {}, opts = {}) => {
|
|
605
|
+
const { text = 'Tool result', toolCallId = `tool-${Date.now()}`, resolutionData, resolutionDataSerialized, botJid = META_AI_BOT_JID } = content
|
|
606
|
+
const wrapped = buildBotRichResponse({ text, botJid, capabilities: [BotCapabilityType.AGENTIC_PLANNING, BotCapabilityType.QUERY_PLAN], botMetadata: { resolvedToolCallMetadata: { toolCallId, resolutionDataSerialized: resolutionDataSerialized ?? JSON.stringify(resolutionData ?? {}) } } })
|
|
607
|
+
const full = await generateWAMessageFromContent(jid, wrapped, { userJid: sock.user?.id, messageId: opts.messageId || generateMessageIDV2(sock.user?.id) })
|
|
608
|
+
return relayRichMessage(jid, full, opts)
|
|
609
|
+
}
|
|
610
|
+
result.sendWhatsAppFlow = async (jid, flow, options = {}) => {
|
|
611
|
+
const { text, footer, image, caption, ...flowOpts } = flow || {}
|
|
612
|
+
const btn = makeWhatsAppFlowButton(flowOpts)
|
|
613
|
+
const content = image ? { image, caption: caption || text || flowOpts.cta } : { text: text || flowOpts.cta || '' }
|
|
614
|
+
return result.sendMessage(jid, { ...content, ...(footer ? { footer } : {}), nativeFlow: [btn] }, options)
|
|
615
|
+
}
|
|
616
|
+
result.sendRichMap = (jid, mapOpts, options = {}) => { const r = new AIRich(result); r.addRichMap(mapOpts); return r.send(jid, options) }
|
|
617
|
+
result.sendDynamic = (jid, dynOpts, options = {}) => { const r = new AIRich(result); r.addDynamic(dynOpts); return r.send(jid, options) }
|
|
618
|
+
result.sendBotPromptSuggestions = (jid, content = {}, options = {}) => { const { text, suggestions = [] } = content; const r = new AIRich(result); if (text) r.addText(text); r.addSuggestPrompts(suggestions, content); return r.send(jid, options) }
|
|
619
|
+
result.sendBotSources = (jid, content = {}, options = {}) => { const { text, sources = [] } = content; const r = new AIRich(result); if (text) r.addText(text); r.addSources(sources); return r.send(jid, options) }
|
|
620
|
+
result.sendBotPttTranscript = (jid, content = {}, options = {}) => { const { text, transcript } = content; const r = new AIRich(result); if (text) r.addText(text); r.addPttTranscript(transcript); return r.send(jid, options) }
|
|
621
|
+
result.sendInstagramProfile = (jid, data, options = {}) => { const r = new AIRich(result); r.addInstagramProfile(data); return r.send(jid, options) }
|
|
622
|
+
result.sendRichGeneration = async (jid, opts = {}) => { const { message, responseId, itemId } = prepareRichGenerationMessage(opts); const full = await generateWAMessageFromContent(jid, message, { userJid: sock.user?.id, messageId: opts.messageId || generateMessageIDV2(sock.user?.id) }); await sock.relayMessage(jid, full.message, { messageId: full.key.id }); return { msg: full, responseId, itemId } }
|
|
623
|
+
result.sendUnifiedResponse = (jid, sections, submessages = [], options = {}) => { const r = new AIRich(result); r.addRawSections(sections, submessages); return r.send(jid, options) }
|
|
624
|
+
result.sendRichButtonGrid = async (jid, grid, options = {}) => {
|
|
625
|
+
const { text, footer, cards = [] } = grid || {}
|
|
626
|
+
if (!Array.isArray(cards) || cards.length === 0) throw new Error('sendRichButtonGrid expects at least one card')
|
|
627
|
+
return result.sendMessage(jid, { ...(text ? { text } : {}), ...(footer ? { footer } : {}), cards }, options)
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const normalize = s => s.toLowerCase().replace(/[^a-z]/g, '')
|
|
631
|
+
const knownKeys = Object.keys(result).filter(k => typeof result[k] === 'function')
|
|
632
|
+
const normalizedMap = new Map(knownKeys.map(k => [normalize(k), k]))
|
|
633
|
+
|
|
634
|
+
return new Proxy(result, {
|
|
635
|
+
get(target, prop, receiver) {
|
|
636
|
+
if (prop in target) return Reflect.get(target, prop, receiver)
|
|
637
|
+
if (typeof prop !== 'string') return undefined
|
|
638
|
+
const match = normalizedMap.get(normalize(prop))
|
|
639
|
+
if (match) { target.logger?.warn?.(`[ZervonexHandler] Unknown method "${prop}" — did you mean "${match}"? Calling it.`); return target[match] }
|
|
640
|
+
return undefined
|
|
641
|
+
}
|
|
642
|
+
})
|
|
643
|
+
}
|