@manybot/manybot 5.5.4 → 5.6.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/README.md +15 -1
- package/dist/client/cache.js +1 -1
- package/dist/client/store.js +9 -0
- package/dist/config.js +230 -12
- package/dist/drivers/baileys/adapter.js +629 -0
- package/dist/drivers/{whatsapp → baileys}/api/index.js +553 -393
- package/dist/drivers/baileys/index.js +594 -0
- package/dist/drivers/{whatsapp → baileys}/loginPrompt.js +2 -0
- package/dist/drivers/{whatsapp → baileys}/messageHandler.js +46 -16
- package/dist/drivers/{whatsapp → baileys}/sdk/baileysSock.js +1 -30
- package/dist/drivers/jid.js +31 -0
- package/dist/drivers/types.js +14 -0
- package/dist/drivers/whatsmeow/client.js +252 -0
- package/dist/drivers/whatsmeow/index.js +79 -0
- package/dist/drivers/whatsmeow/installer.js +86 -0
- package/dist/drivers/whatsmeow/supervisor.js +328 -0
- package/dist/drivers/whatsmeow/whatsmeow.proto +64 -0
- package/dist/i18n/index.js +8 -12
- package/dist/kernel/alerts.js +190 -0
- package/dist/kernel/contactAutoSave.js +200 -0
- package/dist/kernel/driverManager.js +117 -0
- package/dist/kernel/pluginApi.js +25 -7
- package/dist/kernel/pluginLoader.js +12 -12
- package/dist/kernel/sendFallbackGuard.js +183 -0
- package/dist/kernel/sendGuard.js +143 -33
- package/dist/kernel/statusServer.js +39 -0
- package/dist/kernel/updateCheck.js +88 -0
- package/dist/kernel/waContract.js +16 -0
- package/dist/locales/en.json +16 -1
- package/dist/locales/es.json +16 -1
- package/dist/locales/pt.json +16 -1
- package/dist/main.js +100 -5
- package/dist/types.js +18 -11
- package/package.json +6 -8
- package/dist/core/adapter.js +0 -12
- package/dist/core/capabilities.js +0 -16
- package/dist/core/types.js +0 -6
- package/dist/drivers/index.js +0 -14
- package/dist/drivers/whatsapp/adapter.js +0 -7
- package/dist/drivers/whatsapp/index.js +0 -382
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* drivers/baileys/adapter.ts
|
|
3
|
+
*
|
|
4
|
+
* Adapter that wraps a Baileys `WASocket` (the raw, driver-specific socket)
|
|
5
|
+
* and the in-memory store behind the driver-neutral `WaContract` declared
|
|
6
|
+
* in src/kernel/waContract.ts.
|
|
7
|
+
*
|
|
8
|
+
* This is the ONLY file outside `drivers/baileys/sdk/baileysSock.ts` that
|
|
9
|
+
* imports from `@whiskeysockets/baileys`. Every other module in the
|
|
10
|
+
* codebase (kernel, pluginApi, pluginLoader, sendGuard, contactAutoSave,
|
|
11
|
+
* messageHandler, api/index.ts) talks to the driver through this contract
|
|
12
|
+
* — never imports Baileys directly.
|
|
13
|
+
*
|
|
14
|
+
* Responsibilities:
|
|
15
|
+
* - Convert Baileys event payloads (WAMessage, Chat, Contact) into the
|
|
16
|
+
* neutral event payloads the contract declares.
|
|
17
|
+
* - Convert BotQuotedRef / BotGroupMetadata / etc. back into the
|
|
18
|
+
* Baileys-shaped arguments each Baileys method expects.
|
|
19
|
+
* - Implement every method WaContract mandates, by delegating to the
|
|
20
|
+
* matching Baileys WASocket method.
|
|
21
|
+
*
|
|
22
|
+
* Returns/dispatches are pure adapters — error semantics, retries, fallbacks
|
|
23
|
+
* all live elsewhere (sendFallbackGuard, sendGuard, pluginGuard).
|
|
24
|
+
*/
|
|
25
|
+
import { normalizeMessageContent, downloadMediaMessage, jidNormalizedUser, } from "@whiskeysockets/baileys";
|
|
26
|
+
import { createHash } from "node:crypto";
|
|
27
|
+
import { logger } from "#logger";
|
|
28
|
+
/**
|
|
29
|
+
* Classify a Baileys message-content payload (`WAMessageContent`) into
|
|
30
|
+
* the neutral `(type, body, mimetype)` triple used by `BotMessage`. Pure
|
|
31
|
+
* function — no side effects, no closures — so it's safe to call for
|
|
32
|
+
* both real incoming messages and the embedded `quotedMessage` content
|
|
33
|
+
* carried in `contextInfo` (which can be an `ephemeralMessage`/
|
|
34
|
+
* `viewOnceMessage` wrapper, hence the `normalizeMessageContent` call).
|
|
35
|
+
*
|
|
36
|
+
* Exported so `api/index.ts` can decode the quoted payload when
|
|
37
|
+
* synthesizing the `BotMessage` returned by `getReply()`.
|
|
38
|
+
*/
|
|
39
|
+
export function decodeContent(content) {
|
|
40
|
+
const m = normalizeMessageContent(content) ?? undefined;
|
|
41
|
+
let type = "other";
|
|
42
|
+
let body = "";
|
|
43
|
+
let mimetype;
|
|
44
|
+
if (m?.conversation) {
|
|
45
|
+
type = "text";
|
|
46
|
+
body = m.conversation;
|
|
47
|
+
}
|
|
48
|
+
else if (m?.extendedTextMessage?.text) {
|
|
49
|
+
type = "text";
|
|
50
|
+
body = m.extendedTextMessage.text ?? "";
|
|
51
|
+
}
|
|
52
|
+
else if (m?.imageMessage) {
|
|
53
|
+
type = "image";
|
|
54
|
+
body = m.imageMessage.caption ?? "";
|
|
55
|
+
mimetype = m.imageMessage.mimetype ?? undefined;
|
|
56
|
+
}
|
|
57
|
+
else if (m?.videoMessage) {
|
|
58
|
+
type = "video";
|
|
59
|
+
body = m.videoMessage.caption ?? "";
|
|
60
|
+
mimetype = m.videoMessage.mimetype ?? undefined;
|
|
61
|
+
}
|
|
62
|
+
else if (m?.audioMessage) {
|
|
63
|
+
type = "audio";
|
|
64
|
+
mimetype = m.audioMessage.mimetype ?? undefined;
|
|
65
|
+
}
|
|
66
|
+
else if (m?.documentMessage) {
|
|
67
|
+
type = "document";
|
|
68
|
+
body = m.documentMessage.caption ?? "";
|
|
69
|
+
mimetype = m.documentMessage.mimetype ?? undefined;
|
|
70
|
+
}
|
|
71
|
+
else if (m?.stickerMessage) {
|
|
72
|
+
type = "sticker";
|
|
73
|
+
mimetype = m.stickerMessage.mimetype ?? undefined;
|
|
74
|
+
}
|
|
75
|
+
return { type, body, mimetype };
|
|
76
|
+
}
|
|
77
|
+
export function createBaileysAdapter(initial) {
|
|
78
|
+
// mutable so rebind() can swap it; closure-scoped so the contract below
|
|
79
|
+
// always sees the latest sock.
|
|
80
|
+
let sock = initial.sock;
|
|
81
|
+
const store = initial.store;
|
|
82
|
+
// ── Adapter-local helpers ────────────────────────────────────────────────
|
|
83
|
+
/**
|
|
84
|
+
* Build the `quoted` option for `sock.sendMessage(jid, content, opts)`.
|
|
85
|
+
*
|
|
86
|
+
* Baileys expects the `quoted` field to be a full message-shaped object —
|
|
87
|
+
* `{ key: {...}, message: {...} }` — so it can extract both the attribution
|
|
88
|
+
* (stanzaId/participant/fromMe from `key`) and the preview content (from
|
|
89
|
+
* `message`) when generating contextInfo. Passing only `key` makes
|
|
90
|
+
* `generateWAMessageFromContent` call `normalizeMessageContent(undefined)`
|
|
91
|
+
* → `getContentType(undefined)` → `undefined`, then index it with `[]`
|
|
92
|
+
* and throw `Cannot read properties of undefined (reading 'undefined')`.
|
|
93
|
+
*
|
|
94
|
+
* We resolve the full envelope from the in-memory store, indexed by the
|
|
95
|
+
* (remoteJid, id) pair carried in the neutral `BotQuotedRef`. If the
|
|
96
|
+
* envelope is no longer present (evicted past MAX_MSGS_PER_CHAT, lost on
|
|
97
|
+
* restart, etc.) we return `undefined` rather than emit a half-formed
|
|
98
|
+
* `quoted` — degrading to a plain unquoted reply is safer than crashing
|
|
99
|
+
* the calling plugin.
|
|
100
|
+
*/
|
|
101
|
+
function buildQuotedOpts(quoted) {
|
|
102
|
+
if (!quoted?.id || !quoted?.remoteJid)
|
|
103
|
+
return undefined;
|
|
104
|
+
const raw = store.messages.get(quoted.remoteJid)?.get(quoted.id);
|
|
105
|
+
const inner = raw?.message;
|
|
106
|
+
if (!inner)
|
|
107
|
+
return undefined; // safe fallback: no quoted at all
|
|
108
|
+
// `quoted` (used by sendMessage for reply-citation) is a
|
|
109
|
+
// message-shaped object: `{ key: {...}, message: {...} }`. Baileys
|
|
110
|
+
// reads `quoted.key.*` to populate contextInfo (stanzaId, participant,
|
|
111
|
+
// fromMe) — passing the fields flat used to misattribute the reply to
|
|
112
|
+
// the bot itself. `quoted.message` is also required: Baileys' internal
|
|
113
|
+
// `generateWAMessageFromContent` calls `normalizeMessageContent(
|
|
114
|
+
// quoted.message)` and indexes the result with `getContentType(...)`,
|
|
115
|
+
// which throws if `.message` is missing.
|
|
116
|
+
return { quoted: { key: toFlatKey(quoted), message: inner } };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Translate a neutral `BotQuotedRef` into the FLAT key shape Baileys
|
|
120
|
+
* expects for `react`/`delete`/`edit`/`readMessages` (proto.IMessageKey
|
|
121
|
+
* — `{ id, remoteJid, fromMe, participant }` directly, NOT nested under
|
|
122
|
+
* a `.key` field). The nested `{key, message}` form is reserved for the
|
|
123
|
+
* `quoted` field on sendMessage — see `buildQuotedOpts` above.
|
|
124
|
+
*/
|
|
125
|
+
function toFlatKey(ref) {
|
|
126
|
+
return {
|
|
127
|
+
id: ref.id ?? null,
|
|
128
|
+
remoteJid: ref.remoteJid ?? "",
|
|
129
|
+
fromMe: !!ref.fromMe,
|
|
130
|
+
participant: ref.participant ?? undefined,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** Compute sha1-hex of a normalized buffer or string. */
|
|
134
|
+
function sha1(input) {
|
|
135
|
+
const hash = createHash("sha1");
|
|
136
|
+
if (typeof input === "string") {
|
|
137
|
+
hash.update(input, "utf8");
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
hash.update(input);
|
|
141
|
+
}
|
|
142
|
+
return hash.digest("hex");
|
|
143
|
+
}
|
|
144
|
+
/** Translate a Baileys WAMessage into the neutral BotMessage envelope. */
|
|
145
|
+
function toBotMessage(msg) {
|
|
146
|
+
const m = normalizeMessageContent(msg.message) ?? undefined;
|
|
147
|
+
const { type, body, mimetype } = decodeContent(msg.message);
|
|
148
|
+
const key = msg.key;
|
|
149
|
+
const contextInfo = m?.extendedTextMessage?.contextInfo ??
|
|
150
|
+
m?.imageMessage?.contextInfo ??
|
|
151
|
+
m?.videoMessage?.contextInfo ??
|
|
152
|
+
m?.audioMessage?.contextInfo ??
|
|
153
|
+
m?.documentMessage?.contextInfo ??
|
|
154
|
+
undefined;
|
|
155
|
+
const ciTyped = contextInfo;
|
|
156
|
+
return {
|
|
157
|
+
id: msg.key.id ?? "",
|
|
158
|
+
chatId: msg.key.remoteJid ?? "",
|
|
159
|
+
fromMe: !!msg.key.fromMe,
|
|
160
|
+
type,
|
|
161
|
+
contentHash: sha1(body.trim()),
|
|
162
|
+
timestamp: Number(msg.messageTimestamp ?? 0) * 1000,
|
|
163
|
+
body,
|
|
164
|
+
mimetype: mimetype ?? undefined,
|
|
165
|
+
pushName: msg.pushName,
|
|
166
|
+
mentionedJid: ciTyped?.mentionedJid ?? undefined,
|
|
167
|
+
quotedKey: ciTyped?.stanzaId ? {
|
|
168
|
+
id: ciTyped.stanzaId,
|
|
169
|
+
remoteJid: msg.key.remoteJid ?? undefined,
|
|
170
|
+
fromMe: false,
|
|
171
|
+
participant: ciTyped.participant ?? undefined,
|
|
172
|
+
} : undefined,
|
|
173
|
+
fromLid: key.participantAlt,
|
|
174
|
+
fromPn: key.participant,
|
|
175
|
+
participantAlt: key.participantAlt,
|
|
176
|
+
remoteJidAlt: key.remoteJidAlt,
|
|
177
|
+
// Driver-specific escape hatches:
|
|
178
|
+
// - pollEncKeyRaw: poll-decryption key for vote decryption
|
|
179
|
+
// - contextInfo: full IContextInfo (incl. embedded quotedMessage)
|
|
180
|
+
// so quoted-message consumers (api/index.ts
|
|
181
|
+
// buildMessageContext / downloadMedia fallback)
|
|
182
|
+
// can decode the quoted payload without going
|
|
183
|
+
// back through the store.
|
|
184
|
+
_raw: {
|
|
185
|
+
pollEncKeyRaw: m?.messageContextInfo?.messageSecret ?? undefined,
|
|
186
|
+
contextInfo: ciTyped ?? undefined,
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/** Plain summary of a Baileys Chat. */
|
|
191
|
+
function chatSummary(c) {
|
|
192
|
+
return { id: c.id, name: c.name };
|
|
193
|
+
}
|
|
194
|
+
/** Plain summary of a Baileys Contact. */
|
|
195
|
+
function contactSummary(c) {
|
|
196
|
+
return {
|
|
197
|
+
id: c.id,
|
|
198
|
+
name: c.name,
|
|
199
|
+
notify: c.notify,
|
|
200
|
+
verifiedName: c.verifiedName,
|
|
201
|
+
lid: c.lid,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const subscribers = new Map();
|
|
205
|
+
// Tracks the handlers each socket has registered so we can remove them on a
|
|
206
|
+
// fresh socket after reconnect. Declared up here because the rebind helpers
|
|
207
|
+
// below close over it and the first `bindSockEventsExternal(sock)` call
|
|
208
|
+
// happens before the later declaration site would have executed — accessing
|
|
209
|
+
// it there would hit the temporal dead zone.
|
|
210
|
+
const boundHandlers = new WeakMap();
|
|
211
|
+
function emit(event, payload) {
|
|
212
|
+
const set = subscribers.get(event);
|
|
213
|
+
if (!set)
|
|
214
|
+
return;
|
|
215
|
+
for (const h of set) {
|
|
216
|
+
try {
|
|
217
|
+
h(payload);
|
|
218
|
+
}
|
|
219
|
+
catch (e) {
|
|
220
|
+
logger.debug(`[waContract] handler for "${event}" threw: ${e.message}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
bindSockEventsExternal(sock);
|
|
225
|
+
// ── The contract ────────────────────────────────────────────────────────
|
|
226
|
+
const contract = {
|
|
227
|
+
name: "baileys",
|
|
228
|
+
// ── lifecycle ─────────────────────────────────────────────────────────
|
|
229
|
+
async connect() { },
|
|
230
|
+
async disconnect() { },
|
|
231
|
+
isReady() { return false; /* drivers/baileys/index.ts overrides this via the WaContract lifecycle */ },
|
|
232
|
+
async resolveLid(lid) {
|
|
233
|
+
try {
|
|
234
|
+
const repo = sock.signalRepository;
|
|
235
|
+
const fn = repo?.lidMapping?.getPNForLID;
|
|
236
|
+
if (typeof fn === "function") {
|
|
237
|
+
const pn = await fn(lid);
|
|
238
|
+
return pn ?? null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
logger.debug(`[waContract] resolveLid cross-check failed for "${lid}": ${err.message}`);
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
},
|
|
246
|
+
// ── event subscription ────────────────────────────────────────────────
|
|
247
|
+
on(event, handler) {
|
|
248
|
+
let set = subscribers.get(event);
|
|
249
|
+
if (!set) {
|
|
250
|
+
set = new Set();
|
|
251
|
+
subscribers.set(event, set);
|
|
252
|
+
}
|
|
253
|
+
set.add(handler);
|
|
254
|
+
return () => set.delete(handler);
|
|
255
|
+
},
|
|
256
|
+
// ── send ───────────────────────────────────────────────────────────────
|
|
257
|
+
async sendText(jid, text, opts) {
|
|
258
|
+
const content = { text };
|
|
259
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
260
|
+
const sendOpts = buildQuotedOpts(opts?.quoted);
|
|
261
|
+
if (opts?.mentions?.length)
|
|
262
|
+
content.mentions = opts.mentions;
|
|
263
|
+
const ref = await sock.sendMessage(jid, content, sendOpts);
|
|
264
|
+
return toSentRef(ref, jid);
|
|
265
|
+
},
|
|
266
|
+
async sendImage(jid, buffer, opts) {
|
|
267
|
+
const content = { image: buffer };
|
|
268
|
+
if (opts?.caption)
|
|
269
|
+
content.caption = opts.caption;
|
|
270
|
+
if (opts?.viewOnce)
|
|
271
|
+
content.viewOnce = true;
|
|
272
|
+
if (opts?.mentions?.length)
|
|
273
|
+
content.mentions = opts.mentions;
|
|
274
|
+
const ref = await sock.sendMessage(jid, content, buildQuotedOpts(opts?.quoted));
|
|
275
|
+
return toSentRef(ref, jid);
|
|
276
|
+
},
|
|
277
|
+
async sendVideo(jid, buffer, opts) {
|
|
278
|
+
const content = { video: buffer };
|
|
279
|
+
if (opts?.caption)
|
|
280
|
+
content.caption = opts.caption;
|
|
281
|
+
if (opts?.viewOnce)
|
|
282
|
+
content.viewOnce = true;
|
|
283
|
+
if (opts?.gifPlayback)
|
|
284
|
+
content.gifPlayback = true;
|
|
285
|
+
if (opts?.mentions?.length)
|
|
286
|
+
content.mentions = opts.mentions;
|
|
287
|
+
const ref = await sock.sendMessage(jid, content, buildQuotedOpts(opts?.quoted));
|
|
288
|
+
return toSentRef(ref, jid);
|
|
289
|
+
},
|
|
290
|
+
async sendAudio(jid, buffer, opts) {
|
|
291
|
+
const content = { audio: buffer };
|
|
292
|
+
content.mimetype = opts?.mimetype ?? "audio/mp4";
|
|
293
|
+
if (opts?.ptt)
|
|
294
|
+
content.ptt = true;
|
|
295
|
+
if (opts?.viewOnce)
|
|
296
|
+
content.viewOnce = true;
|
|
297
|
+
const ref = await sock.sendMessage(jid, content, buildQuotedOpts(opts?.quoted));
|
|
298
|
+
return toSentRef(ref, jid);
|
|
299
|
+
},
|
|
300
|
+
async sendSticker(jid, buffer, opts) {
|
|
301
|
+
const ref = await sock.sendMessage(jid, { sticker: buffer }, buildQuotedOpts(opts?.quoted));
|
|
302
|
+
return toSentRef(ref, jid);
|
|
303
|
+
},
|
|
304
|
+
async sendDocument(jid, buffer, filename, mimetype, opts) {
|
|
305
|
+
const ref = await sock.sendMessage(jid, { document: buffer, mimetype, fileName: filename }, buildQuotedOpts(opts?.quoted));
|
|
306
|
+
return toSentRef(ref, jid);
|
|
307
|
+
},
|
|
308
|
+
async sendPoll(jid, opts) {
|
|
309
|
+
const poll = { name: opts.name, values: opts.values, selectableCount: opts.selectableCount ?? 1 };
|
|
310
|
+
const ref = await sock.sendMessage(jid, { poll }, buildQuotedOpts(opts.quoted));
|
|
311
|
+
return toSentRef(ref, jid);
|
|
312
|
+
},
|
|
313
|
+
async react(jid, target, emoji) {
|
|
314
|
+
const key = toFlatKey(target);
|
|
315
|
+
await sock.sendMessage(jid, { react: { text: emoji, key } });
|
|
316
|
+
},
|
|
317
|
+
async deleteMessage(jid, target, forEveryone) {
|
|
318
|
+
if (!forEveryone)
|
|
319
|
+
return;
|
|
320
|
+
const key = toFlatKey(target);
|
|
321
|
+
await sock.sendMessage(jid, { delete: key });
|
|
322
|
+
},
|
|
323
|
+
async editMessage(jid, target, text) {
|
|
324
|
+
const key = toFlatKey(target);
|
|
325
|
+
await sock.sendMessage(jid, { text, edit: key });
|
|
326
|
+
},
|
|
327
|
+
// ── presence + read ───────────────────────────────────────────────────
|
|
328
|
+
async sendPresenceUpdate(state, jid) {
|
|
329
|
+
const baileyState = state === "composing" ? "composing"
|
|
330
|
+
: state === "recording" ? "recording"
|
|
331
|
+
: "paused";
|
|
332
|
+
await sock.sendPresenceUpdate(baileyState, jid);
|
|
333
|
+
},
|
|
334
|
+
async readMessages(keys) {
|
|
335
|
+
const baileyKeys = keys.map((k) => toFlatKey(k));
|
|
336
|
+
await sock.readMessages(baileyKeys);
|
|
337
|
+
},
|
|
338
|
+
// ── contacts ──────────────────────────────────────────────────────────
|
|
339
|
+
async onWhatsApp(jid) {
|
|
340
|
+
const fn = sock.onWhatsApp?.bind(sock);
|
|
341
|
+
if (!fn)
|
|
342
|
+
return null;
|
|
343
|
+
return await fn(jid);
|
|
344
|
+
},
|
|
345
|
+
async getBusinessProfile(jid) {
|
|
346
|
+
try {
|
|
347
|
+
const fn = sock.getBusinessProfile;
|
|
348
|
+
return await fn(jid);
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
async profilePictureUrl(jid) {
|
|
355
|
+
try {
|
|
356
|
+
const url = await sock.profilePictureUrl(jid, "image");
|
|
357
|
+
return url ?? null;
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
async fetchStatus(jid) {
|
|
364
|
+
const fn = sock.fetchStatus?.bind(sock);
|
|
365
|
+
if (!fn)
|
|
366
|
+
return null;
|
|
367
|
+
try {
|
|
368
|
+
const res = await fn(jid);
|
|
369
|
+
if (Array.isArray(res)) {
|
|
370
|
+
const entry = res.find((r) => jidNormalizedUser(r.id) === jidNormalizedUser(jid)) ?? res[0];
|
|
371
|
+
return entry?.status?.status ?? null;
|
|
372
|
+
}
|
|
373
|
+
return res?.status ?? null;
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
async updateBlockStatus(jid, action) {
|
|
380
|
+
const fn = sock.updateBlockStatus;
|
|
381
|
+
await fn(jid, action);
|
|
382
|
+
},
|
|
383
|
+
async addOrEditContact(jid, info) {
|
|
384
|
+
await sock.addOrEditContact(jid, info);
|
|
385
|
+
},
|
|
386
|
+
async removeContact(jid) {
|
|
387
|
+
await sock.removeContact(jid);
|
|
388
|
+
},
|
|
389
|
+
// ── groups ─────────────────────────────────────────────────────────────
|
|
390
|
+
async groupMetadata(jid) {
|
|
391
|
+
const meta = await sock.groupMetadata(jid);
|
|
392
|
+
return {
|
|
393
|
+
subject: meta.subject,
|
|
394
|
+
participants: meta.participants.map(p => ({
|
|
395
|
+
id: jidNormalizedUser(p.id),
|
|
396
|
+
isAdmin: p.admin === "admin" || p.admin === "superadmin",
|
|
397
|
+
isSuperAdmin: p.admin === "superadmin",
|
|
398
|
+
phoneNumber: p.phoneNumber,
|
|
399
|
+
})),
|
|
400
|
+
};
|
|
401
|
+
},
|
|
402
|
+
async groupParticipantsUpdate(jid, users, action) {
|
|
403
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
404
|
+
const res = await sock.groupParticipantsUpdate(jid, users, action);
|
|
405
|
+
return res;
|
|
406
|
+
},
|
|
407
|
+
async groupUpdateSubject(jid, subject) {
|
|
408
|
+
await sock.groupUpdateSubject(jid, subject);
|
|
409
|
+
},
|
|
410
|
+
async groupUpdateDescription(jid, description) {
|
|
411
|
+
await sock.groupUpdateDescription(jid, description);
|
|
412
|
+
},
|
|
413
|
+
async groupInviteCode(jid) {
|
|
414
|
+
return await sock.groupInviteCode(jid);
|
|
415
|
+
},
|
|
416
|
+
async groupRevokeInvite(jid) {
|
|
417
|
+
return await sock.groupRevokeInvite(jid);
|
|
418
|
+
},
|
|
419
|
+
// ── profile ────────────────────────────────────────────────────────────
|
|
420
|
+
async updateProfilePicture(jid, buffer) {
|
|
421
|
+
await sock.updateProfilePicture(jid, buffer);
|
|
422
|
+
},
|
|
423
|
+
async updateProfileName(name) {
|
|
424
|
+
await sock.updateProfileName(name);
|
|
425
|
+
},
|
|
426
|
+
async updateProfileStatus(status) {
|
|
427
|
+
await sock.updateProfileStatus(status);
|
|
428
|
+
},
|
|
429
|
+
// ── me ─────────────────────────────────────────────────────────────────
|
|
430
|
+
me() {
|
|
431
|
+
const u = sock.user;
|
|
432
|
+
const id = u?.id ? jidNormalizedUser(u.id) : "";
|
|
433
|
+
return { id, lid: u?.lid };
|
|
434
|
+
},
|
|
435
|
+
// ── verification primitive ──────────────────────────────────────────
|
|
436
|
+
// sendFallbackGuard calls this right after sendText resolves to confirm
|
|
437
|
+
// the message actually landed. The Baileys in-memory store is updated
|
|
438
|
+
// synchronously by the `messages.upsert` listener (store.ts), and
|
|
439
|
+
// own-sent messages land there as soon as sock.sendMessage returns —
|
|
440
|
+
// so the first lookup is effectively free (no network round-trip).
|
|
441
|
+
async getHistory(jid, opts) {
|
|
442
|
+
const limit = opts?.limit ?? 5;
|
|
443
|
+
const chatMsgs = store.messages.get(jid);
|
|
444
|
+
if (!chatMsgs || chatMsgs.size === 0)
|
|
445
|
+
return [];
|
|
446
|
+
// Newest-first so callers can slice `limit` off the head; we sort
|
|
447
|
+
// by messageTimestamp (seconds) and fall back to insertion order
|
|
448
|
+
// for messages synced together (same timestamp) so ordering stays
|
|
449
|
+
// stable across reads.
|
|
450
|
+
const all = [...chatMsgs.values()];
|
|
451
|
+
all.sort((a, b) => {
|
|
452
|
+
const ta = Number(a.messageTimestamp ?? 0);
|
|
453
|
+
const tb = Number(b.messageTimestamp ?? 0);
|
|
454
|
+
if (ta !== tb)
|
|
455
|
+
return tb - ta;
|
|
456
|
+
return 0;
|
|
457
|
+
});
|
|
458
|
+
return all.slice(0, limit).map(toBotMessage);
|
|
459
|
+
},
|
|
460
|
+
// ── media (download) ───────────────────────────────────────────────────
|
|
461
|
+
async downloadMedia(msg, opts) {
|
|
462
|
+
// Resolve the Baileys message envelope needed by `downloadMediaMessage`.
|
|
463
|
+
// Preferred path: if the caller already carries the embedded message
|
|
464
|
+
// payload (synthetic `BotMessage` for a quoted message, whose
|
|
465
|
+
// `_raw.contextInfo.quotedMessage` is the full WAMessageContent),
|
|
466
|
+
// build the envelope directly from that. This avoids the silent
|
|
467
|
+
// failure mode where the quoted message's original envelope has
|
|
468
|
+
// aged out of the store's per-chat ring buffer.
|
|
469
|
+
//
|
|
470
|
+
// Fallback: the regular case (downloading media for the incoming
|
|
471
|
+
// message itself, or any other BotMessage that has a real envelope
|
|
472
|
+
// in the store) — look it up by (chatId, id).
|
|
473
|
+
const embedded = msg._raw;
|
|
474
|
+
const embeddedContent = embedded?.contextInfo?.quotedMessage;
|
|
475
|
+
const raw = embeddedContent
|
|
476
|
+
? {
|
|
477
|
+
key: {
|
|
478
|
+
id: embedded.contextInfo?.stanzaId ?? msg.id,
|
|
479
|
+
remoteJid: msg.chatId,
|
|
480
|
+
fromMe: false,
|
|
481
|
+
participant: embedded.contextInfo?.participant ?? undefined,
|
|
482
|
+
},
|
|
483
|
+
message: embeddedContent,
|
|
484
|
+
}
|
|
485
|
+
: store.messages.get(msg.chatId)?.get(msg.id);
|
|
486
|
+
if (!raw)
|
|
487
|
+
return null;
|
|
488
|
+
try {
|
|
489
|
+
const buffer = await downloadMediaMessage(raw, "buffer", {}, {
|
|
490
|
+
logger: silentBaileysLogger,
|
|
491
|
+
reuploadRequest: sock.updateMediaMessage,
|
|
492
|
+
});
|
|
493
|
+
if (!buffer || !Buffer.isBuffer(buffer))
|
|
494
|
+
return null;
|
|
495
|
+
// Animated sticker → mp4 is handled by the caller (api/index.ts
|
|
496
|
+
// wa.downloadMedia) — kept at the api layer for now.
|
|
497
|
+
if (opts?.asMp4) {
|
|
498
|
+
// No animated-sticker conversion here for now (would couple to
|
|
499
|
+
// ffmpeg + node-webpmux). Caller does it.
|
|
500
|
+
}
|
|
501
|
+
return { mimetype: msg.mimetype ?? "application/octet-stream", data: buffer };
|
|
502
|
+
}
|
|
503
|
+
catch (err) {
|
|
504
|
+
logger.warn(`[waContract] downloadMedia failed: ${err.message}`);
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
// ── Rebind helpers (post-reconnect in drivers/baileys/index.ts) ────────
|
|
510
|
+
//
|
|
511
|
+
// We need to keep references to the handlers we registered on the old
|
|
512
|
+
// sock.ev so we can remove them on a fresh socket — otherwise the old
|
|
513
|
+
// socket would leak listeners and every reconnect would double the
|
|
514
|
+
// fan-out work. The trick is the handler bodies close over `emit` and
|
|
515
|
+
// `toBotMessage`, which read from module-local state that doesn't change.
|
|
516
|
+
function bindSockEventsExternal(s) {
|
|
517
|
+
const ev = s.ev;
|
|
518
|
+
const handlers = new Map();
|
|
519
|
+
function register(event, h) {
|
|
520
|
+
handlers.set(event, h);
|
|
521
|
+
ev.on(event, h);
|
|
522
|
+
}
|
|
523
|
+
register("messages.upsert", (arg) => {
|
|
524
|
+
const { messages, type } = arg;
|
|
525
|
+
emit("messages.upsert", { messages: messages.map(toBotMessage), type });
|
|
526
|
+
});
|
|
527
|
+
register("messages.update", (arg) => {
|
|
528
|
+
const updates = arg;
|
|
529
|
+
emit("messages.update", { updates });
|
|
530
|
+
});
|
|
531
|
+
register("messaging-history.set", (arg) => {
|
|
532
|
+
const { chats, contacts, messages } = arg;
|
|
533
|
+
emit("messaging-history.set", {
|
|
534
|
+
chats: chats.map(chatSummary),
|
|
535
|
+
contacts: contacts.map(contactSummary),
|
|
536
|
+
messages: (messages ?? []).map(toBotMessage),
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
register("chats.upsert", (arg) => {
|
|
540
|
+
emit("chats.upsert", { chats: arg.map(chatSummary) });
|
|
541
|
+
});
|
|
542
|
+
register("chats.update", (arg) => {
|
|
543
|
+
emit("chats.update", { updates: arg });
|
|
544
|
+
});
|
|
545
|
+
register("contacts.upsert", (arg) => {
|
|
546
|
+
emit("contacts.upsert", { contacts: arg.map(contactSummary) });
|
|
547
|
+
});
|
|
548
|
+
register("contacts.update", (arg) => {
|
|
549
|
+
emit("contacts.update", { updates: arg.map(contactSummary) });
|
|
550
|
+
});
|
|
551
|
+
register("group-participants.update", (arg) => {
|
|
552
|
+
const { id, participants } = arg;
|
|
553
|
+
emit("group-participants.update", { id, participants });
|
|
554
|
+
});
|
|
555
|
+
register("groups.update", (arg) => {
|
|
556
|
+
emit("groups.update", { updates: arg });
|
|
557
|
+
});
|
|
558
|
+
register("connection.update", (arg) => {
|
|
559
|
+
const { connection, lastDisconnect } = arg;
|
|
560
|
+
emit("connection.update", {
|
|
561
|
+
connection,
|
|
562
|
+
lastDisconnect: lastDisconnect?.error ? { statusCode: lastDisconnect.error.output?.statusCode } : undefined,
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
boundHandlers.set(s, handlers);
|
|
566
|
+
}
|
|
567
|
+
function unbindSockEvents(s) {
|
|
568
|
+
const handlers = boundHandlers.get(s);
|
|
569
|
+
if (!handlers)
|
|
570
|
+
return;
|
|
571
|
+
const ev = s.ev;
|
|
572
|
+
for (const [event, h] of handlers) {
|
|
573
|
+
ev.off(event, h);
|
|
574
|
+
}
|
|
575
|
+
boundHandlers.delete(s);
|
|
576
|
+
}
|
|
577
|
+
// ── Handle returned to drivers/baileys/index.ts ────────────────────────
|
|
578
|
+
// Hang the raw `sock` off the contract via a private symbol so
|
|
579
|
+
// `drivers/baileys/api/index.ts` (the Baileys-only plugin-context
|
|
580
|
+
// builder) can pull it back when it needs a Baileys-only operation
|
|
581
|
+
// (poll decryption, gif detection, message envelope decoding for the
|
|
582
|
+
// helpers that don't yet have a driver-neutral equivalent). The symbol
|
|
583
|
+
// is shared via Symbol.for() so the api file and the adapter can agree
|
|
584
|
+
// on the same key without a top-level import. No other module in the
|
|
585
|
+
// codebase sees this — the rest of the kernel talks only to WaContract.
|
|
586
|
+
const RAW_SOCK = Symbol.for("manybot.baileys.rawSocket");
|
|
587
|
+
contract[RAW_SOCK] = sock;
|
|
588
|
+
return {
|
|
589
|
+
contract,
|
|
590
|
+
rebind(newSock) {
|
|
591
|
+
unbindSockEvents(sock);
|
|
592
|
+
sock = newSock;
|
|
593
|
+
bindSockEventsExternal(newSock);
|
|
594
|
+
// Keep the symbol attached to the contract pointing at the new
|
|
595
|
+
// socket too — api/index.ts caches the contract object, so the
|
|
596
|
+
// symbol slot is the only way it sees the rebind.
|
|
597
|
+
contract[RAW_SOCK] = newSock;
|
|
598
|
+
},
|
|
599
|
+
unbind(oldSock) {
|
|
600
|
+
unbindSockEvents(oldSock);
|
|
601
|
+
},
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
// ── Helpers used inside the adapter above ────────────────────────────────────
|
|
605
|
+
//
|
|
606
|
+
// `buildQuotedOpts` and `toFlatKey` are defined inside the
|
|
607
|
+
// `createBaileysAdapter` closure (so they can see the `store`) — only
|
|
608
|
+
// `toSentRef` and `silentBaileysLogger` remain module-scoped helpers.
|
|
609
|
+
function toSentRef(raw, fallbackChatId) {
|
|
610
|
+
const r = raw;
|
|
611
|
+
return {
|
|
612
|
+
id: r?.key?.id ?? "",
|
|
613
|
+
chatId: r?.key?.remoteJid ?? fallbackChatId,
|
|
614
|
+
timestamp: Date.now(),
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* pino-shape shim for Baileys' downloadMediaMessage — see the in-source
|
|
619
|
+
* explanation in api/index.ts (same shape, kept here for the adapter).
|
|
620
|
+
*/
|
|
621
|
+
const silentBaileysLogger = {
|
|
622
|
+
level: "silent",
|
|
623
|
+
child() { return silentBaileysLogger; },
|
|
624
|
+
trace() { },
|
|
625
|
+
debug() { },
|
|
626
|
+
info() { },
|
|
627
|
+
warn(obj, msg) { logger.warn(`[baileys]`, msg ?? obj); },
|
|
628
|
+
error(obj, msg) { logger.error(`[baileys]`, msg ?? obj); },
|
|
629
|
+
};
|