@manybot/manybot 5.5.4 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +556 -0
- package/dist/drivers/{whatsapp → baileys}/api/index.js +533 -386
- package/dist/drivers/baileys/index.js +560 -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 +0 -29
- package/dist/drivers/jid.js +31 -0
- package/dist/drivers/types.js +14 -0
- package/dist/drivers/whatsmeow/client.js +203 -0
- package/dist/drivers/whatsmeow/index.js +79 -0
- package/dist/drivers/whatsmeow/installer.js +70 -0
- package/dist/drivers/whatsmeow/supervisor.js +309 -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 +173 -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 +15 -1
- package/dist/locales/es.json +15 -1
- package/dist/locales/pt.json +15 -1
- package/dist/main.js +77 -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,556 @@
|
|
|
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
|
+
export function createBaileysAdapter(initial) {
|
|
29
|
+
// mutable so rebind() can swap it; closure-scoped so the contract below
|
|
30
|
+
// always sees the latest sock.
|
|
31
|
+
let sock = initial.sock;
|
|
32
|
+
const store = initial.store;
|
|
33
|
+
// ── Adapter-local helpers ────────────────────────────────────────────────
|
|
34
|
+
/** Compute sha1-hex of a normalized buffer or string. */
|
|
35
|
+
function sha1(input) {
|
|
36
|
+
const hash = createHash("sha1");
|
|
37
|
+
if (typeof input === "string") {
|
|
38
|
+
hash.update(input, "utf8");
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
hash.update(input);
|
|
42
|
+
}
|
|
43
|
+
return hash.digest("hex");
|
|
44
|
+
}
|
|
45
|
+
/** Translate a Baileys WAMessage into the neutral BotMessage envelope. */
|
|
46
|
+
function toBotMessage(msg) {
|
|
47
|
+
const m = normalizeMessageContent(msg.message) ?? undefined;
|
|
48
|
+
let type = "other";
|
|
49
|
+
let body = "";
|
|
50
|
+
let mimetype;
|
|
51
|
+
if (m?.conversation) {
|
|
52
|
+
type = "text";
|
|
53
|
+
body = m.conversation;
|
|
54
|
+
}
|
|
55
|
+
else if (m?.extendedTextMessage?.text) {
|
|
56
|
+
type = "text";
|
|
57
|
+
body = m.extendedTextMessage.text ?? "";
|
|
58
|
+
}
|
|
59
|
+
else if (m?.imageMessage) {
|
|
60
|
+
type = "image";
|
|
61
|
+
body = m.imageMessage.caption ?? "";
|
|
62
|
+
mimetype = m.imageMessage.mimetype ?? undefined;
|
|
63
|
+
}
|
|
64
|
+
else if (m?.videoMessage) {
|
|
65
|
+
type = "video";
|
|
66
|
+
body = m.videoMessage.caption ?? "";
|
|
67
|
+
mimetype = m.videoMessage.mimetype ?? undefined;
|
|
68
|
+
}
|
|
69
|
+
else if (m?.audioMessage) {
|
|
70
|
+
type = "audio";
|
|
71
|
+
mimetype = m.audioMessage.mimetype ?? undefined;
|
|
72
|
+
}
|
|
73
|
+
else if (m?.documentMessage) {
|
|
74
|
+
type = "document";
|
|
75
|
+
body = m.documentMessage.caption ?? "";
|
|
76
|
+
mimetype = m.documentMessage.mimetype ?? undefined;
|
|
77
|
+
}
|
|
78
|
+
else if (m?.stickerMessage) {
|
|
79
|
+
type = "sticker";
|
|
80
|
+
mimetype = m.stickerMessage.mimetype ?? undefined;
|
|
81
|
+
}
|
|
82
|
+
const key = msg.key;
|
|
83
|
+
const contextInfo = m?.extendedTextMessage?.contextInfo ??
|
|
84
|
+
m?.imageMessage?.contextInfo ??
|
|
85
|
+
m?.videoMessage?.contextInfo ??
|
|
86
|
+
m?.audioMessage?.contextInfo ??
|
|
87
|
+
m?.documentMessage?.contextInfo ??
|
|
88
|
+
undefined;
|
|
89
|
+
return {
|
|
90
|
+
id: msg.key.id ?? "",
|
|
91
|
+
chatId: msg.key.remoteJid ?? "",
|
|
92
|
+
fromMe: !!msg.key.fromMe,
|
|
93
|
+
type,
|
|
94
|
+
contentHash: sha1(body.trim()),
|
|
95
|
+
timestamp: Number(msg.messageTimestamp ?? 0) * 1000,
|
|
96
|
+
body,
|
|
97
|
+
mimetype: mimetype ?? undefined,
|
|
98
|
+
pushName: msg.pushName,
|
|
99
|
+
mentionedJid: contextInfo?.mentionedJid ?? undefined,
|
|
100
|
+
quotedKey: contextInfo?.stanzaId ? {
|
|
101
|
+
id: contextInfo.stanzaId,
|
|
102
|
+
remoteJid: msg.key.remoteJid ?? undefined,
|
|
103
|
+
fromMe: false,
|
|
104
|
+
participant: contextInfo.participant ?? undefined,
|
|
105
|
+
} : undefined,
|
|
106
|
+
fromLid: key.participantAlt,
|
|
107
|
+
fromPn: key.participant,
|
|
108
|
+
participantAlt: key.participantAlt,
|
|
109
|
+
remoteJidAlt: key.remoteJidAlt,
|
|
110
|
+
// Poll-decryption key lives here for poll vote decryption downstream.
|
|
111
|
+
_raw: {
|
|
112
|
+
pollEncKeyRaw: m?.messageContextInfo?.messageSecret ?? undefined,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** Plain summary of a Baileys Chat. */
|
|
117
|
+
function chatSummary(c) {
|
|
118
|
+
return { id: c.id, name: c.name };
|
|
119
|
+
}
|
|
120
|
+
/** Plain summary of a Baileys Contact. */
|
|
121
|
+
function contactSummary(c) {
|
|
122
|
+
return {
|
|
123
|
+
id: c.id,
|
|
124
|
+
name: c.name,
|
|
125
|
+
notify: c.notify,
|
|
126
|
+
verifiedName: c.verifiedName,
|
|
127
|
+
lid: c.lid,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const subscribers = new Map();
|
|
131
|
+
// Tracks the handlers each socket has registered so we can remove them on a
|
|
132
|
+
// fresh socket after reconnect. Declared up here because the rebind helpers
|
|
133
|
+
// below close over it and the first `bindSockEventsExternal(sock)` call
|
|
134
|
+
// happens before the later declaration site would have executed — accessing
|
|
135
|
+
// it there would hit the temporal dead zone.
|
|
136
|
+
const boundHandlers = new WeakMap();
|
|
137
|
+
function emit(event, payload) {
|
|
138
|
+
const set = subscribers.get(event);
|
|
139
|
+
if (!set)
|
|
140
|
+
return;
|
|
141
|
+
for (const h of set) {
|
|
142
|
+
try {
|
|
143
|
+
h(payload);
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
logger.debug(`[waContract] handler for "${event}" threw: ${e.message}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
bindSockEventsExternal(sock);
|
|
151
|
+
// ── The contract ────────────────────────────────────────────────────────
|
|
152
|
+
const contract = {
|
|
153
|
+
name: "baileys",
|
|
154
|
+
// ── lifecycle ─────────────────────────────────────────────────────────
|
|
155
|
+
async connect() { },
|
|
156
|
+
async disconnect() { },
|
|
157
|
+
isReady() { return false; /* drivers/baileys/index.ts overrides this via the WaContract lifecycle */ },
|
|
158
|
+
async resolveLid(lid) {
|
|
159
|
+
try {
|
|
160
|
+
const repo = sock.signalRepository;
|
|
161
|
+
const fn = repo?.lidMapping?.getPNForLID;
|
|
162
|
+
if (typeof fn === "function") {
|
|
163
|
+
const pn = await fn(lid);
|
|
164
|
+
return pn ?? null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
logger.debug(`[waContract] resolveLid cross-check failed for "${lid}": ${err.message}`);
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
},
|
|
172
|
+
// ── event subscription ────────────────────────────────────────────────
|
|
173
|
+
on(event, handler) {
|
|
174
|
+
let set = subscribers.get(event);
|
|
175
|
+
if (!set) {
|
|
176
|
+
set = new Set();
|
|
177
|
+
subscribers.set(event, set);
|
|
178
|
+
}
|
|
179
|
+
set.add(handler);
|
|
180
|
+
return () => set.delete(handler);
|
|
181
|
+
},
|
|
182
|
+
// ── send ───────────────────────────────────────────────────────────────
|
|
183
|
+
async sendText(jid, text, opts) {
|
|
184
|
+
const content = { text };
|
|
185
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
186
|
+
const sendOpts = buildQuotedOpts(opts?.quoted);
|
|
187
|
+
if (opts?.mentions?.length)
|
|
188
|
+
content.mentions = opts.mentions;
|
|
189
|
+
const ref = await sock.sendMessage(jid, content, sendOpts);
|
|
190
|
+
return toSentRef(ref, jid);
|
|
191
|
+
},
|
|
192
|
+
async sendImage(jid, buffer, opts) {
|
|
193
|
+
const content = { image: buffer };
|
|
194
|
+
if (opts?.caption)
|
|
195
|
+
content.caption = opts.caption;
|
|
196
|
+
if (opts?.viewOnce)
|
|
197
|
+
content.viewOnce = true;
|
|
198
|
+
if (opts?.mentions?.length)
|
|
199
|
+
content.mentions = opts.mentions;
|
|
200
|
+
const ref = await sock.sendMessage(jid, content, buildQuotedOpts(opts?.quoted));
|
|
201
|
+
return toSentRef(ref, jid);
|
|
202
|
+
},
|
|
203
|
+
async sendVideo(jid, buffer, opts) {
|
|
204
|
+
const content = { video: buffer };
|
|
205
|
+
if (opts?.caption)
|
|
206
|
+
content.caption = opts.caption;
|
|
207
|
+
if (opts?.viewOnce)
|
|
208
|
+
content.viewOnce = true;
|
|
209
|
+
if (opts?.gifPlayback)
|
|
210
|
+
content.gifPlayback = true;
|
|
211
|
+
if (opts?.mentions?.length)
|
|
212
|
+
content.mentions = opts.mentions;
|
|
213
|
+
const ref = await sock.sendMessage(jid, content, buildQuotedOpts(opts?.quoted));
|
|
214
|
+
return toSentRef(ref, jid);
|
|
215
|
+
},
|
|
216
|
+
async sendAudio(jid, buffer, opts) {
|
|
217
|
+
const content = { audio: buffer };
|
|
218
|
+
content.mimetype = opts?.mimetype ?? "audio/mp4";
|
|
219
|
+
if (opts?.ptt)
|
|
220
|
+
content.ptt = true;
|
|
221
|
+
if (opts?.viewOnce)
|
|
222
|
+
content.viewOnce = true;
|
|
223
|
+
const ref = await sock.sendMessage(jid, content, buildQuotedOpts(opts?.quoted));
|
|
224
|
+
return toSentRef(ref, jid);
|
|
225
|
+
},
|
|
226
|
+
async sendSticker(jid, buffer, opts) {
|
|
227
|
+
const ref = await sock.sendMessage(jid, { sticker: buffer }, buildQuotedOpts(opts?.quoted));
|
|
228
|
+
return toSentRef(ref, jid);
|
|
229
|
+
},
|
|
230
|
+
async sendDocument(jid, buffer, filename, mimetype, opts) {
|
|
231
|
+
const ref = await sock.sendMessage(jid, { document: buffer, mimetype, fileName: filename }, buildQuotedOpts(opts?.quoted));
|
|
232
|
+
return toSentRef(ref, jid);
|
|
233
|
+
},
|
|
234
|
+
async sendPoll(jid, opts) {
|
|
235
|
+
const poll = { name: opts.name, values: opts.values, selectableCount: opts.selectableCount ?? 1 };
|
|
236
|
+
const ref = await sock.sendMessage(jid, { poll }, buildQuotedOpts(opts.quoted));
|
|
237
|
+
return toSentRef(ref, jid);
|
|
238
|
+
},
|
|
239
|
+
async react(jid, target, emoji) {
|
|
240
|
+
const key = toBaileysKey(target);
|
|
241
|
+
await sock.sendMessage(jid, { react: { text: emoji, key } });
|
|
242
|
+
},
|
|
243
|
+
async deleteMessage(jid, target, forEveryone) {
|
|
244
|
+
if (!forEveryone)
|
|
245
|
+
return;
|
|
246
|
+
const key = toBaileysKey(target);
|
|
247
|
+
await sock.sendMessage(jid, { delete: key });
|
|
248
|
+
},
|
|
249
|
+
async editMessage(jid, target, text) {
|
|
250
|
+
const key = toBaileysKey(target);
|
|
251
|
+
await sock.sendMessage(jid, { text, edit: key });
|
|
252
|
+
},
|
|
253
|
+
// ── presence + read ───────────────────────────────────────────────────
|
|
254
|
+
async sendPresenceUpdate(state, jid) {
|
|
255
|
+
const baileyState = state === "composing" ? "composing"
|
|
256
|
+
: state === "recording" ? "recording"
|
|
257
|
+
: "paused";
|
|
258
|
+
await sock.sendPresenceUpdate(baileyState, jid);
|
|
259
|
+
},
|
|
260
|
+
async readMessages(keys) {
|
|
261
|
+
const baileyKeys = keys.map(toBaileysKey);
|
|
262
|
+
await sock.readMessages(baileyKeys);
|
|
263
|
+
},
|
|
264
|
+
// ── contacts ──────────────────────────────────────────────────────────
|
|
265
|
+
async onWhatsApp(jid) {
|
|
266
|
+
const fn = sock.onWhatsApp?.bind(sock);
|
|
267
|
+
if (!fn)
|
|
268
|
+
return null;
|
|
269
|
+
return await fn(jid);
|
|
270
|
+
},
|
|
271
|
+
async getBusinessProfile(jid) {
|
|
272
|
+
try {
|
|
273
|
+
const fn = sock.getBusinessProfile;
|
|
274
|
+
return await fn(jid);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
async profilePictureUrl(jid) {
|
|
281
|
+
try {
|
|
282
|
+
const url = await sock.profilePictureUrl(jid, "image");
|
|
283
|
+
return url ?? null;
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
async fetchStatus(jid) {
|
|
290
|
+
const fn = sock.fetchStatus?.bind(sock);
|
|
291
|
+
if (!fn)
|
|
292
|
+
return null;
|
|
293
|
+
try {
|
|
294
|
+
const res = await fn(jid);
|
|
295
|
+
if (Array.isArray(res)) {
|
|
296
|
+
const entry = res.find((r) => jidNormalizedUser(r.id) === jidNormalizedUser(jid)) ?? res[0];
|
|
297
|
+
return entry?.status?.status ?? null;
|
|
298
|
+
}
|
|
299
|
+
return res?.status ?? null;
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
async updateBlockStatus(jid, action) {
|
|
306
|
+
const fn = sock.updateBlockStatus;
|
|
307
|
+
await fn(jid, action);
|
|
308
|
+
},
|
|
309
|
+
async addOrEditContact(jid, info) {
|
|
310
|
+
await sock.addOrEditContact(jid, info);
|
|
311
|
+
},
|
|
312
|
+
async removeContact(jid) {
|
|
313
|
+
await sock.removeContact(jid);
|
|
314
|
+
},
|
|
315
|
+
// ── groups ─────────────────────────────────────────────────────────────
|
|
316
|
+
async groupMetadata(jid) {
|
|
317
|
+
const meta = await sock.groupMetadata(jid);
|
|
318
|
+
return {
|
|
319
|
+
subject: meta.subject,
|
|
320
|
+
participants: meta.participants.map(p => ({
|
|
321
|
+
id: jidNormalizedUser(p.id),
|
|
322
|
+
isAdmin: p.admin === "admin" || p.admin === "superadmin",
|
|
323
|
+
isSuperAdmin: p.admin === "superadmin",
|
|
324
|
+
phoneNumber: p.phoneNumber,
|
|
325
|
+
})),
|
|
326
|
+
};
|
|
327
|
+
},
|
|
328
|
+
async groupParticipantsUpdate(jid, users, action) {
|
|
329
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
330
|
+
const res = await sock.groupParticipantsUpdate(jid, users, action);
|
|
331
|
+
return res;
|
|
332
|
+
},
|
|
333
|
+
async groupUpdateSubject(jid, subject) {
|
|
334
|
+
await sock.groupUpdateSubject(jid, subject);
|
|
335
|
+
},
|
|
336
|
+
async groupUpdateDescription(jid, description) {
|
|
337
|
+
await sock.groupUpdateDescription(jid, description);
|
|
338
|
+
},
|
|
339
|
+
async groupInviteCode(jid) {
|
|
340
|
+
return await sock.groupInviteCode(jid);
|
|
341
|
+
},
|
|
342
|
+
async groupRevokeInvite(jid) {
|
|
343
|
+
return await sock.groupRevokeInvite(jid);
|
|
344
|
+
},
|
|
345
|
+
// ── profile ────────────────────────────────────────────────────────────
|
|
346
|
+
async updateProfilePicture(jid, buffer) {
|
|
347
|
+
await sock.updateProfilePicture(jid, buffer);
|
|
348
|
+
},
|
|
349
|
+
async updateProfileName(name) {
|
|
350
|
+
await sock.updateProfileName(name);
|
|
351
|
+
},
|
|
352
|
+
async updateProfileStatus(status) {
|
|
353
|
+
await sock.updateProfileStatus(status);
|
|
354
|
+
},
|
|
355
|
+
// ── me ─────────────────────────────────────────────────────────────────
|
|
356
|
+
me() {
|
|
357
|
+
const u = sock.user;
|
|
358
|
+
const id = u?.id ? jidNormalizedUser(u.id) : "";
|
|
359
|
+
return { id, lid: u?.lid };
|
|
360
|
+
},
|
|
361
|
+
// ── verification primitive ──────────────────────────────────────────
|
|
362
|
+
// sendFallbackGuard calls this right after sendText resolves to confirm
|
|
363
|
+
// the message actually landed. The Baileys in-memory store is updated
|
|
364
|
+
// synchronously by the `messages.upsert` listener (store.ts), and
|
|
365
|
+
// own-sent messages land there as soon as sock.sendMessage returns —
|
|
366
|
+
// so the first lookup is effectively free (no network round-trip).
|
|
367
|
+
async getHistory(jid, opts) {
|
|
368
|
+
const limit = opts?.limit ?? 5;
|
|
369
|
+
const chatMsgs = store.messages.get(jid);
|
|
370
|
+
if (!chatMsgs || chatMsgs.size === 0)
|
|
371
|
+
return [];
|
|
372
|
+
// Newest-first so callers can slice `limit` off the head; we sort
|
|
373
|
+
// by messageTimestamp (seconds) and fall back to insertion order
|
|
374
|
+
// for messages synced together (same timestamp) so ordering stays
|
|
375
|
+
// stable across reads.
|
|
376
|
+
const all = [...chatMsgs.values()];
|
|
377
|
+
all.sort((a, b) => {
|
|
378
|
+
const ta = Number(a.messageTimestamp ?? 0);
|
|
379
|
+
const tb = Number(b.messageTimestamp ?? 0);
|
|
380
|
+
if (ta !== tb)
|
|
381
|
+
return tb - ta;
|
|
382
|
+
return 0;
|
|
383
|
+
});
|
|
384
|
+
return all.slice(0, limit).map(toBotMessage);
|
|
385
|
+
},
|
|
386
|
+
// ── media (download) ───────────────────────────────────────────────────
|
|
387
|
+
async downloadMedia(msg, opts) {
|
|
388
|
+
// The poll-decryption raw envelope left only what's needed for that,
|
|
389
|
+
// so to downloadMediaMessage we still need the original WAMessage.
|
|
390
|
+
// Get it back from the store by id.
|
|
391
|
+
const raw = store.messages.get(msg.chatId)?.get(msg.id);
|
|
392
|
+
if (!raw)
|
|
393
|
+
return null;
|
|
394
|
+
try {
|
|
395
|
+
const buffer = await downloadMediaMessage(raw, "buffer", {}, {
|
|
396
|
+
logger: silentBaileysLogger,
|
|
397
|
+
reuploadRequest: sock.updateMediaMessage,
|
|
398
|
+
});
|
|
399
|
+
if (!buffer || !Buffer.isBuffer(buffer))
|
|
400
|
+
return null;
|
|
401
|
+
// Animated sticker → mp4 is handled by the caller (api/index.ts
|
|
402
|
+
// wa.downloadMedia) — kept at the api layer for now.
|
|
403
|
+
if (opts?.asMp4) {
|
|
404
|
+
// No animated-sticker conversion here for now (would couple to
|
|
405
|
+
// ffmpeg + node-webpmux). Caller does it.
|
|
406
|
+
}
|
|
407
|
+
return { mimetype: msg.mimetype ?? "application/octet-stream", data: buffer };
|
|
408
|
+
}
|
|
409
|
+
catch (err) {
|
|
410
|
+
logger.warn(`[waContract] downloadMedia failed: ${err.message}`);
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
// ── Rebind helpers (post-reconnect in drivers/baileys/index.ts) ────────
|
|
416
|
+
//
|
|
417
|
+
// We need to keep references to the handlers we registered on the old
|
|
418
|
+
// sock.ev so we can remove them on a fresh socket — otherwise the old
|
|
419
|
+
// socket would leak listeners and every reconnect would double the
|
|
420
|
+
// fan-out work. The trick is the handler bodies close over `emit` and
|
|
421
|
+
// `toBotMessage`, which read from module-local state that doesn't change.
|
|
422
|
+
function bindSockEventsExternal(s) {
|
|
423
|
+
const ev = s.ev;
|
|
424
|
+
const handlers = new Map();
|
|
425
|
+
function register(event, h) {
|
|
426
|
+
handlers.set(event, h);
|
|
427
|
+
ev.on(event, h);
|
|
428
|
+
}
|
|
429
|
+
register("messages.upsert", (arg) => {
|
|
430
|
+
const { messages, type } = arg;
|
|
431
|
+
emit("messages.upsert", { messages: messages.map(toBotMessage), type });
|
|
432
|
+
});
|
|
433
|
+
register("messages.update", (arg) => {
|
|
434
|
+
const updates = arg;
|
|
435
|
+
emit("messages.update", { updates });
|
|
436
|
+
});
|
|
437
|
+
register("messaging-history.set", (arg) => {
|
|
438
|
+
const { chats, contacts, messages } = arg;
|
|
439
|
+
emit("messaging-history.set", {
|
|
440
|
+
chats: chats.map(chatSummary),
|
|
441
|
+
contacts: contacts.map(contactSummary),
|
|
442
|
+
messages: (messages ?? []).map(toBotMessage),
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
register("chats.upsert", (arg) => {
|
|
446
|
+
emit("chats.upsert", { chats: arg.map(chatSummary) });
|
|
447
|
+
});
|
|
448
|
+
register("chats.update", (arg) => {
|
|
449
|
+
emit("chats.update", { updates: arg });
|
|
450
|
+
});
|
|
451
|
+
register("contacts.upsert", (arg) => {
|
|
452
|
+
emit("contacts.upsert", { contacts: arg.map(contactSummary) });
|
|
453
|
+
});
|
|
454
|
+
register("contacts.update", (arg) => {
|
|
455
|
+
emit("contacts.update", { updates: arg.map(contactSummary) });
|
|
456
|
+
});
|
|
457
|
+
register("group-participants.update", (arg) => {
|
|
458
|
+
const { id, participants } = arg;
|
|
459
|
+
emit("group-participants.update", { id, participants });
|
|
460
|
+
});
|
|
461
|
+
register("groups.update", (arg) => {
|
|
462
|
+
emit("groups.update", { updates: arg });
|
|
463
|
+
});
|
|
464
|
+
register("connection.update", (arg) => {
|
|
465
|
+
const { connection, lastDisconnect } = arg;
|
|
466
|
+
emit("connection.update", {
|
|
467
|
+
connection,
|
|
468
|
+
lastDisconnect: lastDisconnect?.error ? { statusCode: lastDisconnect.error.output?.statusCode } : undefined,
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
boundHandlers.set(s, handlers);
|
|
472
|
+
}
|
|
473
|
+
function unbindSockEvents(s) {
|
|
474
|
+
const handlers = boundHandlers.get(s);
|
|
475
|
+
if (!handlers)
|
|
476
|
+
return;
|
|
477
|
+
const ev = s.ev;
|
|
478
|
+
for (const [event, h] of handlers) {
|
|
479
|
+
ev.off(event, h);
|
|
480
|
+
}
|
|
481
|
+
boundHandlers.delete(s);
|
|
482
|
+
}
|
|
483
|
+
// ── Handle returned to drivers/baileys/index.ts ────────────────────────
|
|
484
|
+
// Hang the raw `sock` off the contract via a private symbol so
|
|
485
|
+
// `drivers/baileys/api/index.ts` (the Baileys-only plugin-context
|
|
486
|
+
// builder) can pull it back when it needs a Baileys-only operation
|
|
487
|
+
// (poll decryption, gif detection, message envelope decoding for the
|
|
488
|
+
// helpers that don't yet have a driver-neutral equivalent). The symbol
|
|
489
|
+
// is shared via Symbol.for() so the api file and the adapter can agree
|
|
490
|
+
// on the same key without a top-level import. No other module in the
|
|
491
|
+
// codebase sees this — the rest of the kernel talks only to WaContract.
|
|
492
|
+
const RAW_SOCK = Symbol.for("manybot.baileys.rawSocket");
|
|
493
|
+
contract[RAW_SOCK] = sock;
|
|
494
|
+
return {
|
|
495
|
+
contract,
|
|
496
|
+
rebind(newSock) {
|
|
497
|
+
unbindSockEvents(sock);
|
|
498
|
+
sock = newSock;
|
|
499
|
+
bindSockEventsExternal(newSock);
|
|
500
|
+
// Keep the symbol attached to the contract pointing at the new
|
|
501
|
+
// socket too — api/index.ts caches the contract object, so the
|
|
502
|
+
// symbol slot is the only way it sees the rebind.
|
|
503
|
+
contract[RAW_SOCK] = newSock;
|
|
504
|
+
},
|
|
505
|
+
unbind(oldSock) {
|
|
506
|
+
unbindSockEvents(oldSock);
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
// ── Helpers used inside the adapter above ────────────────────────────────────
|
|
511
|
+
function buildQuotedOpts(quoted) {
|
|
512
|
+
if (!quoted)
|
|
513
|
+
return undefined;
|
|
514
|
+
return { quoted: toBaileysKey(quoted) };
|
|
515
|
+
}
|
|
516
|
+
function toBaileysKey(ref) {
|
|
517
|
+
// Baileys expects `quoted` to be a message-shaped object with these
|
|
518
|
+
// fields nested under `.key` (quoted.key.id / .remoteJid / .fromMe /
|
|
519
|
+
// .participant) — it reads quoted.key.* to build contextInfo
|
|
520
|
+
// (stanzaId, participant, fromMe). Passing them flat (as this used to)
|
|
521
|
+
// means quoted.key is undefined inside Baileys, so participant/fromMe
|
|
522
|
+
// resolve to undefined and the quoted reply gets misattributed to the
|
|
523
|
+
// bot itself instead of the original sender. The quoted text still
|
|
524
|
+
// rendered before this fix because WhatsApp's client resolves the
|
|
525
|
+
// preview content locally via stanzaId — only the author attribution
|
|
526
|
+
// was broken.
|
|
527
|
+
return {
|
|
528
|
+
key: {
|
|
529
|
+
id: ref.id ?? null,
|
|
530
|
+
remoteJid: ref.remoteJid ?? "",
|
|
531
|
+
fromMe: !!ref.fromMe,
|
|
532
|
+
participant: ref.participant ?? undefined,
|
|
533
|
+
},
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
function toSentRef(raw, fallbackChatId) {
|
|
537
|
+
const r = raw;
|
|
538
|
+
return {
|
|
539
|
+
id: r?.key?.id ?? "",
|
|
540
|
+
chatId: r?.key?.remoteJid ?? fallbackChatId,
|
|
541
|
+
timestamp: Date.now(),
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* pino-shape shim for Baileys' downloadMediaMessage — see the in-source
|
|
546
|
+
* explanation in api/index.ts (same shape, kept here for the adapter).
|
|
547
|
+
*/
|
|
548
|
+
const silentBaileysLogger = {
|
|
549
|
+
level: "silent",
|
|
550
|
+
child() { return silentBaileysLogger; },
|
|
551
|
+
trace() { },
|
|
552
|
+
debug() { },
|
|
553
|
+
info() { },
|
|
554
|
+
warn(obj, msg) { logger.warn(`[baileys]`, msg ?? obj); },
|
|
555
|
+
error(obj, msg) { logger.error(`[baileys]`, msg ?? obj); },
|
|
556
|
+
};
|