@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.
Files changed (40) hide show
  1. package/README.md +15 -1
  2. package/dist/client/cache.js +1 -1
  3. package/dist/client/store.js +9 -0
  4. package/dist/config.js +230 -12
  5. package/dist/drivers/baileys/adapter.js +556 -0
  6. package/dist/drivers/{whatsapp → baileys}/api/index.js +533 -386
  7. package/dist/drivers/baileys/index.js +560 -0
  8. package/dist/drivers/{whatsapp → baileys}/loginPrompt.js +2 -0
  9. package/dist/drivers/{whatsapp → baileys}/messageHandler.js +46 -16
  10. package/dist/drivers/{whatsapp → baileys}/sdk/baileysSock.js +0 -29
  11. package/dist/drivers/jid.js +31 -0
  12. package/dist/drivers/types.js +14 -0
  13. package/dist/drivers/whatsmeow/client.js +203 -0
  14. package/dist/drivers/whatsmeow/index.js +79 -0
  15. package/dist/drivers/whatsmeow/installer.js +70 -0
  16. package/dist/drivers/whatsmeow/supervisor.js +309 -0
  17. package/dist/drivers/whatsmeow/whatsmeow.proto +64 -0
  18. package/dist/i18n/index.js +8 -12
  19. package/dist/kernel/alerts.js +190 -0
  20. package/dist/kernel/contactAutoSave.js +200 -0
  21. package/dist/kernel/driverManager.js +117 -0
  22. package/dist/kernel/pluginApi.js +25 -7
  23. package/dist/kernel/pluginLoader.js +12 -12
  24. package/dist/kernel/sendFallbackGuard.js +173 -0
  25. package/dist/kernel/sendGuard.js +143 -33
  26. package/dist/kernel/statusServer.js +39 -0
  27. package/dist/kernel/updateCheck.js +88 -0
  28. package/dist/kernel/waContract.js +16 -0
  29. package/dist/locales/en.json +15 -1
  30. package/dist/locales/es.json +15 -1
  31. package/dist/locales/pt.json +15 -1
  32. package/dist/main.js +77 -5
  33. package/dist/types.js +18 -11
  34. package/package.json +6 -8
  35. package/dist/core/adapter.js +0 -12
  36. package/dist/core/capabilities.js +0 -16
  37. package/dist/core/types.js +0 -6
  38. package/dist/drivers/index.js +0 -14
  39. package/dist/drivers/whatsapp/adapter.js +0 -7
  40. package/dist/drivers/whatsapp/index.js +0 -382
@@ -1,5 +1,5 @@
1
1
  /**
2
- * drivers/whatsapp/messageHandler.ts
2
+ * drivers/baileys/messageHandler.ts
3
3
  *
4
4
  * WhatsApp message pipeline.
5
5
  * Moved from kernel/messageHandler.ts to keep all WhatsApp logic together.
@@ -11,11 +11,13 @@
11
11
  *
12
12
  * Each plugin decides whether to act or ignore.
13
13
  */
14
- import { CHATS } from "#config";
15
- import { buildApi, buildChatFromMsg } from "./api/index.js";
14
+ import { CHATS, EXCLUDE_CHATS } from "#config";
15
+ import { buildApi, buildChatFromMsg, buildMessageContext } from "./api/index.js";
16
16
  import { pluginRegistry } from "#kernel/pluginLoader.js";
17
17
  import { runPlugin } from "#kernel/pluginGuard.js";
18
- import { normalizeJid, toPresenceCapable } from "./sdk/baileysSock.js";
18
+ import { acquireChatSlot } from "#sendguard";
19
+ import { trackIncomingForContactSave } from "#kernel/contactAutoSave.js";
20
+ import { normalizeJid } from "#drivers/jid.js";
19
21
  const INCOMING_DEBOUNCE_MS = 0;
20
22
  const lastProcessedAt = new Map();
21
23
  // ── Dedup of already-processed messages ────────────────────────────────────
@@ -40,21 +42,34 @@ function alreadyProcessed(id) {
40
42
  return false;
41
43
  }
42
44
  /**
43
- * @param {WAProtoMsg} msg - raw Baileys message
44
- * @param {WASocket} sock
45
- * @param {WAStore} store
45
+ * @param {BotMessage} msg - driver-neutral incoming message envelope
46
+ * @param {WaContract} contract - driver-neutral contract (replaces WASocket)
47
+ * @param {BotStore} store - in-memory store
46
48
  */
47
- export async function handleMessage(msg, sock, store) {
48
- const rawJid = msg.key.remoteJid ?? "";
49
- const jid = normalizeJid(rawJid);
49
+ export async function handleMessage(msg, contract, store) {
50
+ const rawJid = msg.chatId;
51
+ const jid = normalizeJid(store.resolveJid(rawJid));
50
52
  if (CHATS.length > 0 && !CHATS.includes(jid)) {
51
53
  return;
52
54
  }
53
- if (alreadyProcessed(msg.key.id)) {
55
+ if (EXCLUDE_CHATS.includes(jid)) {
56
+ return;
57
+ }
58
+ if (alreadyProcessed(msg.id)) {
54
59
  return;
55
60
  }
56
61
  // Mark as read/delivered to reduce the chance WhatsApp resends it
57
- sock.readMessages?.([msg.key]).catch(() => { });
62
+ // (`msg.quotedKey`/`fromLid`/`fromPn` carry the LID/PN parts so the
63
+ // contract can reconstruct a proper key on each driver).
64
+ const rawKey = msg.id ? {
65
+ id: msg.id,
66
+ remoteJid: msg.chatId,
67
+ fromMe: false,
68
+ participant: msg.fromPn ?? msg.fromLid ?? undefined,
69
+ } : undefined;
70
+ if (rawKey) {
71
+ contract.readMessages([rawKey]).catch(() => { });
72
+ }
58
73
  // Debounce rapid bursts per chat
59
74
  if (INCOMING_DEBOUNCE_MS > 0) {
60
75
  const now = Date.now();
@@ -67,12 +82,27 @@ export async function handleMessage(msg, sock, store) {
67
82
  lastProcessedAt.set(jid, Date.now());
68
83
  }
69
84
  // Build a WAChat adapter from the message metadata
70
- const chat = await buildChatFromMsg(msg, store, sock);
85
+ const chat = await buildChatFromMsg(msg, store, contract);
86
+ // Gradual contact-saving (best-effort, never blocks message handling)
87
+ const msgCtx = buildMessageContext(msg, contract, store);
88
+ const isGroup = jid.endsWith("@g.us");
89
+ trackIncomingForContactSave(contract, msg, msgCtx.sender, isGroup, msgCtx.hasPrefix)
90
+ .catch(() => { });
91
+ // Caps how many chats get answered at the same time — see SECURITY_LEVEL.
92
+ const releaseChatSlot = await acquireChatSlot(jid);
93
+ try {
94
+ await runPluginsForMessage(msg, chat, contract, store, rawJid);
95
+ }
96
+ finally {
97
+ releaseChatSlot();
98
+ }
99
+ }
100
+ async function runPluginsForMessage(msg, chat, contract, store, rawJid) {
71
101
  for (const plugin of pluginRegistry.values()) {
72
102
  const ctx = buildApi({
73
103
  msg,
74
104
  chat,
75
- sock,
105
+ contract,
76
106
  store,
77
107
  pluginRegistry,
78
108
  pluginName: plugin.name,
@@ -83,7 +113,7 @@ export async function handleMessage(msg, sock, store) {
83
113
  if (useTyping) {
84
114
  // Refresh presence every 4s so WhatsApp doesn't auto-clear it
85
115
  typingInterval = setInterval(() => {
86
- toPresenceCapable(sock).setPresence(rawJid, "composing").catch(() => { });
116
+ contract.sendPresenceUpdate("composing", rawJid).catch(() => { });
87
117
  }, 4000);
88
118
  }
89
119
  try {
@@ -92,7 +122,7 @@ export async function handleMessage(msg, sock, store) {
92
122
  finally {
93
123
  if (useTyping) {
94
124
  clearInterval(typingInterval);
95
- toPresenceCapable(sock).setPresence(rawJid, "paused").catch(() => { });
125
+ contract.sendPresenceUpdate("paused", rawJid).catch(() => { });
96
126
  }
97
127
  }
98
128
  }
@@ -13,7 +13,6 @@ import { resolveLoginMethod } from "../loginPrompt.js";
13
13
  import { logger } from "#logger";
14
14
  import { t } from "#i18n";
15
15
  import { createStore } from "#client/store.js";
16
- import { CapabilitySet } from "#core/capabilities.js";
17
16
  import pino from "pino";
18
17
  // ── Auth path ─────────────────────────────────────────────────────────────────
19
18
  // Baileys' sock.ev wraps an internal EventEmitter that no longer exposes
@@ -122,31 +121,3 @@ export async function createSocket(authDirName = CLIENT_ID) {
122
121
  }
123
122
  return { sock, store };
124
123
  }
125
- /**
126
- * Normalize a Baileys JID to the @c.us format used in ManyBot configs.
127
- * Groups (@g.us) and broadcasts are passed through unchanged.
128
- *
129
- * @param {string} jid
130
- * @returns {string}
131
- */
132
- export function normalizeJid(jid) {
133
- if (!jid)
134
- return jid;
135
- return jid
136
- .replace(/@s\.whatsapp\.net$/, "@c.us")
137
- .replace(/:\d+@/, "@");
138
- }
139
- /**
140
- * Minimal PresenceCapable view over a raw socket. Transitional shim used
141
- * by kernel code that still works with a raw sock instead of a full
142
- * PlatformAdapter — goes away once that code is migrated.
143
- *
144
- * @param {WASocket} sock
145
- * @returns {PresenceCapable}
146
- */
147
- export function toPresenceCapable(sock) {
148
- return {
149
- capabilities: new CapabilitySet(["presence"]),
150
- setPresence: (chatId, state) => sock.sendPresenceUpdate(state, chatId),
151
- };
152
- }
@@ -0,0 +1,31 @@
1
+ // Neutral JID utilities used by both Baileys and WhatsMeow drivers.
2
+ /**
3
+ * Normalize a JID to the internal "@c.us" format used in ManyBot configs.
4
+ * Groups (@g.us) and broadcasts are passed through unchanged.
5
+ */
6
+ export function normalizeJid(jid) {
7
+ if (!jid)
8
+ return jid;
9
+ return jid
10
+ .replace(/@s\.whatsapp\.net$/, "@c.us")
11
+ .replace(/:\d+@/, "@");
12
+ }
13
+ /**
14
+ * Reverse of normalizeJid – convert back to the raw wire format "@s.whatsapp.net".
15
+ */
16
+ export function denormalizeJid(jid) {
17
+ return jid.replace(/@c\.us$/, "@s.whatsapp.net");
18
+ }
19
+ /**
20
+ * Convert any identifier (phone number, already‑wire JID, or framework JID) to the
21
+ * wire JID format WhatsApp expects ("...@s.whatsapp.net").
22
+ */
23
+ export function toWireJid(id) {
24
+ const trimmed = id.trim();
25
+ if (/@(s\.whatsapp\.net|lid|g\.us)$/.test(trimmed))
26
+ return trimmed;
27
+ if (trimmed.endsWith("@c.us"))
28
+ return denormalizeJid(trimmed);
29
+ const digits = trimmed.replace(/\D/g, "");
30
+ return `${digits}@s.whatsapp.net`;
31
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * src/drivers/types.ts
3
+ *
4
+ * Driver-neutral envelope types shared by every WhatsApp driver
5
+ * implementation (Baileys today, whatsmeow in a later phase). The
6
+ * full driver surface (send, react, presence, contacts, groups,
7
+ * profile, media) is declared as `WaContract` in `#kernel/waContract.js`
8
+ * — every driver implements it. This module holds only the message
9
+ * shapes that flow across the driver boundary.
10
+ *
11
+ * Plugins depend on these types through the `WaContract` re-exports,
12
+ * never on a specific driver's Baileys/grpc types.
13
+ */
14
+ export {};
@@ -0,0 +1,203 @@
1
+ import { logger } from "#logger";
2
+ import { CONFIG } from "#config";
3
+ import * as grpc from "@grpc/grpc-js";
4
+ import * as protoLoader from "@grpc/proto-loader";
5
+ import path from "path";
6
+ import { fileURLToPath } from "node:url";
7
+ /**
8
+ * Whatsmeow gRPC client implementing the WaContract interface.
9
+ *
10
+ * Phase 1 scope: only the send path (sendText) and the
11
+ * verification primitives (getHistory) are fully wired. Every other
12
+ * WaContract method throws "not implemented" — the kernel loads plugins
13
+ * only after `connection.update === "open"`, and a plugin that calls e.g.
14
+ * `groupMetadata` on a whatsmeow-primary bot will surface a clear error
15
+ * to the caller, not a silent no-op.
16
+ *
17
+ * Connects to the address defined in config (default localhost:50051).
18
+ */
19
+ class WhatsmeowClient {
20
+ name = "whatsmeow";
21
+ client; // grpc client stub
22
+ ready = false;
23
+ handlers = new Map();
24
+ /**
25
+ * Resolve the .proto path relative to this compiled module so it works
26
+ * in ESM (where __dirname doesn't exist), under `node dist/main.js`
27
+ * (proto is copied to dist/drivers/whatsmeow/whatsmeow.proto by the
28
+ * build), and under a global npm install (the proto ships alongside
29
+ * the JS in the package's `dist/` per `files` in package.json). In
30
+ * dev (`tsx src/main.ts`) the proto already lives next to this source
31
+ * file, so the same relative path resolves correctly there too.
32
+ */
33
+ resolveProtoPath() {
34
+ const here = path.dirname(fileURLToPath(import.meta.url));
35
+ return path.resolve(here, "whatsmeow.proto");
36
+ }
37
+ loadProto() {
38
+ const protoPath = this.resolveProtoPath();
39
+ const packageDef = protoLoader.loadSync(protoPath, {
40
+ keepCase: true,
41
+ longs: String,
42
+ enums: String,
43
+ defaults: true,
44
+ oneofs: true,
45
+ });
46
+ const grpcObj = grpc.loadPackageDefinition(packageDef);
47
+ return grpcObj.whatsmeow.WhatsmeowService;
48
+ }
49
+ async connect() {
50
+ const address = CONFIG.drivers.whatsmeow.grpcAddress ?? "localhost:50051";
51
+ const Service = this.loadProto();
52
+ this.client = new Service(address, grpc.credentials.createInsecure());
53
+ // Perform a health check to confirm service is ready
54
+ await new Promise((resolve, reject) => {
55
+ this.client.HealthCheck({}, (err, resp) => {
56
+ if (err)
57
+ return reject(err);
58
+ this.ready = !!resp?.ready;
59
+ if (this.ready)
60
+ resolve();
61
+ else
62
+ reject(new Error("Whatsmeow service not ready"));
63
+ });
64
+ });
65
+ logger.info("[whatsmeow] connected via gRPC");
66
+ // Open the server-streaming event subscription. Each WaEvent that
67
+ // arrives is fanned out to the local on() subscribers in the
68
+ // neutral envelope shape.
69
+ const stream = this.client.SubscribeEvents({});
70
+ stream.on("data", (raw) => {
71
+ try {
72
+ if (raw.connState) {
73
+ const state = raw.connState.state ?? "connecting";
74
+ this.dispatch("connection.update", {
75
+ connection: state === "open" ? "open" : state === "close" ? "close" : "connecting",
76
+ });
77
+ }
78
+ else if (raw.message) {
79
+ this.dispatch("messages.upsert", {
80
+ messages: [raw.message],
81
+ type: "notify",
82
+ });
83
+ }
84
+ }
85
+ catch (e) {
86
+ logger.debug(`[whatsmeow] event dispatch failed: ${e.message}`);
87
+ }
88
+ });
89
+ stream.on("error", (err) => {
90
+ logger.warn(`[whatsmeow] event stream error: ${err.message}`);
91
+ this.ready = false;
92
+ });
93
+ stream.on("end", () => {
94
+ logger.warn(`[whatsmeow] event stream ended`);
95
+ this.ready = false;
96
+ });
97
+ }
98
+ async disconnect() {
99
+ if (this.client) {
100
+ try {
101
+ await new Promise((resolve) => {
102
+ this.client.Disconnect({}, () => resolve());
103
+ });
104
+ }
105
+ catch { }
106
+ this.client.close();
107
+ }
108
+ this.ready = false;
109
+ }
110
+ isReady() {
111
+ return this.ready;
112
+ }
113
+ // ── Event fan-out ─────────────────────────────────────────────────────────
114
+ on(event, handler) {
115
+ let set = this.handlers.get(event);
116
+ if (!set) {
117
+ set = new Set();
118
+ this.handlers.set(event, set);
119
+ }
120
+ set.add(handler);
121
+ return () => set.delete(handler);
122
+ }
123
+ dispatch(event, payload) {
124
+ const set = this.handlers.get(event);
125
+ if (!set)
126
+ return;
127
+ for (const h of set) {
128
+ try {
129
+ h(payload);
130
+ }
131
+ catch (e) {
132
+ logger.debug(`[whatsmeow] handler for "${event}" threw: ${e.message}`);
133
+ }
134
+ }
135
+ }
136
+ // ── Send ───────────────────────────────────────────────────────────────────
137
+ // Only sendText is in phase-1 scope. Media fallback is
138
+ // documented in the interface but explicitly deferred.
139
+ async sendText(jid, text, opts) {
140
+ const req = {
141
+ jid,
142
+ text,
143
+ quotedId: opts?.quoted?.id ?? "",
144
+ mentions: opts?.mentions ?? [],
145
+ };
146
+ return new Promise((resolve, reject) => {
147
+ this.client.SendText(req, (err, resp) => {
148
+ if (err)
149
+ return reject(err);
150
+ resolve({ id: resp.id, chatId: resp.chatId, timestamp: Number(resp.timestamp) });
151
+ });
152
+ });
153
+ }
154
+ // ── Verification primitive ─────────────────────────────────────────────────
155
+ async getHistory(jid, opts) {
156
+ const req = { jid, limit: opts?.limit ?? 5 };
157
+ return new Promise((resolve, reject) => {
158
+ this.client.GetHistory(req, (err, resp) => {
159
+ if (err)
160
+ return reject(err);
161
+ resolve(resp.messages ?? []);
162
+ });
163
+ });
164
+ }
165
+ // ── All other WaContract methods: stubbed for now ─────────────────────────
166
+ // These throw a clear error so a plugin calling them on a whatsmeow-
167
+ // primary bot fails loudly instead of silently no-op'ing. Coverage will
168
+ // grow in later phases as the whatsmeow .proto grows.
169
+ unimplemented(method) {
170
+ throw new Error(`[whatsmeow] ${method} not implemented in whatsmeow driver yet`);
171
+ }
172
+ async resolveLid(_lid) { return null; }
173
+ async sendImage(_jid, _buffer, _opts) { this.unimplemented("sendImage"); }
174
+ async sendVideo(_jid, _buffer, _opts) { this.unimplemented("sendVideo"); }
175
+ async sendAudio(_jid, _buffer, _opts) { this.unimplemented("sendAudio"); }
176
+ async sendSticker(_jid, _buffer, _opts) { this.unimplemented("sendSticker"); }
177
+ async sendDocument(_jid, _buffer, _filename, _mimetype, _opts) { this.unimplemented("sendDocument"); }
178
+ async sendPoll(_jid, _opts) { this.unimplemented("sendPoll"); }
179
+ async react(_jid, _target, _emoji) { this.unimplemented("react"); }
180
+ async deleteMessage(_jid, _target, _forEveryone) { this.unimplemented("deleteMessage"); }
181
+ async editMessage(_jid, _target, _text) { this.unimplemented("editMessage"); }
182
+ async sendPresenceUpdate(_state, _jid) { this.unimplemented("sendPresenceUpdate"); }
183
+ async readMessages(_keys) { this.unimplemented("readMessages"); }
184
+ async onWhatsApp(_jid) { this.unimplemented("onWhatsApp"); }
185
+ async getBusinessProfile(_jid) { this.unimplemented("getBusinessProfile"); }
186
+ async profilePictureUrl(_jid) { this.unimplemented("profilePictureUrl"); }
187
+ async fetchStatus(_jid) { this.unimplemented("fetchStatus"); }
188
+ async updateBlockStatus(_jid, _action) { this.unimplemented("updateBlockStatus"); }
189
+ async addOrEditContact(_jid, _info) { this.unimplemented("addOrEditContact"); }
190
+ async removeContact(_jid) { this.unimplemented("removeContact"); }
191
+ async groupMetadata(_jid) { this.unimplemented("groupMetadata"); }
192
+ async groupParticipantsUpdate(_jid, _users, _action) { this.unimplemented("groupParticipantsUpdate"); }
193
+ async groupUpdateSubject(_jid, _subject) { this.unimplemented("groupUpdateSubject"); }
194
+ async groupUpdateDescription(_jid, _description) { this.unimplemented("groupUpdateDescription"); }
195
+ async groupInviteCode(_jid) { this.unimplemented("groupInviteCode"); }
196
+ async groupRevokeInvite(_jid) { this.unimplemented("groupRevokeInvite"); }
197
+ async updateProfilePicture(_jid, _buffer) { this.unimplemented("updateProfilePicture"); }
198
+ async updateProfileName(_name) { this.unimplemented("updateProfileName"); }
199
+ async updateProfileStatus(_status) { this.unimplemented("updateProfileStatus"); }
200
+ me() { this.unimplemented("me"); }
201
+ async downloadMedia(_msg, _opts) { this.unimplemented("downloadMedia"); }
202
+ }
203
+ export const whatsmeowContract = new WhatsmeowClient();
@@ -0,0 +1,79 @@
1
+ /**
2
+ * drivers/whatsmeow/index.ts
3
+ *
4
+ * Public surface of the whatsmeow driver:
5
+ * - whatsmeowContract : the raw contract (test-only / fallback path)
6
+ * - wrapWithSupervisor(...) : returns a contract whose lifecycle
7
+ * methods (connect / disconnect / isReady)
8
+ * are gated on the supervisor state
9
+ *
10
+ * The supervisor is the lifecycle authority for the subprocess; this
11
+ * proxy exists so the DriverManager sees one contract whose `isReady()`
12
+ * never lies — true only when both the gRPC client is up AND the
13
+ * subprocess has answered HealthCheck{ready:true}.
14
+ */
15
+ import { whatsmeowContract } from "./client.js";
16
+ export { whatsmeowContract };
17
+ export { startWhatsmeowSupervisor } from "./supervisor.js";
18
+ /**
19
+ * Wraps a raw whatsmeow contract so its lifecycle methods delegate to
20
+ * the supervisor. Send/event methods still pass through unchanged —
21
+ * the contract already knows how to talk gRPC; the supervisor only
22
+ * owns "is it safe to use right now?".
23
+ */
24
+ export function wrapWithSupervisor(contract, supervisor) {
25
+ return {
26
+ name: contract.name,
27
+ async connect() {
28
+ await supervisor.whenReady();
29
+ await contract.connect();
30
+ },
31
+ async disconnect() {
32
+ // Try to stop the subprocess too — disconnecting the contract
33
+ // alone would leave the Go process running until the bot shuts
34
+ // down. Idempotent; safe to call multiple times.
35
+ await Promise.allSettled([
36
+ contract.disconnect(),
37
+ supervisor.shutdown(),
38
+ ]);
39
+ },
40
+ isReady: () => supervisor.isReady() && contract.isReady(),
41
+ on: (...args) => contract.on(...args),
42
+ resolveLid: contract.resolveLid
43
+ ? (lid) => contract.resolveLid(lid)
44
+ : undefined,
45
+ sendText: (...args) => contract.sendText(...args),
46
+ sendImage: (...args) => contract.sendImage(...args),
47
+ sendVideo: (...args) => contract.sendVideo(...args),
48
+ sendAudio: (...args) => contract.sendAudio(...args),
49
+ sendSticker: (...args) => contract.sendSticker(...args),
50
+ sendDocument: (...args) => contract.sendDocument(...args),
51
+ sendPoll: (...args) => contract.sendPoll(...args),
52
+ react: (...args) => contract.react(...args),
53
+ deleteMessage: (...args) => contract.deleteMessage(...args),
54
+ editMessage: (...args) => contract.editMessage(...args),
55
+ sendPresenceUpdate: (...args) => contract.sendPresenceUpdate(...args),
56
+ readMessages: (...args) => contract.readMessages(...args),
57
+ onWhatsApp: (...args) => contract.onWhatsApp(...args),
58
+ getBusinessProfile: (...args) => contract.getBusinessProfile(...args),
59
+ profilePictureUrl: (...args) => contract.profilePictureUrl(...args),
60
+ fetchStatus: (...args) => contract.fetchStatus(...args),
61
+ updateBlockStatus: (...args) => contract.updateBlockStatus(...args),
62
+ addOrEditContact: (...args) => contract.addOrEditContact(...args),
63
+ removeContact: (...args) => contract.removeContact(...args),
64
+ groupMetadata: (...args) => contract.groupMetadata(...args),
65
+ groupParticipantsUpdate: (...args) => contract.groupParticipantsUpdate(...args),
66
+ groupUpdateSubject: (...args) => contract.groupUpdateSubject(...args),
67
+ groupUpdateDescription: (...args) => contract.groupUpdateDescription(...args),
68
+ groupInviteCode: (...args) => contract.groupInviteCode(...args),
69
+ groupRevokeInvite: (...args) => contract.groupRevokeInvite(...args),
70
+ updateProfilePicture: (...args) => contract.updateProfilePicture(...args),
71
+ updateProfileName: (...args) => contract.updateProfileName(...args),
72
+ updateProfileStatus: (...args) => contract.updateProfileStatus(...args),
73
+ me: () => contract.me(),
74
+ downloadMedia: (...args) => contract.downloadMedia(...args),
75
+ getHistory: contract.getHistory
76
+ ? (...args) => contract.getHistory(...args)
77
+ : undefined,
78
+ };
79
+ }
@@ -0,0 +1,70 @@
1
+ import { mkdirSync, writeFileSync, chmodSync } from "node:fs";
2
+ import path from "node:path";
3
+ import * as clack from "@clack/prompts";
4
+ import { persistConfigValue } from "#config";
5
+ import { t } from "#i18n";
6
+ const SUPPORTED = [
7
+ { os: "linux", arch: "x64", name: "whatsmeow-service-linux-x64" },
8
+ { os: "linux", arch: "arm64", name: "whatsmeow-service-linux-arm64" },
9
+ { os: "win32", arch: "x64", name: "whatsmeow-service-windows-x64.exe" },
10
+ ];
11
+ function detectTarget() {
12
+ return SUPPORTED.find((t) => t.os === process.platform && t.arch === process.arch) ?? null;
13
+ }
14
+ function str(val) {
15
+ return typeof val === "string" ? val : String(val);
16
+ }
17
+ async function fetchLatestTag() {
18
+ const res = await fetch("https://api.github.com/repos/many-bot/manybot/releases/latest");
19
+ if (!res.ok)
20
+ throw new Error(`GitHub API: ${res.status}`);
21
+ const data = await res.json();
22
+ return data.tag_name ?? "v5.6.1";
23
+ }
24
+ function binaryDir() {
25
+ return path.resolve(process.cwd(), "whatsmeow-service", "bin");
26
+ }
27
+ export async function promptWhatsmeowInstall() {
28
+ const target = detectTarget();
29
+ if (!target) {
30
+ clack.log.warn(str(t("whatsmeow.unsupportedArch", { os: process.platform, arch: process.arch })));
31
+ return;
32
+ }
33
+ const choice = await clack.confirm({
34
+ message: str(t("whatsmeow.installPrompt")),
35
+ initialValue: false,
36
+ });
37
+ if (clack.isCancel(choice) || !choice)
38
+ return;
39
+ const spin = clack.spinner();
40
+ spin.start(str(t("whatsmeow.fetchingTag")));
41
+ let tag;
42
+ try {
43
+ tag = await fetchLatestTag();
44
+ }
45
+ catch {
46
+ spin.stop(str(t("whatsmeow.fetchFailed")));
47
+ return;
48
+ }
49
+ const url = `https://github.com/many-bot/manybot/releases/download/${tag}/${target.name}`;
50
+ spin.message(str(t("whatsmeow.downloading", { url })));
51
+ let res;
52
+ try {
53
+ res = await fetch(url);
54
+ if (!res.ok)
55
+ throw new Error(`${res.status}`);
56
+ }
57
+ catch (e) {
58
+ spin.stop(str(t("whatsmeow.downloadFailed", { reason: e.message })));
59
+ return;
60
+ }
61
+ const buffer = Buffer.from(await res.arrayBuffer());
62
+ const dir = binaryDir();
63
+ mkdirSync(dir, { recursive: true });
64
+ const outPath = path.join(dir, "whatsmeow-service");
65
+ writeFileSync(outPath, buffer);
66
+ chmodSync(outPath, 0o755);
67
+ await persistConfigValue("driver_whatsmeow_enabled", "true");
68
+ spin.stop(str(t("whatsmeow.installed", { path: outPath })));
69
+ clack.note(str(t("whatsmeow.restartNotice")), str(t("whatsmeow.installTitle")));
70
+ }