@zervonex/baileys 0.0.0-stage → 3.0.1
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 +3203 -2
- 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 +590 -0
- package/lib/Builders/base.js +130 -0
- package/lib/Builders/builders.js +448 -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 +904 -0
- package/lib/Socket/messages-send.js +693 -0
- package/lib/Socket/mex.js +34 -0
- package/lib/Socket/newsletter.js +752 -0
- package/lib/Socket/nexus-handler.js +55 -0
- package/lib/Socket/registration.js +155 -0
- package/lib/Socket/socket.js +798 -0
- package/lib/Socket/spam-report.js +65 -0
- package/lib/Socket/username.js +82 -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 +316 -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 +39 -0
- package/lib/Utils/key-store.js +3 -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 +742 -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 +142 -0
- package/lib/Utils/sync-action-utils.js +33 -0
- package/lib/Utils/tc-token-utils.js +102 -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 +68 -0
- package/package.json +216 -5
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import { proto } from '../../WAProto/index.js'
|
|
3
|
+
import { generateWAMessage, generateWAMessageFromContent, getUrlInfo, prepareWAMessageMedia } from '../Utils/index.js'
|
|
4
|
+
|
|
5
|
+
export const getRandomHex = () => '#' + Array.from(crypto.getRandomValues(new Uint8Array(3)), b => b.toString(16).padStart(2, '0')).join('')
|
|
6
|
+
|
|
7
|
+
export const norm = (data, opts = {}) => {
|
|
8
|
+
if (!data) return { ...opts }
|
|
9
|
+
if (Array.isArray(data)) return { stickers: data, ...opts }
|
|
10
|
+
if (typeof data !== 'object') return { ...opts }
|
|
11
|
+
const keys = Object.keys(data)
|
|
12
|
+
const base = keys.length && keys.every(k => !isNaN(k)) ? { stickers: Object.values(data) } : { ...data }
|
|
13
|
+
return { ...base, ...Object.fromEntries(Object.entries(opts).filter(([, v]) => v != null)) }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class BaseBuilder {
|
|
17
|
+
#client; #data = {}
|
|
18
|
+
constructor(client) {
|
|
19
|
+
if (!client) throw new Error('Socket client is required')
|
|
20
|
+
this.#client = client
|
|
21
|
+
}
|
|
22
|
+
from(data, opts = {}) { this.#data = norm(data, opts); return this }
|
|
23
|
+
get data() { return this.#data }
|
|
24
|
+
get client() { return this.#client }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const buildDispatch = (builders, handlers = {}) => ({
|
|
28
|
+
...Object.fromEntries(builders.flatMap(B => (B.dispatchKeys ?? []).map(key => [key, (sock, jid, content, opts) => new B(sock).from(content[key], opts).send(jid, opts)]))),
|
|
29
|
+
...handlers
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// orderMessage → "order" | stickerPack → "stickerpack"
|
|
33
|
+
const toFactoryName = key => key.replace(/(?:Message|Pack|Packet)$/i, '').toLowerCase()
|
|
34
|
+
// orderMessage → "sendOrderMessage" | already starts with "send" → unchanged
|
|
35
|
+
const toSendName = key => key.toLowerCase().startsWith('send') ? key : 'send' + key.charAt(0).toUpperCase() + key.slice(1)
|
|
36
|
+
|
|
37
|
+
export const buildPlugins = (builders, extra = {}) => {
|
|
38
|
+
const seen = new Set()
|
|
39
|
+
const entries = []
|
|
40
|
+
|
|
41
|
+
for (const B of builders) {
|
|
42
|
+
// 1. explicit static shortcuts win first
|
|
43
|
+
for (const [name, factory] of Object.entries(B.shortcuts ?? {})) {
|
|
44
|
+
if (!seen.has(name)) { seen.add(name); entries.push([name, factory]) }
|
|
45
|
+
}
|
|
46
|
+
// 2. auto-derive factory + send shortcut from each dispatchKey
|
|
47
|
+
for (const key of B.dispatchKeys ?? []) {
|
|
48
|
+
const factoryName = toFactoryName(key)
|
|
49
|
+
const sendName = toSendName(key)
|
|
50
|
+
if (!seen.has(factoryName)) { seen.add(factoryName); entries.push([factoryName, sock => opts => new B(sock, opts)]) }
|
|
51
|
+
if (!seen.has(sendName)) { seen.add(sendName); entries.push([sendName, sock => (jid, data, opts = {}) => new B(sock).from(data, opts).send(jid, opts)]) }
|
|
52
|
+
}
|
|
53
|
+
// 3. fallback: factory from class name (covers builders with no dispatchKeys)
|
|
54
|
+
const className = B.name?.toLowerCase()
|
|
55
|
+
if (className && !seen.has(className)) { seen.add(className); entries.push([className, sock => opts => new B(sock, opts)]) }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 4. extra always wins — overrides any earlier registration
|
|
59
|
+
for (const [name, factory] of Object.entries(extra)) {
|
|
60
|
+
const idx = entries.findIndex(([n]) => n === name)
|
|
61
|
+
if (idx === -1) { seen.add(name); entries.push([name, factory]) }
|
|
62
|
+
else entries[idx] = [name, factory]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return Object.fromEntries(entries)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function handleGroupStatus(sock, jid, content, config) {
|
|
69
|
+
const needsBackground = !content.image && !content.video
|
|
70
|
+
const msg = await generateWAMessage(jid, content, {
|
|
71
|
+
logger: sock.logger, userJid: sock.user?.id, upload: sock.waUploadToServer,
|
|
72
|
+
getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: config?.linkPreviewImageThumbnailWidth, fetchOpts: { timeout: 4000, ...(config?.options ?? {}) }, logger: sock.logger, uploadImage: config?.generateHighQualityLinkPreview ? sock.waUploadToServer : undefined }),
|
|
73
|
+
...(needsBackground && {
|
|
74
|
+
font: content.font ?? Math.floor(Math.random() * 9),
|
|
75
|
+
textColor: content.textColor || getRandomHex(),
|
|
76
|
+
backgroundColor: content.backgroundColor || getRandomHex()
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
return sock.relayMessage(jid, msg.message, { messageId: msg.key.id, additionalNodes: [{ tag: 'meta', attrs: { is_group_status: 'true' }, content: undefined }] })
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function handleStatusMention(sock, jid, d, config) {
|
|
83
|
+
const userJid = sock.user?.id
|
|
84
|
+
const mediaType = d.image ? 'image' : 'video'
|
|
85
|
+
const media = await prepareWAMessageMedia({ [mediaType]: d.image ?? d.video }, { upload: sock.waUploadToServer })
|
|
86
|
+
const statusMsg = await sock.relayMessage('status@broadcast', { ...media }, {
|
|
87
|
+
statusJidList: [d.mentions, userJid].filter(Boolean),
|
|
88
|
+
additionalNodes: [{ tag: 'meta', attrs: {}, content: [{ tag: 'mentioned_users', attrs: {}, content: [{ tag: 'to', attrs: { jid: d.mentions }, content: undefined }] }] }]
|
|
89
|
+
})
|
|
90
|
+
const mentionMsg = await generateWAMessageFromContent(jid, {
|
|
91
|
+
statusMentionMessage: proto.Message.StatusMentionMessage.create({
|
|
92
|
+
message: { protocolMessage: proto.Message.ProtocolMessage.create({ messageId: statusMsg?.key?.id ?? d.mentions, type: proto.Message.ProtocolMessage.Type.STATUS_MENTION_MESSAGE }) }
|
|
93
|
+
})
|
|
94
|
+
}, { userJid })
|
|
95
|
+
return sock.relayMessage(jid, mentionMsg.message, { messageId: mentionMsg.key.id, additionalNodes: [{ tag: 'meta', attrs: { is_status_mention: 'true' }, content: undefined }] })
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function handlePollResult(sock, jid, p) {
|
|
99
|
+
const msg = await generateWAMessageFromContent(jid, {
|
|
100
|
+
pollResultSnapshotMessage: proto.Message.PollResultSnapshotMessage.create({
|
|
101
|
+
name: p.name,
|
|
102
|
+
pollVotes: (p.pollVotes ?? []).map(v => proto.Message.PollResultSnapshotMessage.PollVote.create({ optionName: v.optionName, optionVoteCount: String(v.optionVoteCount ?? 0) })),
|
|
103
|
+
contextInfo: proto.ContextInfo.create({ isForwarded: true, forwardingScore: 1, forwardedNewsletterMessageInfo: proto.ContextInfo.ForwardedNewsletterMessageInfo.create({ newsletterName: p.newsletter?.newsletterName ?? 'Newsletter', newsletterJid: p.newsletter?.newsletterJid ?? '120363399602691477@newsletter', serverMessageId: 1000, contentType: 'UPDATE' }) })
|
|
104
|
+
})
|
|
105
|
+
}, { userJid: sock.user?.id })
|
|
106
|
+
await sock.relayMessage(jid, msg.message, { messageId: msg.key.id })
|
|
107
|
+
return msg
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const q = quoted => quoted ? { quoted } : {}
|
|
111
|
+
|
|
112
|
+
export const PRIMITIVE_SHORTCUTS = {
|
|
113
|
+
sendText: sock => (jid, text, opts = {}) => sock.sendMessage(jid, { text }, opts),
|
|
114
|
+
sendImage: sock => (jid, image, caption = '', opts = {}) => sock.sendMessage(jid, { image, caption }, opts),
|
|
115
|
+
sendVideo: sock => (jid, video, caption = '', opts = {}) => sock.sendMessage(jid, { video, caption }, opts),
|
|
116
|
+
sendDocument: sock => (jid, document, caption = '', opts = {}) => sock.sendMessage(jid, { document, caption }, opts),
|
|
117
|
+
sendAudio: sock => (jid, audio, opts = {}) => sock.sendMessage(jid, { audio, ptt: opts.ptt ?? false, ...opts }),
|
|
118
|
+
sendSticker: sock => (jid, sticker, opts = {}) => sock.sendMessage(jid, { sticker }, opts),
|
|
119
|
+
sendLocation: sock => (jid, location, opts = {}) => sock.sendMessage(jid, { location }, opts),
|
|
120
|
+
sendContact: sock => (jid, contact, opts = {}) => sock.sendMessage(jid, { contacts: { displayName: contact.name ?? '', contacts: [contact] } }, opts),
|
|
121
|
+
sendReaction: sock => (jid, key, emoji, opts = {}) => sock.sendMessage(jid, { react: { text: emoji, key } }, opts),
|
|
122
|
+
sendForward: sock => (jid, message, opts = {}) => sock.sendMessage(jid, { forward: message, force: opts.force }, opts)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const HANDLER_SHORTCUTS = {
|
|
126
|
+
sendStatusMentionMessage: (sock, config) => (jid, data) => handleStatusMention(sock, jid, norm(data), config),
|
|
127
|
+
sendStatusMentions: (sock, config) => (jid, data) => handleStatusMention(sock, jid, norm(data), config),
|
|
128
|
+
sendPollResultMessage: sock => (jid, data) => handlePollResult(sock, jid, norm(data)),
|
|
129
|
+
sendGroupStatusMessage: (sock, config) => (jid, content, opts = {}) => handleGroupStatus(sock, jid, norm(content), config)
|
|
130
|
+
}
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import { proto } from '../../WAProto/index.js'
|
|
3
|
+
import { BOT_RENDERING_CONFIG_METADATA } from '../Defaults/index.js'
|
|
4
|
+
import { generateWAMessage, generateWAMessageFromContent, generateMessageIDV2, prepareStickerPackMessage, prepareWAMessageMedia } from '../Utils/index.js'
|
|
5
|
+
import { getBizBinaryNode } from '../WABinary/index.js'
|
|
6
|
+
import { BaseBuilder, norm, q } from './base.js'
|
|
7
|
+
|
|
8
|
+
const delay = ms => new Promise(r => setTimeout(r, ms))
|
|
9
|
+
|
|
10
|
+
const SPECIAL_FLOW = {
|
|
11
|
+
cta_url: { v: '2', name: 'cta_url' }, cta_call: { v: '2', name: 'cta_call' },
|
|
12
|
+
cta_copy: { v: '2', name: 'cta_copy' }, cta_reminder: { v: '2', name: 'cta_reminder' },
|
|
13
|
+
cta_cancel_reminder: { v: '2', name: 'cta_cancel_reminder' }, address: { v: '2', name: 'address_message' },
|
|
14
|
+
send_location: { v: '2', name: 'send_location' }, cta_open_webview: { v: '3', name: 'cta_open_webview' },
|
|
15
|
+
review_and_pay: { v: '4', name: 'review_and_pay' }, review_order: { v: '4', name: 'review_order' },
|
|
16
|
+
payment_status: { v: '4', name: 'payment_status' }, transaction_details: { v: '4', name: 'transaction_details' },
|
|
17
|
+
order_details: { v: '4', name: 'order_details' }, multi_product: { v: '4', name: 'multi_product' },
|
|
18
|
+
catalog: { v: '5', name: 'catalog_message' }, flow: { v: '5', name: 'flow' },
|
|
19
|
+
galaxy_message: { v: '9', name: 'galaxy_message' },
|
|
20
|
+
}
|
|
21
|
+
const DEFAULT_FLOW = { v: '9', name: 'mixed' }
|
|
22
|
+
|
|
23
|
+
// ─── Button ───────────────────────────────────────────────────────────────────
|
|
24
|
+
export class Button {
|
|
25
|
+
#client; #buttons = []; #header = {}; #body = ''; #footer = ''
|
|
26
|
+
#title = ''; #subtitle = ''; #params = {}; #mode = 'native'; #bloks = null
|
|
27
|
+
|
|
28
|
+
constructor(client, opts = {}) {
|
|
29
|
+
if (!client) throw new Error('Socket client is required')
|
|
30
|
+
this.#client = client
|
|
31
|
+
if (opts.mode) this.#mode = opts.mode
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setTitle(t) { this.#title = t; return this }
|
|
35
|
+
setSubtitle(s) { this.#subtitle = s; return this }
|
|
36
|
+
setBody(t) { this.#body = t; return this }
|
|
37
|
+
setFooter(t) { this.#footer = t; return this }
|
|
38
|
+
setImage(url, opts = {}) { this.#header = { hasMediaAttachment: true, imageMessage: { url, ...opts } }; return this }
|
|
39
|
+
setVideo(url, opts = {}) { this.#header = { hasMediaAttachment: true, videoMessage: { url, ...opts } }; return this }
|
|
40
|
+
setDocument(url, fileName = 'file', opts = {}) { this.#header = { hasMediaAttachment: true, documentMessage: { url, fileName, ...opts } }; return this }
|
|
41
|
+
setLocation(lat, lon, name = '') { this.#header = { hasMediaAttachment: false, locationMessage: { degreesLatitude: lat, degreesLongitude: lon, name } }; return this }
|
|
42
|
+
setParams(p) { this.#params = p; return this }
|
|
43
|
+
setLimitedTimeOffer(exp) { this.#params.limited_time_offer = { expiration_time_ms: exp }; return this }
|
|
44
|
+
setBottomSheet(opts = {}) { this.#params.bottom_sheet = opts; return this }
|
|
45
|
+
setTapTargetConfiguration(opts = {}) { this.#params.tap_target_configuration = opts; return this }
|
|
46
|
+
|
|
47
|
+
#addNative(name, params) { this.#mode = 'native'; this.#buttons.push({ name, buttonParamsJson: JSON.stringify(params) }); return this }
|
|
48
|
+
|
|
49
|
+
reply(text, id) { return this.#addNative('quick_reply', { display_text: text, id }) }
|
|
50
|
+
url(text, url, merchantUrl) { return this.#addNative('cta_url', { display_text: text, url, merchant_url: merchantUrl ?? url }) }
|
|
51
|
+
call(text, phone) { return this.#addNative('cta_call', { display_text: text, phone_number: phone }) }
|
|
52
|
+
copy(text, code) { return this.#addNative('cta_copy', { display_text: text, copy_code: code }) }
|
|
53
|
+
openWebview(text, url, opts = {}) { return this.#addNative('cta_open_webview', { display_text: text, url, ...opts }) }
|
|
54
|
+
catalog(text, opts = {}) { return this.#addNative('catalog', { display_text: text, ...opts }) }
|
|
55
|
+
flow(text, flowId, opts = {}) { return this.#addNative('flow', { display_text: text, flow_id: flowId, flow_token: opts.token ?? crypto.randomUUID(), ...opts }) }
|
|
56
|
+
remind(text, opts = {}) { return this.#addNative('cta_reminder', { display_text: text, ...opts }) }
|
|
57
|
+
cancelReminder(text, opts = {}) { return this.#addNative('cta_cancel_reminder', { display_text: text, ...opts }) }
|
|
58
|
+
address(text, opts = {}) { return this.#addNative('address', { display_text: text, ...opts }) }
|
|
59
|
+
sendLocation(text) { return this.#addNative('send_location', { display_text: text }) }
|
|
60
|
+
reviewAndPay(text, opts = {}) { return this.#addNative('review_and_pay', { display_text: text, ...opts }) }
|
|
61
|
+
reviewOrder(text, opts = {}) { return this.#addNative('review_order', { display_text: text, ...opts }) }
|
|
62
|
+
orderDetails(text, opts = {}) { return this.#addNative('order_details', { display_text: text, ...opts }) }
|
|
63
|
+
paymentStatus(text, opts = {}) { return this.#addNative('payment_status', { display_text: text, ...opts }) }
|
|
64
|
+
transactionDetails(text, opts = {}) { return this.#addNative('transaction_details', { display_text: text, ...opts }) }
|
|
65
|
+
select(title, sections) { return this.#addNative('single_select', { title, sections }) }
|
|
66
|
+
|
|
67
|
+
legacyButton(text, id) { this.#mode = 'legacy'; this.#buttons.push({ buttonId: id, buttonText: { displayText: text }, type: 1 }); return this }
|
|
68
|
+
templateReply(text, id) { this.#mode = 'template'; this.#buttons.push({ quickReplyButton: { displayText: text, id } }); return this }
|
|
69
|
+
templateUrl(text, url) { this.#mode = 'template'; this.#buttons.push({ urlButton: { displayText: text, url } }); return this }
|
|
70
|
+
templateCall(text, phone) { this.#mode = 'template'; this.#buttons.push({ callButton: { displayText: text, phoneNumber: phone } }); return this }
|
|
71
|
+
|
|
72
|
+
setBloksWidget(components) {
|
|
73
|
+
const flat = []
|
|
74
|
+
const resolve = c => { if (!c.__id) c.__id = `c_${flat.length}`; flat.push(c); if (c.children) c.children = c.children.map(ch => { resolve(ch); return ch.__id }); if (c.$ref) c.$ref = c.$ref.__id }
|
|
75
|
+
; (Array.isArray(components) ? components : [components]).forEach(c => resolve(c))
|
|
76
|
+
this.#bloks = flat; this.#mode = 'native'; return this
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
toCard() { return { header: this.#header, body: { text: this.#body }, footer: { text: this.#footer }, nativeFlowMessage: { buttons: this.#buttons } } }
|
|
80
|
+
|
|
81
|
+
async build(jid, opts = {}) {
|
|
82
|
+
const messageId = opts.messageId ?? generateMessageIDV2()
|
|
83
|
+
const userJid = this.#client.user?.id
|
|
84
|
+
if (this.#mode === 'legacy') return generateWAMessageFromContent(jid, { buttonsMessage: { ...(Object.keys(this.#header).length ? this.#header : { contentText: this.#body }), footer: this.#footer, buttons: this.#buttons, headerType: Object.keys(this.#header).length ? 3 : 1 } }, { userJid, messageId })
|
|
85
|
+
if (this.#mode === 'template') return generateWAMessageFromContent(jid, { templateMessage: { hydratedTemplate: { hydratedContentText: this.#body, hydratedFooterText: this.#footer, hydratedButtons: this.#buttons } } }, { userJid, messageId })
|
|
86
|
+
const isSingleSelect = this.#buttons.length === 1 && this.#buttons[0].name === 'single_select'
|
|
87
|
+
if (isSingleSelect && !this.#bloks) { const params = JSON.parse(this.#buttons[0].buttonParamsJson); return generateWAMessageFromContent(jid, { listMessage: { title: this.#title, description: this.#body, footerText: this.#footer, buttonText: params.title ?? 'Select', listType: 1, sections: params.sections ?? [] } }, { userJid, messageId }) }
|
|
88
|
+
const flowInfo = this.#buttons.reduce((acc, b) => { const f = SPECIAL_FLOW[b.name]; return (!acc || (f && parseInt(f.v) > parseInt(acc.v))) ? (f ?? DEFAULT_FLOW) : acc }, null) ?? DEFAULT_FLOW
|
|
89
|
+
return generateWAMessageFromContent(jid, { interactiveMessage: { header: Object.keys(this.#header).length ? this.#header : undefined, body: { text: this.#body }, footer: { text: this.#footer }, nativeFlowMessage: { messageParamsJson: Object.keys(this.#params).length ? JSON.stringify(this.#params) : undefined, buttons: this.#bloks ? [{ name: 'galaxy_message', buttonParamsJson: JSON.stringify({ wa_flow_response_params: { title: this.#title }, components: this.#bloks }) }] : this.#buttons }, ...(this.#title ? { title: this.#title } : {}), ...(this.#subtitle ? { subtitle: this.#subtitle } : {}) } }, { userJid, messageId })
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async send(jid, opts = {}) {
|
|
93
|
+
const msg = await this.build(jid, opts)
|
|
94
|
+
const bizNode = getBizBinaryNode(msg.message)
|
|
95
|
+
await this.#client.relayMessage(jid, msg.message, { messageId: msg.key.id, additionalNodes: bizNode ? [bizNode] : [] })
|
|
96
|
+
return msg
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ─── Interactive ──────────────────────────────────────────────────────────────
|
|
101
|
+
export class Interactive extends BaseBuilder {
|
|
102
|
+
static dispatchKeys = ['interactiveMessage']
|
|
103
|
+
static shortcuts = {
|
|
104
|
+
sendInteractiveMessage: sock => (jid, data, quoted) => new Interactive(sock).from(data).send(jid, q(quoted)),
|
|
105
|
+
sendProductMessage: sock => (jid, data, quoted) => new Interactive(sock).from(data, { __product: true }).send(jid, q(quoted)),
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async send(jid, opts = {}) {
|
|
109
|
+
const i = this.data
|
|
110
|
+
const userJid = this.client.user?.id
|
|
111
|
+
const upload = this.client.waUploadToServer
|
|
112
|
+
let media = null
|
|
113
|
+
if (i.thumbnail) media = await prepareWAMessageMedia({ image: { url: i.thumbnail } }, { upload })
|
|
114
|
+
else if (i.image) media = await prepareWAMessageMedia({ image: i.image }, { upload })
|
|
115
|
+
else if (i.video) media = await prepareWAMessageMedia({ video: i.video }, { upload })
|
|
116
|
+
else if (i.document) media = await prepareWAMessageMedia({ document: i.document }, { upload })
|
|
117
|
+
const bodyText = i.body?.text ?? i.title ?? ''
|
|
118
|
+
const footerText = typeof i.footer === 'string' ? i.footer : (i.footer?.text ?? '')
|
|
119
|
+
const headerTitle = typeof i.header === 'string' ? i.header : (i.header?.title ?? '')
|
|
120
|
+
let nativeFlow = null
|
|
121
|
+
if (i.buttons?.length || i.nativeFlowMessage) { const nfm = i.nativeFlowMessage ?? {}; nativeFlow = proto.Message.InteractiveMessage.NativeFlowMessage.create({ buttons: i.buttons ?? nfm.buttons ?? [], messageParamsJson: nfm.messageParamsJson ?? '' }) }
|
|
122
|
+
const headerMedia = {}
|
|
123
|
+
if (media?.imageMessage) headerMedia.imageMessage = media.imageMessage
|
|
124
|
+
if (media?.videoMessage) headerMedia.videoMessage = media.videoMessage
|
|
125
|
+
if (media?.documentMessage) headerMedia.documentMessage = media.documentMessage
|
|
126
|
+
const interactive = proto.Message.InteractiveMessage.create({ body: proto.Message.InteractiveMessage.Body.create({ text: bodyText }), footer: proto.Message.InteractiveMessage.Footer.create({ text: footerText }), header: proto.Message.InteractiveMessage.Header.create({ title: headerTitle, hasMediaAttachment: !!media, ...headerMedia }), ...(nativeFlow ? { nativeFlowMessage: nativeFlow } : {}) })
|
|
127
|
+
if (i.contextInfo) interactive.contextInfo = i.contextInfo
|
|
128
|
+
const msg = await generateWAMessageFromContent(jid, { interactiveMessage: interactive }, { userJid, quoted: opts.quoted })
|
|
129
|
+
const bizNode = getBizBinaryNode(msg.message)
|
|
130
|
+
await this.client.relayMessage(jid, msg.message, { messageId: msg.key.id, additionalNodes: bizNode ? [bizNode] : [] })
|
|
131
|
+
return msg
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ─── Carousel ─────────────────────────────────────────────────────────────────
|
|
136
|
+
export class Carousel {
|
|
137
|
+
#client; #cards = []; #caption = ''; #footer = ''; #cardType = 'HSCROLL_CARDS'
|
|
138
|
+
|
|
139
|
+
static dispatchKeys = ['carouselMessage']
|
|
140
|
+
static shortcuts = {
|
|
141
|
+
sendCarouselMessage: sock => (jid, data, quoted) => { const d = norm(data); const c = new Carousel(sock); if (d.caption) c.setCaption(d.caption); if (d.footer) c.setFooter(d.footer); (d.cards ?? []).forEach(card => c.card(card)); return c.send(jid, q(quoted)) },
|
|
142
|
+
sendCarouselProtoMessage: sock => (jid, data, quoted) => Carousel.shortcuts.sendCarouselMessage(sock)(jid, data, quoted),
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
constructor(client) { if (!client) throw new Error('Socket client is required'); this.#client = client }
|
|
146
|
+
setCaption(t) { this.#caption = t; return this }
|
|
147
|
+
setFooter(t) { this.#footer = t; return this }
|
|
148
|
+
setCardType(t) { this.#cardType = t; return this }
|
|
149
|
+
card(b) { this.#cards.push(b instanceof Button ? b.toCard() : b); return this }
|
|
150
|
+
|
|
151
|
+
async send(jid, opts = {}) {
|
|
152
|
+
if (!this.#cards.length) throw new Error('Carousel requires at least 1 card')
|
|
153
|
+
if (this.#cards.length > 10) throw new Error('Carousel max 10 cards')
|
|
154
|
+
const msg = await generateWAMessageFromContent(jid, { interactiveMessage: proto.Message.InteractiveMessage.create({ header: proto.Message.InteractiveMessage.Header.create({ hasMediaAttachment: false }), body: proto.Message.InteractiveMessage.Body.create({ text: this.#caption }), footer: proto.Message.InteractiveMessage.Footer.create({ text: this.#footer }), carouselMessage: proto.Message.InteractiveMessage.CarouselMessage.create({ cards: this.#cards.map(c => ({ header: proto.Message.InteractiveMessage.Header.create({ title: c.header?.title ?? '', hasMediaAttachment: c.header?.hasMediaAttachment ?? false, ...c.header }), body: proto.Message.InteractiveMessage.Body.create({ text: c.body?.text ?? '' }), footer: proto.Message.InteractiveMessage.Footer.create({ text: c.footer?.text ?? '' }), nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({ buttons: c.nativeFlowMessage?.buttons ?? [] }) })), messageVersion: 1, carouselCardType: this.#cardType === 'ALBUM_IMAGE' ? 2 : 1 }) }) }, { userJid: this.#client.user?.id })
|
|
155
|
+
const bizNode = getBizBinaryNode(msg.message)
|
|
156
|
+
await this.#client.relayMessage(jid, msg.message, { messageId: msg.key.id, additionalNodes: bizNode ? [bizNode] : [] })
|
|
157
|
+
return msg
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ─── Album ────────────────────────────────────────────────────────────────────
|
|
162
|
+
export class Album extends BaseBuilder {
|
|
163
|
+
#items = []; #delay = 1500
|
|
164
|
+
|
|
165
|
+
static dispatchKeys = ['albumMessage']
|
|
166
|
+
static shortcuts = {
|
|
167
|
+
sendAlbumMessage: sock => (jid, items, quoted, opts = {}) => sock.sendMessage(jid, { albumMessage: items }, { ...q(quoted), ...opts }),
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
image(urlOrBuffer, caption = '') { this.#items.push({ image: typeof urlOrBuffer === 'string' ? { url: urlOrBuffer } : urlOrBuffer, caption }); return this }
|
|
171
|
+
video(urlOrBuffer, caption = '') { this.#items.push({ video: typeof urlOrBuffer === 'string' ? { url: urlOrBuffer } : urlOrBuffer, caption }); return this }
|
|
172
|
+
add(items) { items.forEach(item => this.#items.push(item)); return this }
|
|
173
|
+
setDelay(ms) { this.#delay = ms; return this }
|
|
174
|
+
|
|
175
|
+
async send(jid, opts = {}) {
|
|
176
|
+
const items = this.data.albumMessage ? (Array.isArray(this.data.albumMessage) ? this.data.albumMessage : [this.data.albumMessage]) : this.#items
|
|
177
|
+
if (items.length < 2) throw new Error('Album requires at least 2 items')
|
|
178
|
+
const userJid = this.client.user?.id
|
|
179
|
+
const album = await generateWAMessageFromContent(jid, { messageContextInfo: proto.MessageContextInfo.create({ messageSecret: crypto.randomBytes(32) }), albumMessage: proto.Message.AlbumMessage.create({ expectedImageCount: items.filter(a => a.image).length, expectedVideoCount: items.filter(a => a.video).length }) }, { userJid, messageId: generateMessageIDV2() })
|
|
180
|
+
await this.client.relayMessage(jid, album.message, { messageId: album.key.id })
|
|
181
|
+
for (const item of items) {
|
|
182
|
+
const img = await generateWAMessage(jid, item, { upload: this.client.waUploadToServer, userJid })
|
|
183
|
+
img.message.messageContextInfo = proto.MessageContextInfo.create({ messageSecret: crypto.randomBytes(32), messageAssociation: proto.MessageAssociation.create({ associationType: 1, parentMessageKey: album.key }) })
|
|
184
|
+
await this.client.relayMessage(jid, img.message, { messageId: img.key.id })
|
|
185
|
+
await delay(opts.albumDelay ?? this.#delay)
|
|
186
|
+
}
|
|
187
|
+
return album
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ─── Poll ─────────────────────────────────────────────────────────────────────
|
|
192
|
+
export class Poll extends BaseBuilder {
|
|
193
|
+
#poll = { name: '', values: [], selectableOptionsCount: 1, toAnnouncementGroup: false, hideVoter: false }
|
|
194
|
+
|
|
195
|
+
static shortcuts = {
|
|
196
|
+
sendPoll: sock => (jid, name, values, multiSelect = false, opts = {}) => sock.sendMessage(jid, { poll: { name, values, selectableOptionsCount: multiSelect ? 0 : 1 } }, opts),
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
name(n) { this.#poll.name = n; return this }
|
|
200
|
+
options(o) { this.#poll.values = o; return this }
|
|
201
|
+
addOption(o) { this.#poll.values.push(o); return this }
|
|
202
|
+
multiSelect(max = 0) { this.#poll.selectableOptionsCount = max; return this }
|
|
203
|
+
quiz(ans) { this.#poll.pollType = 1; this.#poll.correctAnswer = ans; return this }
|
|
204
|
+
setEndTime(t) { this.#poll.endTime = t; return this }
|
|
205
|
+
|
|
206
|
+
async send(jid, opts = {}) {
|
|
207
|
+
if (!this.#poll.name) throw new Error('Poll name is required')
|
|
208
|
+
if (this.#poll.values.length < 2) throw new Error('Poll requires at least 2 options')
|
|
209
|
+
return this.client.sendMessage(jid, { poll: this.#poll }, opts)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ─── Payment ──────────────────────────────────────────────────────────────────
|
|
214
|
+
export class Payment extends BaseBuilder {
|
|
215
|
+
static dispatchKeys = ['requestPaymentMessage']
|
|
216
|
+
static shortcuts = {
|
|
217
|
+
sendPaymentMessage: sock => (jid, data, quoted) => new Payment(sock).from(data).send(jid, q(quoted)),
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
setAmount(a) { this.data.amount = a; return this }
|
|
221
|
+
setCurrency(c) { this.data.currency = c; return this }
|
|
222
|
+
setExpiry(t) { this.data.expiry = t; return this }
|
|
223
|
+
setNote(n) { this.data.note = n; return this }
|
|
224
|
+
setFrom(j) { this.data.from = j; return this }
|
|
225
|
+
|
|
226
|
+
async send(jid, opts = {}) {
|
|
227
|
+
const d = this.data
|
|
228
|
+
const ctx = opts.quoted ? { stanzaId: opts.quoted.key?.id, participant: opts.quoted.key?.participant, quotedMessage: opts.quoted.message } : {}
|
|
229
|
+
const notes = d.note ? { extendedTextMessage: { text: d.note, contextInfo: ctx } } : {}
|
|
230
|
+
const msg = await generateWAMessageFromContent(jid, { requestPaymentMessage: proto.Message.RequestPaymentMessage.fromObject({ expiryTimestamp: d.expiry ?? 0, amount1000: d.amount ?? 0, currencyCodeIso4217: d.currency ?? 'NGN', requestFrom: d.from ?? '0@s.whatsapp.net', noteMessage: notes, background: d.background ?? { id: 'DEFAULT', placeholderArgb: 0xfff0f0f0 } }) }, { userJid: this.client.user?.id })
|
|
231
|
+
await this.client.relayMessage(jid, msg.message, { messageId: msg.key.id })
|
|
232
|
+
return msg
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ─── Order ────────────────────────────────────────────────────────────────────
|
|
237
|
+
export class Order extends BaseBuilder {
|
|
238
|
+
static dispatchKeys = ['orderMessage']
|
|
239
|
+
static shortcuts = {
|
|
240
|
+
sendOrderMessage: sock => (jid, data, quoted) => new Order(sock).from(data).send(jid, q(quoted)),
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async send(jid, opts = {}) {
|
|
244
|
+
const o = this.data
|
|
245
|
+
const userJid = this.client.user?.id
|
|
246
|
+
let thumb = null
|
|
247
|
+
if (o.thumbnail) {
|
|
248
|
+
if (Buffer.isBuffer(o.thumbnail)) { thumb = o.thumbnail }
|
|
249
|
+
else if (typeof o.thumbnail === 'string') { try { const { default: axios } = await import('axios'); const res = await axios.get(o.thumbnail, { responseType: 'arraybuffer' }); thumb = Buffer.from(res.data) } catch { } }
|
|
250
|
+
}
|
|
251
|
+
const cleanJid = id => { if (!id) return null; const [user] = id.split(':'); return user.includes('@') ? user : `${user}@s.whatsapp.net` }
|
|
252
|
+
const seller = cleanJid(o.sellerJid) ?? cleanJid(userJid) ?? cleanJid(jid) ?? '0@s.whatsapp.net'
|
|
253
|
+
const msg = await generateWAMessageFromContent(jid, { orderMessage: proto.Message.OrderMessage.create({ orderId: o.orderId ?? `NEXUS${Date.now()}`, thumbnail: thumb, itemCount: o.itemCount ?? 0, status: 2, surface: 1, message: o.message, orderTitle: o.orderTitle, sellerJid: seller, token: o.token ?? 'NEXUS_TOKEN', totalAmount1000: o.totalAmount1000 ?? 0, totalCurrencyCode: o.totalCurrencyCode ?? 'NGN', messageVersion: 2 }) }, { userJid, quoted: opts.quoted })
|
|
254
|
+
await this.client.relayMessage(jid, msg.message, { messageId: msg.key.id })
|
|
255
|
+
return msg
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ─── Event ────────────────────────────────────────────────────────────────────
|
|
260
|
+
export class Event extends BaseBuilder {
|
|
261
|
+
static dispatchKeys = ['eventMessage']
|
|
262
|
+
static shortcuts = {
|
|
263
|
+
sendEventMessage: sock => (jid, data, quoted) => new Event(sock).from(data).send(jid, q(quoted)),
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
setName(n) { this.data.name = n; return this }
|
|
267
|
+
setDescription(d) { this.data.description = d; return this }
|
|
268
|
+
setLocation(l) { this.data.location = l; return this }
|
|
269
|
+
setJoinLink(l) { this.data.joinLink = l; return this }
|
|
270
|
+
setStartTime(t) { this.data.startTime = t; return this }
|
|
271
|
+
setEndTime(t) { this.data.endTime = t; return this }
|
|
272
|
+
setCanceled(v = true) { this.data.isCanceled = v; return this }
|
|
273
|
+
setReminder(sec) { this.data.hasReminder = true; this.data.reminderOffsetSec = sec; return this }
|
|
274
|
+
setScheduledCall(v = true) { this.data.isScheduleCall = v; return this }
|
|
275
|
+
|
|
276
|
+
async send(jid, opts = {}) {
|
|
277
|
+
const e = this.data
|
|
278
|
+
const parseTime = (val, def) => typeof val === 'string' ? parseInt(val) : (val ?? def)
|
|
279
|
+
const msg = await generateWAMessageFromContent(jid, { messageContextInfo: proto.MessageContextInfo.create({ deviceListMetadata: {}, deviceListMetadataVersion: 2, messageSecret: crypto.randomBytes(32), supportPayload: JSON.stringify({ version: 2, is_ai_message: true, should_show_system_message: true, ticket_id: crypto.randomBytes(16).toString('hex') }) }), eventMessage: proto.Message.EventMessage.create({ contextInfo: proto.ContextInfo.create({ mentionedJid: [jid], participant: jid, remoteJid: 'status@broadcast', forwardedNewsletterMessageInfo: proto.ContextInfo.ForwardedNewsletterMessageInfo.create({ newsletterName: 'Nexus Events', newsletterJid: '120363422827915475@newsletter', serverMessageId: 1 }) }), isCanceled: e.isCanceled ?? false, name: e.name, description: e.description, location: e.location ?? { degreesLatitude: 0, degreesLongitude: 0, name: 'Location' }, joinLink: e.joinLink ?? '', startTime: parseTime(e.startTime, Date.now()), endTime: parseTime(e.endTime, Date.now() + 3600000), extraGuestsAllowed: e.extraGuestsAllowed !== false, ...(e.hasReminder ? { hasReminder: true, reminderOffsetSec: e.reminderOffsetSec ?? 3600 } : {}), ...(e.isScheduleCall !== undefined ? { isScheduleCall: e.isScheduleCall } : {}) }) }, { userJid: this.client.user?.id, quoted: opts.quoted })
|
|
280
|
+
await this.client.relayMessage(jid, msg.message, { messageId: msg.key.id })
|
|
281
|
+
return msg
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ─── StickerPack ──────────────────────────────────────────────────────────────
|
|
286
|
+
export class StickerPack extends BaseBuilder {
|
|
287
|
+
static dispatchKeys = ['stickerPack', 'stickerPackMessage']
|
|
288
|
+
static shortcuts = {
|
|
289
|
+
stickerPackMessage: sock => (jid, data, opts = {}) => new StickerPack(sock).from(data, { name: opts.packName, publisher: opts.packPublisher }).send(jid, q(opts.quoted)),
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
setStickers(s) { this.data.stickers = s; return this }
|
|
293
|
+
|
|
294
|
+
async send(jid, opts = {}) {
|
|
295
|
+
const userJid = this.client.user?.id
|
|
296
|
+
const { quoted, ...passOpts } = opts
|
|
297
|
+
const result = await prepareStickerPackMessage(this.data, { upload: this.client.waUploadToServer, logger: this.client.logger, ...passOpts })
|
|
298
|
+
if (result.isBatched) {
|
|
299
|
+
let last
|
|
300
|
+
for (let i = 0; i < result.stickerPackMessage.length; i++) {
|
|
301
|
+
const msg = await generateWAMessageFromContent(jid, { stickerPackMessage: result.stickerPackMessage[i] }, { userJid, quoted })
|
|
302
|
+
await this.client.relayMessage(jid, msg.message, { messageId: msg.key.id })
|
|
303
|
+
last = msg
|
|
304
|
+
if (i < result.stickerPackMessage.length - 1) await delay(2000)
|
|
305
|
+
}
|
|
306
|
+
return last
|
|
307
|
+
}
|
|
308
|
+
const msg = await generateWAMessageFromContent(jid, { stickerPackMessage: result.stickerPackMessage }, { userJid, quoted })
|
|
309
|
+
await this.client.relayMessage(jid, msg.message, { messageId: msg.key.id })
|
|
310
|
+
return msg
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// ─── VerticalAlbum ────────────────────────────────────────────────────────────
|
|
314
|
+
export class VerticalAlbum extends BaseBuilder {
|
|
315
|
+
static dispatchKeys = ['verticalAlbumMessage']
|
|
316
|
+
static shortcuts = {
|
|
317
|
+
sendVerticalAlbumMessage: sock => (jid, data, opts = {}) => new VerticalAlbum(sock).from(data, opts).send(jid, opts)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async send(jid, opts = {}) {
|
|
321
|
+
const items = this.data.items ?? (Array.isArray(this.data.verticalAlbumMessage) ? this.data.verticalAlbumMessage : [])
|
|
322
|
+
if (!Array.isArray(items) || items.length < 2) throw new Error('VerticalAlbum requires at least 2 items')
|
|
323
|
+
const expiration = String(Date.now() + 30 * 24 * 60 * 60 * 1000)
|
|
324
|
+
const primitives = items.map((item, i) => {
|
|
325
|
+
const kind = item?.type || (item?.video ? 'video' : 'image')
|
|
326
|
+
if (kind === 'video') {
|
|
327
|
+
const v = item.video || item.data || item
|
|
328
|
+
const url = typeof v === 'string' ? v : (v?.url ?? v?.videoUrl ?? '')
|
|
329
|
+
if (!url) throw new Error(`VerticalAlbum item ${i}: video requires a URL`)
|
|
330
|
+
return { media: { url, mime_type: v?.mimeType ?? v?.mime_type ?? 'video/mp4', file_length: v?.fileLength ?? v?.file_length ?? 0, duration: v?.duration ?? 0 }, imagine_type: 'ANIMATE', status: { status: 'READY' }, __typename: 'GenAIImaginePrimitive', ...(v?.thumbnail ? { thumbnail: { raw_media: v.thumbnail } } : {}) }
|
|
331
|
+
}
|
|
332
|
+
const img = item?.image || item?.data || item
|
|
333
|
+
const url = typeof img === 'string' ? img : (img?.imageHighResUrl ?? img?.highResUrl ?? img?.fullUrl ?? img?.imagePreviewUrl ?? img?.previewUrl ?? img?.url ?? '')
|
|
334
|
+
if (!url) throw new Error(`VerticalAlbum item ${i}: image requires a URL`)
|
|
335
|
+
const w = Number(img?.width ?? 600), h = Number(img?.height ?? 400)
|
|
336
|
+
const media = (u, mw, mh) => ({ url: u, url_fallback: u, mime_type: img?.mimeType ?? img?.mime_type ?? 'image/png', expiration_timestamp_ms: expiration, width: mw, height: mh })
|
|
337
|
+
return { preview_image: media(url, w, h), full_image: media(img?.highResUrl ?? url, w, h), dark_mode_preview_image: media(img?.darkModePreviewUrl ?? url, w, h), dark_mode_full_image: media(img?.darkModeHighResUrl ?? url, w, h), asset_query_status: 'FETCHED', display_mode: 'FULL_WIDTH', full_width: true, __typename: 'GenAIImagePrimitive' }
|
|
338
|
+
})
|
|
339
|
+
const unified = { response_id: crypto.randomUUID(), sections: [{ view_model: { primitives, display_mode: 'FULL_WIDTH', full_width: true, __typename: 'GenAIVStackLayoutViewModel' } }] }
|
|
340
|
+
const msg = await generateWAMessageFromContent(jid, {
|
|
341
|
+
messageContextInfo: { botMetadata: { pluginMetadata: {}, botRenderingConfigMetadata: BOT_RENDERING_CONFIG_METADATA } },
|
|
342
|
+
botForwardedMessage: { message: { richResponseMessage: proto.Message.AIRichResponseMessage?.create?.({ messageType: 1, submessages: [], unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) }, contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' }, forwardOrigin: 4 } }) ?? { messageType: 1, submessages: [], unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) }, contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' }, forwardOrigin: 4 } } } }
|
|
343
|
+
}, { userJid: this.client.user?.id })
|
|
344
|
+
await this.client.relayMessage(jid, msg.message, { messageId: msg.key.id })
|
|
345
|
+
return msg
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ─── GridImage ────────────────────────────────────────────────────────────────
|
|
350
|
+
export class GridImage extends BaseBuilder {
|
|
351
|
+
static dispatchKeys = ['gridImageMessage']
|
|
352
|
+
static shortcuts = {
|
|
353
|
+
sendGridImageMessage: sock => (jid, data, opts = {}) => new GridImage(sock).from(data, opts).send(jid, opts)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async send(jid, opts = {}) {
|
|
357
|
+
const imageUrls = this.data.imageUrls ?? this.data.images ?? (Array.isArray(this.data.gridImageMessage) ? this.data.gridImageMessage : [])
|
|
358
|
+
if (!Array.isArray(imageUrls) || imageUrls.length < 2) throw new Error('GridImage requires at least 2 images')
|
|
359
|
+
const expiration = String(Date.now() + 30 * 24 * 60 * 60 * 1000)
|
|
360
|
+
// GRID_IMAGE submessage type (6) — proper protocol path, not just unified sections
|
|
361
|
+
const submessages = [{ messageType: 6, gridImageMetadata: { imageUrls } }]
|
|
362
|
+
const sections = imageUrls.map(img => {
|
|
363
|
+
const image = typeof img === 'string' ? { url: img } : img
|
|
364
|
+
const url = image.imageHighResUrl ?? image.highResUrl ?? image.fullUrl ?? image.imagePreviewUrl ?? image.previewUrl ?? image.url ?? ''
|
|
365
|
+
const w = Number(image.width ?? 600), h = Number(image.height ?? 400)
|
|
366
|
+
const media = (u, mw, mh) => ({ url: u, url_fallback: u, mime_type: image.mimeType ?? image.mime_type ?? 'image/png', expiration_timestamp_ms: expiration, width: mw, height: mh })
|
|
367
|
+
return { preview_image: media(url, w, h), full_image: media(image.highResUrl ?? url, w, h), dark_mode_preview_image: media(image.darkModePreviewUrl ?? url, w, h), dark_mode_full_image: media(image.darkModeHighResUrl ?? url, w, h), asset_query_status: 'FETCHED', __typename: 'GenAIImagePrimitive' }
|
|
368
|
+
})
|
|
369
|
+
const unified = { response_id: crypto.randomUUID(), sections: [{ view_model: { primitives: sections, __typename: 'GenAIGridLayoutViewModel' } }] }
|
|
370
|
+
const msg = await generateWAMessageFromContent(jid, {
|
|
371
|
+
messageContextInfo: { botMetadata: { pluginMetadata: {}, botRenderingConfigMetadata: BOT_RENDERING_CONFIG_METADATA } },
|
|
372
|
+
botForwardedMessage: { message: { richResponseMessage: { messageType: 1, submessages, unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) }, contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' }, forwardOrigin: 4 } } } }
|
|
373
|
+
}, { userJid: this.client.user?.id })
|
|
374
|
+
await this.client.relayMessage(jid, msg.message, { messageId: msg.key.id })
|
|
375
|
+
return msg
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// ─── VideoGrid ────────────────────────────────────────────────────────────────
|
|
380
|
+
export class VideoGrid extends BaseBuilder {
|
|
381
|
+
static dispatchKeys = ['videoGridMessage']
|
|
382
|
+
static shortcuts = {
|
|
383
|
+
sendVideoGridMessage: sock => (jid, data, opts = {}) => new VideoGrid(sock).from(data, opts).send(jid, opts)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async send(jid, opts = {}) {
|
|
387
|
+
const videos = this.data.videos ?? (Array.isArray(this.data.videoGridMessage) ? this.data.videoGridMessage : [])
|
|
388
|
+
if (!Array.isArray(videos) || videos.length < 2) throw new Error('VideoGrid requires at least 2 videos')
|
|
389
|
+
const primitives = videos.map((v, i) => {
|
|
390
|
+
const url = typeof v === 'string' ? v : (v?.url ?? v?.videoUrl ?? v?.reelsUrl ?? '')
|
|
391
|
+
if (!url) throw new Error(`VideoGrid item ${i}: requires a URL`)
|
|
392
|
+
const item = typeof v === 'string' ? {} : v
|
|
393
|
+
return { post_id: item.postId != null ? String(item.postId) : undefined, reels_url: url, reels_title: item.title, thumbnail_url: item.thumbnailUrl ?? item.thumbnail, creator: item.creator, avatar_url: item.avatarUrl, content_hash: item.contentHash, likes_count: item.likes, comments_count: item.comments, shares_count: item.shares, is_verified: item.isVerified, source_app: item.sourceApp, 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' }, __typename: 'GenAIVideoPrimitive' }
|
|
394
|
+
})
|
|
395
|
+
const unified = { response_id: crypto.randomUUID(), sections: [{ view_model: { primitives, __typename: 'GenAIGridLayoutViewModel' } }] }
|
|
396
|
+
const msg = await generateWAMessageFromContent(jid, {
|
|
397
|
+
messageContextInfo: { botMetadata: { pluginMetadata: {}, botRenderingConfigMetadata: BOT_RENDERING_CONFIG_METADATA } },
|
|
398
|
+
botForwardedMessage: { message: { richResponseMessage: { messageType: 1, submessages: [], unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) }, contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' }, forwardOrigin: 4 } } } }
|
|
399
|
+
}, { userJid: this.client.user?.id })
|
|
400
|
+
await this.client.relayMessage(jid, msg.message, { messageId: msg.key.id })
|
|
401
|
+
return msg
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ─── A2UI ─────────────────────────────────────────────────────────────────────
|
|
406
|
+
export class A2UI extends BaseBuilder {
|
|
407
|
+
static dispatchKeys = ['a2uiMessage']
|
|
408
|
+
static shortcuts = {
|
|
409
|
+
sendA2UIMessage: sock => (jid, data, opts = {}) => new A2UI(sock).from(data, opts).send(jid, opts),
|
|
410
|
+
sendA2UICommandMenu: sock => (jid, data, opts = {}) => new A2UI(sock).from(data, { __commandMenu: true, ...opts }).send(jid, opts)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async send(jid, opts = {}) {
|
|
414
|
+
const d = this.data
|
|
415
|
+
let content
|
|
416
|
+
if (d.__commandMenu) {
|
|
417
|
+
// command menu preset — builds bloksWidget + nativeFlowMessage together
|
|
418
|
+
const rows = (d.rows ?? []).map((r, i) => ({ title: String(r?.[0] ?? r?.title ?? `Command ${i + 1}`), description: String(r?.[1] ?? r?.description ?? ''), id: String(r?.[2] ?? r?.id ?? r?.[0] ?? `command_${i + 1}`) }))
|
|
419
|
+
const buttons = (d.buttons ?? []).map((b, i) => ({ id: `a2ui_button_${i + 1}`, label: String(b?.text ?? b?.label ?? `Button ${i + 1}`), url: String(b?.url ?? '') }))
|
|
420
|
+
const componentIds = ['title', 'divider', 'tableCard', 'footer', ...(buttons.length ? ['buttonRow'] : [])]
|
|
421
|
+
const components = [
|
|
422
|
+
{ id: 'root', component: 'Column', align: 'center', children: componentIds },
|
|
423
|
+
{ id: 'title', component: 'Text', text: String(d.title ?? 'Menu'), variant: 'h2' },
|
|
424
|
+
{ id: 'divider', component: 'Divider' },
|
|
425
|
+
{ id: 'tableCard', component: 'Card', child: 'tableColumn' },
|
|
426
|
+
{ id: 'tableColumn', component: 'Column', children: ['headerRow', ...rows.flatMap((_, i) => [`divider_${i}`, `row_${i}`])] },
|
|
427
|
+
{ id: 'headerRow', component: 'Row', children: ['headerCommand', 'headerFunction'] },
|
|
428
|
+
{ id: 'headerCommand', component: 'Text', text: 'Command', variant: 'h5' },
|
|
429
|
+
{ id: 'headerFunction', component: 'Text', text: 'Function', variant: 'h5' },
|
|
430
|
+
...rows.flatMap((r, i) => [{ id: `divider_${i}`, component: 'Divider' }, { id: `row_${i}`, component: 'Row', children: [`command_${i}`, `description_${i}`] }, { id: `command_${i}`, component: 'Text', text: r.title }, { id: `description_${i}`, component: 'Text', text: r.description }]),
|
|
431
|
+
{ id: 'footer', component: 'Text', text: d.footer ?? 'Powered by NexusBot', variant: 'caption' }
|
|
432
|
+
]
|
|
433
|
+
if (buttons.length) {
|
|
434
|
+
components.push({ id: 'buttonRow', component: 'Row', justify: 'spaceEvenly', children: buttons.flatMap((b, i) => [`buttonLabel_${i}`, `button_${i}`]) })
|
|
435
|
+
buttons.forEach((b, i) => { components.push({ id: `buttonLabel_${i}`, component: 'Text', text: b.label }); components.push({ id: `button_${i}`, component: 'Button', child: `buttonLabel_${i}`, variant: 'primary', action: { call: 'openUrl', args: { url: b.url } } }) })
|
|
436
|
+
}
|
|
437
|
+
content = { interactiveMessage: { ...(d.imageMessage ? { header: { imageMessage: d.imageMessage, hasMediaAttachment: true } } : {}), bloksWidget: { type: 'im_a2ui', data: JSON.stringify({ version: 'v0.9', createSurface: { surfaceId: 'plogme-a2ui-command-menu', catalogId: 'https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json', components } }), fallback: String(d.fallback ?? d.title ?? '') }, nativeFlowMessage: { messageParamsJson: JSON.stringify({ limited_time_offer: { text: String(d.title ?? ''), url: buttons[0]?.url ?? '', copy_code: String(d.title ?? ''), expiration_time: Date.now() * 1000 } }), buttons: [{ name: 'single_select', buttonParamsJson: JSON.stringify({ title: String(d.title ?? ''), sections: [{ title: 'Available Commands', rows }] }) }] } } }
|
|
438
|
+
} else {
|
|
439
|
+
// generic A2UI envelope
|
|
440
|
+
const { imageMessage, bloksWidget = {}, nativeFlowMessage, fallback = '', ...rest } = d
|
|
441
|
+
content = { interactiveMessage: { ...rest, ...(imageMessage ? { header: { imageMessage, hasMediaAttachment: true } } : {}), bloksWidget: { type: bloksWidget.type ?? 'im_a2ui', data: typeof bloksWidget.data === 'string' ? bloksWidget.data : JSON.stringify(bloksWidget.data ?? {}), ...(bloksWidget.fallback || fallback ? { fallback: String(bloksWidget.fallback ?? fallback) } : {}) }, ...(nativeFlowMessage ? { nativeFlowMessage: { ...nativeFlowMessage, ...(nativeFlowMessage.buttons ? { buttons: nativeFlowMessage.buttons } : {}) } } : {}) } }
|
|
442
|
+
}
|
|
443
|
+
const msg = await generateWAMessageFromContent(jid, content, { userJid: this.client.user?.id })
|
|
444
|
+
const bizNode = getBizBinaryNode(msg.message)
|
|
445
|
+
await this.client.relayMessage(jid, msg.message, { messageId: msg.key.id, additionalNodes: bizNode ? [bizNode] : [] })
|
|
446
|
+
return msg
|
|
447
|
+
}
|
|
448
|
+
}
|