@johpaz/hive 1.1.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/CONTRIBUTING.md +44 -0
- package/README.md +310 -0
- package/package.json +96 -0
- package/packages/cli/package.json +28 -0
- package/packages/cli/src/commands/agent-run.ts +168 -0
- package/packages/cli/src/commands/agents.ts +398 -0
- package/packages/cli/src/commands/chat.ts +142 -0
- package/packages/cli/src/commands/config.ts +50 -0
- package/packages/cli/src/commands/cron.ts +161 -0
- package/packages/cli/src/commands/dev.ts +95 -0
- package/packages/cli/src/commands/doctor.ts +133 -0
- package/packages/cli/src/commands/gateway.ts +443 -0
- package/packages/cli/src/commands/logs.ts +57 -0
- package/packages/cli/src/commands/mcp.ts +175 -0
- package/packages/cli/src/commands/message.ts +77 -0
- package/packages/cli/src/commands/onboard.ts +1868 -0
- package/packages/cli/src/commands/security.ts +144 -0
- package/packages/cli/src/commands/service.ts +50 -0
- package/packages/cli/src/commands/sessions.ts +116 -0
- package/packages/cli/src/commands/skills.ts +187 -0
- package/packages/cli/src/commands/update.ts +25 -0
- package/packages/cli/src/index.ts +185 -0
- package/packages/cli/src/utils/token.ts +6 -0
- package/packages/code-bridge/README.md +78 -0
- package/packages/code-bridge/package.json +18 -0
- package/packages/code-bridge/src/index.ts +95 -0
- package/packages/code-bridge/src/process-manager.ts +212 -0
- package/packages/code-bridge/src/schemas.ts +133 -0
- package/packages/core/package.json +46 -0
- package/packages/core/src/agent/agent-loop.ts +369 -0
- package/packages/core/src/agent/compaction.ts +140 -0
- package/packages/core/src/agent/context-compiler.ts +378 -0
- package/packages/core/src/agent/context-guard.ts +91 -0
- package/packages/core/src/agent/context.ts +138 -0
- package/packages/core/src/agent/conversation-store.ts +198 -0
- package/packages/core/src/agent/curator.ts +158 -0
- package/packages/core/src/agent/hooks.ts +166 -0
- package/packages/core/src/agent/index.ts +116 -0
- package/packages/core/src/agent/llm-client.ts +503 -0
- package/packages/core/src/agent/native-tools.ts +505 -0
- package/packages/core/src/agent/prompt-builder.ts +532 -0
- package/packages/core/src/agent/providers/index.ts +167 -0
- package/packages/core/src/agent/providers.ts +1 -0
- package/packages/core/src/agent/reflector.ts +170 -0
- package/packages/core/src/agent/service.ts +64 -0
- package/packages/core/src/agent/stuck-loop.ts +133 -0
- package/packages/core/src/agent/supervisor.ts +39 -0
- package/packages/core/src/agent/tracer.ts +102 -0
- package/packages/core/src/agent/workspace.ts +110 -0
- package/packages/core/src/canvas/canvas-manager.test.ts +161 -0
- package/packages/core/src/canvas/canvas-manager.ts +319 -0
- package/packages/core/src/canvas/canvas-tools.ts +420 -0
- package/packages/core/src/canvas/emitter.ts +115 -0
- package/packages/core/src/canvas/index.ts +2 -0
- package/packages/core/src/channels/base.ts +138 -0
- package/packages/core/src/channels/discord.ts +260 -0
- package/packages/core/src/channels/index.ts +7 -0
- package/packages/core/src/channels/manager.ts +383 -0
- package/packages/core/src/channels/slack.ts +287 -0
- package/packages/core/src/channels/telegram.ts +502 -0
- package/packages/core/src/channels/webchat.ts +128 -0
- package/packages/core/src/channels/whatsapp.ts +375 -0
- package/packages/core/src/config/index.ts +12 -0
- package/packages/core/src/config/loader.ts +529 -0
- package/packages/core/src/events/event-bus.ts +169 -0
- package/packages/core/src/gateway/index.ts +5 -0
- package/packages/core/src/gateway/initializer.ts +290 -0
- package/packages/core/src/gateway/lane-queue.ts +169 -0
- package/packages/core/src/gateway/resolver.ts +108 -0
- package/packages/core/src/gateway/router.ts +124 -0
- package/packages/core/src/gateway/server.ts +3317 -0
- package/packages/core/src/gateway/session.ts +95 -0
- package/packages/core/src/gateway/slash-commands.ts +192 -0
- package/packages/core/src/heartbeat/index.ts +157 -0
- package/packages/core/src/index.ts +19 -0
- package/packages/core/src/integrations/catalog.ts +286 -0
- package/packages/core/src/integrations/env.ts +64 -0
- package/packages/core/src/integrations/index.ts +2 -0
- package/packages/core/src/memory/index.ts +1 -0
- package/packages/core/src/memory/notes.ts +68 -0
- package/packages/core/src/plugins/api.ts +128 -0
- package/packages/core/src/plugins/index.ts +2 -0
- package/packages/core/src/plugins/loader.ts +365 -0
- package/packages/core/src/resilience/circuit-breaker.ts +225 -0
- package/packages/core/src/security/google-chat.ts +269 -0
- package/packages/core/src/security/index.ts +192 -0
- package/packages/core/src/security/pairing.ts +250 -0
- package/packages/core/src/security/rate-limit.ts +270 -0
- package/packages/core/src/security/signal.ts +321 -0
- package/packages/core/src/state/store.ts +312 -0
- package/packages/core/src/storage/bun-sqlite-store.ts +188 -0
- package/packages/core/src/storage/crypto.ts +101 -0
- package/packages/core/src/storage/db-context.ts +333 -0
- package/packages/core/src/storage/onboarding.ts +1087 -0
- package/packages/core/src/storage/schema.ts +541 -0
- package/packages/core/src/storage/seed.ts +571 -0
- package/packages/core/src/storage/sqlite.ts +387 -0
- package/packages/core/src/storage/usage.ts +212 -0
- package/packages/core/src/tools/bridge-events.ts +74 -0
- package/packages/core/src/tools/browser.ts +275 -0
- package/packages/core/src/tools/codebridge.ts +421 -0
- package/packages/core/src/tools/coordinator-tools.ts +179 -0
- package/packages/core/src/tools/cron.ts +611 -0
- package/packages/core/src/tools/exec.ts +140 -0
- package/packages/core/src/tools/fs.ts +364 -0
- package/packages/core/src/tools/index.ts +12 -0
- package/packages/core/src/tools/memory.ts +176 -0
- package/packages/core/src/tools/notify.ts +113 -0
- package/packages/core/src/tools/project-management.ts +376 -0
- package/packages/core/src/tools/project.ts +375 -0
- package/packages/core/src/tools/read.ts +158 -0
- package/packages/core/src/tools/web.ts +436 -0
- package/packages/core/src/tools/workspace.ts +171 -0
- package/packages/core/src/utils/benchmark.ts +80 -0
- package/packages/core/src/utils/crypto.ts +73 -0
- package/packages/core/src/utils/date.ts +42 -0
- package/packages/core/src/utils/index.ts +4 -0
- package/packages/core/src/utils/logger.ts +388 -0
- package/packages/core/src/utils/retry.ts +70 -0
- package/packages/core/src/voice/index.ts +583 -0
- package/packages/core/tsconfig.json +9 -0
- package/packages/mcp/package.json +26 -0
- package/packages/mcp/src/config.ts +13 -0
- package/packages/mcp/src/index.ts +1 -0
- package/packages/mcp/src/logger.ts +42 -0
- package/packages/mcp/src/manager.ts +434 -0
- package/packages/mcp/src/transports/index.ts +67 -0
- package/packages/mcp/src/transports/sse.ts +241 -0
- package/packages/mcp/src/transports/websocket.ts +159 -0
- package/packages/skills/package.json +21 -0
- package/packages/skills/src/bundled/agent_management/SKILL.md +24 -0
- package/packages/skills/src/bundled/browser_automation/SKILL.md +30 -0
- package/packages/skills/src/bundled/context_compact/SKILL.md +35 -0
- package/packages/skills/src/bundled/cron_manager/SKILL.md +52 -0
- package/packages/skills/src/bundled/file_manager/SKILL.md +76 -0
- package/packages/skills/src/bundled/http_client/SKILL.md +24 -0
- package/packages/skills/src/bundled/memory/SKILL.md +42 -0
- package/packages/skills/src/bundled/project_management/SKILL.md +26 -0
- package/packages/skills/src/bundled/shell/SKILL.md +43 -0
- package/packages/skills/src/bundled/system_notify/SKILL.md +52 -0
- package/packages/skills/src/bundled/voice/SKILL.md +25 -0
- package/packages/skills/src/bundled/web_search/SKILL.md +29 -0
- package/packages/skills/src/index.ts +1 -0
- package/packages/skills/src/loader.ts +282 -0
- package/packages/tools/package.json +43 -0
- package/packages/tools/src/browser/browser.test.ts +111 -0
- package/packages/tools/src/browser/index.ts +272 -0
- package/packages/tools/src/canvas/index.ts +220 -0
- package/packages/tools/src/cron/cron.test.ts +164 -0
- package/packages/tools/src/cron/index.ts +304 -0
- package/packages/tools/src/filesystem/filesystem.test.ts +240 -0
- package/packages/tools/src/filesystem/index.ts +379 -0
- package/packages/tools/src/git/index.ts +239 -0
- package/packages/tools/src/index.ts +4 -0
- package/packages/tools/src/shell/detect-env.ts +70 -0
- package/packages/tools/tsconfig.json +9 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import makeWASocket, {
|
|
2
|
+
DisconnectReason,
|
|
3
|
+
useMultiFileAuthState,
|
|
4
|
+
type WASocket,
|
|
5
|
+
type ConnectionState,
|
|
6
|
+
} from "@whiskeysockets/baileys";
|
|
7
|
+
import type { ChannelConfig, IncomingMessage, OutboundMessage } from "./base.ts";
|
|
8
|
+
import { BaseChannel } from "./base.ts";
|
|
9
|
+
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import { logger } from "../utils/logger.ts";
|
|
12
|
+
|
|
13
|
+
export interface WhatsAppConfig extends ChannelConfig {
|
|
14
|
+
accountId: string;
|
|
15
|
+
agentId: string;
|
|
16
|
+
reconnectMaxAttempts?: number;
|
|
17
|
+
reconnectBaseDelayMs?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface WhatsAppConnectionState {
|
|
21
|
+
status: "connecting" | "connected" | "disconnected" | "qr" | "error";
|
|
22
|
+
qrCode?: string;
|
|
23
|
+
lastConnected?: Date;
|
|
24
|
+
reconnectAttempts: number;
|
|
25
|
+
error?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class WhatsAppChannel extends BaseChannel {
|
|
29
|
+
name = "whatsapp";
|
|
30
|
+
accountId: string;
|
|
31
|
+
config: WhatsAppConfig;
|
|
32
|
+
|
|
33
|
+
private socket: WASocket | null = null;
|
|
34
|
+
private connectionState: WhatsAppConnectionState = {
|
|
35
|
+
status: "disconnected",
|
|
36
|
+
reconnectAttempts: 0,
|
|
37
|
+
};
|
|
38
|
+
private authPath: string;
|
|
39
|
+
private reconnectTimeout: Timer | null = null;
|
|
40
|
+
private log = logger.child("whatsapp");
|
|
41
|
+
private jidCache: Map<string, string> = new Map();
|
|
42
|
+
|
|
43
|
+
constructor(config: WhatsAppConfig) {
|
|
44
|
+
super();
|
|
45
|
+
this.config = config;
|
|
46
|
+
this.accountId = config.accountId;
|
|
47
|
+
this.authPath = this.getAuthPath(config.agentId, config.accountId);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private getAuthPath(agentId: string, accountId: string): string {
|
|
51
|
+
const baseDir = process.env.HOME ?? "";
|
|
52
|
+
const authDir = path.join(baseDir, ".hive", "agents", agentId, "whatsapp", accountId);
|
|
53
|
+
|
|
54
|
+
if (!existsSync(authDir)) {
|
|
55
|
+
mkdirSync(authDir, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return authDir;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async start(): Promise<void> {
|
|
62
|
+
this.running = true;
|
|
63
|
+
await this.connect();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async stop(): Promise<void> {
|
|
67
|
+
this.running = false;
|
|
68
|
+
|
|
69
|
+
if (this.reconnectTimeout) {
|
|
70
|
+
clearTimeout(this.reconnectTimeout);
|
|
71
|
+
this.reconnectTimeout = null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (this.socket) {
|
|
75
|
+
try {
|
|
76
|
+
await this.socket.end(undefined);
|
|
77
|
+
} catch {
|
|
78
|
+
// Ignore close errors
|
|
79
|
+
}
|
|
80
|
+
this.socket = null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.connectionState.status = "disconnected";
|
|
84
|
+
this.log.info("WhatsApp channel stopped");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private async connect(): Promise<void> {
|
|
88
|
+
if (!this.running) return;
|
|
89
|
+
|
|
90
|
+
this.connectionState.status = "connecting";
|
|
91
|
+
this.log.info("Connecting to WhatsApp...");
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const { state, saveCreds } = await useMultiFileAuthState(this.authPath);
|
|
95
|
+
|
|
96
|
+
this.socket = makeWASocket({
|
|
97
|
+
auth: state,
|
|
98
|
+
printQRInTerminal: false,
|
|
99
|
+
logger: this.createBaileysLogger(),
|
|
100
|
+
getMessage: async () => ({ conversation: "" }),
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
this.socket.ev.on("connection.update", async (update) => {
|
|
104
|
+
await this.handleConnectionUpdate(update, saveCreds);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
this.socket.ev.on("messages.upsert", async (update) => {
|
|
108
|
+
await this.handleMessages(update);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
this.socket.ev.on("creds.update", saveCreds);
|
|
112
|
+
|
|
113
|
+
} catch (error) {
|
|
114
|
+
this.connectionState.status = "error";
|
|
115
|
+
this.connectionState.error = (error as Error).message;
|
|
116
|
+
this.log.error(`WhatsApp connection error: ${(error as Error).message}`);
|
|
117
|
+
this.scheduleReconnect();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private createBaileysLogger(): undefined {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private async handleConnectionUpdate(
|
|
126
|
+
update: Partial<ConnectionState>,
|
|
127
|
+
saveCreds: () => Promise<void>
|
|
128
|
+
): Promise<void> {
|
|
129
|
+
const { connection, lastDisconnect, qr } = update;
|
|
130
|
+
|
|
131
|
+
if (qr) {
|
|
132
|
+
this.connectionState.status = "qr";
|
|
133
|
+
this.connectionState.qrCode = qr;
|
|
134
|
+
this.printQR(qr);
|
|
135
|
+
this.log.info("Scan the QR code above with WhatsApp");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (connection === "close") {
|
|
139
|
+
const statusCode = (lastDisconnect?.error as { output?: { statusCode: number } })?.output?.statusCode;
|
|
140
|
+
const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
|
|
141
|
+
|
|
142
|
+
this.connectionState.status = "disconnected";
|
|
143
|
+
this.log.warn(`WhatsApp disconnected: ${statusCode}`);
|
|
144
|
+
|
|
145
|
+
if (statusCode === DisconnectReason.loggedOut) {
|
|
146
|
+
this.log.error("WhatsApp logged out - session invalidated. Need to re-scan QR.");
|
|
147
|
+
rmSync(this.authPath, { recursive: true, force: true });
|
|
148
|
+
mkdirSync(this.authPath, { recursive: true });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (shouldReconnect && this.running) {
|
|
152
|
+
this.scheduleReconnect();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (connection === "open") {
|
|
157
|
+
this.connectionState.status = "connected";
|
|
158
|
+
this.connectionState.lastConnected = new Date();
|
|
159
|
+
this.connectionState.reconnectAttempts = 0;
|
|
160
|
+
this.connectionState.qrCode = undefined;
|
|
161
|
+
void saveCreds();
|
|
162
|
+
this.log.info("WhatsApp connected successfully");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private printQR(qr: string): void {
|
|
167
|
+
this.log.info("\n" + "=".repeat(50));
|
|
168
|
+
this.log.info(" WHATSAPP QR CODE - Scan with your phone");
|
|
169
|
+
this.log.info("=".repeat(50) + "\n");
|
|
170
|
+
|
|
171
|
+
const qrcode = require("qrcode-terminal");
|
|
172
|
+
qrcode.generate(qr, { small: false }, (qrString: string) => {
|
|
173
|
+
this.log.info(qrString);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
this.log.info("\n" + "=".repeat(50));
|
|
177
|
+
this.log.info(" Open WhatsApp > Settings > Linked Devices");
|
|
178
|
+
this.log.info("=".repeat(50) + "\n");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private async handleMessages(update: { messages: unknown[]; type: string }): Promise<void> {
|
|
182
|
+
if (update.type !== "notify") return;
|
|
183
|
+
|
|
184
|
+
for (const msg of update.messages) {
|
|
185
|
+
const typedMsg = msg as {
|
|
186
|
+
key: { fromMe?: boolean; remoteJid?: string; id?: string };
|
|
187
|
+
message?: Record<string, unknown>;
|
|
188
|
+
messageTimestamp?: number;
|
|
189
|
+
pushName?: string;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
if (typedMsg.key.fromMe) continue;
|
|
193
|
+
|
|
194
|
+
const from = typedMsg.key.remoteJid;
|
|
195
|
+
if (!from) continue;
|
|
196
|
+
|
|
197
|
+
const { content, audioMediaId } = this.extractMessageContent(typedMsg.message);
|
|
198
|
+
if (!content && !audioMediaId) continue;
|
|
199
|
+
|
|
200
|
+
const isGroup = from.includes("@g.us");
|
|
201
|
+
const peerId = isGroup ? from : from.replace("@s.whatsapp.net", "");
|
|
202
|
+
|
|
203
|
+
const incoming: IncomingMessage = {
|
|
204
|
+
sessionId: this.formatSessionId(peerId, isGroup ? "group" : "direct"),
|
|
205
|
+
channel: "whatsapp",
|
|
206
|
+
accountId: this.accountId,
|
|
207
|
+
peerId,
|
|
208
|
+
peerKind: isGroup ? "group" : "direct",
|
|
209
|
+
content: content || "",
|
|
210
|
+
audio: audioMediaId ? { url: `whatsapp:${audioMediaId}` } : undefined,
|
|
211
|
+
metadata: {
|
|
212
|
+
messageId: typedMsg.key.id,
|
|
213
|
+
timestamp: typedMsg.messageTimestamp,
|
|
214
|
+
pushName: typedMsg.pushName,
|
|
215
|
+
audioMediaId,
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
await this.handleMessage(incoming);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private extractMessageContent(message?: Record<string, unknown>): { content: string | null; audioMediaId: string | null } {
|
|
224
|
+
if (!message) return { content: null, audioMediaId: null };
|
|
225
|
+
|
|
226
|
+
if (message.conversation) {
|
|
227
|
+
return { content: message.conversation as string, audioMediaId: null };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const extendedText = message.extendedTextMessage as { text?: string } | undefined;
|
|
231
|
+
if (extendedText?.text) {
|
|
232
|
+
return { content: extendedText.text, audioMediaId: null };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const imageMsg = message.imageMessage as { caption?: string } | undefined;
|
|
236
|
+
if (imageMsg?.caption) {
|
|
237
|
+
return { content: `[Image] ${imageMsg.caption}`, audioMediaId: null };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const videoMsg = message.videoMessage as { caption?: string } | undefined;
|
|
241
|
+
if (videoMsg?.caption) {
|
|
242
|
+
return { content: `[Video] ${videoMsg.caption}`, audioMediaId: null };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const docMsg = message.documentMessage as { caption?: string } | undefined;
|
|
246
|
+
if (docMsg?.caption) {
|
|
247
|
+
return { content: `[Document] ${docMsg.caption}`, audioMediaId: null };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const audioMsg = message.audioMessage as { mediaKey?: string; id?: string } | undefined;
|
|
251
|
+
if (audioMsg) {
|
|
252
|
+
return { content: "[Audio message]", audioMediaId: audioMsg.id || audioMsg.mediaKey || "unknown" };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return { content: null, audioMediaId: null };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private scheduleReconnect(): void {
|
|
259
|
+
if (!this.running) return;
|
|
260
|
+
|
|
261
|
+
const maxAttempts = this.config.reconnectMaxAttempts ?? 10;
|
|
262
|
+
const baseDelay = this.config.reconnectBaseDelayMs ?? 5000;
|
|
263
|
+
const attempts = this.connectionState.reconnectAttempts;
|
|
264
|
+
|
|
265
|
+
if (attempts >= maxAttempts) {
|
|
266
|
+
this.log.error(`Max reconnection attempts (${maxAttempts}) reached`);
|
|
267
|
+
this.connectionState.status = "error";
|
|
268
|
+
this.connectionState.error = "Max reconnection attempts reached";
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const delay = Math.min(baseDelay * Math.pow(2, attempts), 60000);
|
|
273
|
+
this.connectionState.reconnectAttempts++;
|
|
274
|
+
|
|
275
|
+
this.log.info(`Reconnecting in ${delay / 1000}s (attempt ${attempts + 1}/${maxAttempts})`);
|
|
276
|
+
|
|
277
|
+
this.reconnectTimeout = setTimeout(async () => {
|
|
278
|
+
await this.connect();
|
|
279
|
+
}, delay);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private getJid(sessionId: string): string {
|
|
283
|
+
const cached = this.jidCache.get(sessionId);
|
|
284
|
+
if (cached) return cached;
|
|
285
|
+
|
|
286
|
+
const peerId = this.extractPeerId(sessionId);
|
|
287
|
+
const jid = peerId.includes("@") ? peerId : `${peerId}@s.whatsapp.net`;
|
|
288
|
+
this.jidCache.set(sessionId, jid);
|
|
289
|
+
return jid;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async startTyping(sessionId: string): Promise<void> {
|
|
293
|
+
if (!this.socket || this.connectionState.status !== "connected") return;
|
|
294
|
+
|
|
295
|
+
const jid = this.getJid(sessionId);
|
|
296
|
+
try {
|
|
297
|
+
await this.socket.sendPresenceUpdate("composing", jid);
|
|
298
|
+
} catch {
|
|
299
|
+
// Ignore typing errors
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async stopTyping(sessionId: string): Promise<void> {
|
|
304
|
+
if (!this.socket || this.connectionState.status !== "connected") return;
|
|
305
|
+
|
|
306
|
+
const jid = this.getJid(sessionId);
|
|
307
|
+
try {
|
|
308
|
+
await this.socket.sendPresenceUpdate("paused", jid);
|
|
309
|
+
} catch {
|
|
310
|
+
// Ignore typing errors
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async markAsRead(sessionId: string, messageId?: string): Promise<void> {
|
|
315
|
+
if (!this.socket || this.connectionState.status !== "connected") return;
|
|
316
|
+
if (!messageId) return;
|
|
317
|
+
|
|
318
|
+
const jid = this.getJid(sessionId);
|
|
319
|
+
try {
|
|
320
|
+
await this.socket.readMessages([
|
|
321
|
+
{ remoteJid: jid, id: messageId, fromMe: false }
|
|
322
|
+
]);
|
|
323
|
+
} catch {
|
|
324
|
+
// Ignore read receipt errors
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async send(sessionId: string, message: OutboundMessage): Promise<void> {
|
|
329
|
+
if (!this.socket || this.connectionState.status !== "connected") {
|
|
330
|
+
throw new Error("WhatsApp not connected");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
await this.stopTyping(sessionId);
|
|
334
|
+
|
|
335
|
+
const text = message.content ?? message.chunk ?? "";
|
|
336
|
+
if (!text) return;
|
|
337
|
+
|
|
338
|
+
const jid = this.getJid(sessionId);
|
|
339
|
+
|
|
340
|
+
await this.socket.sendMessage(jid, { text });
|
|
341
|
+
this.log.debug(`Sent message to ${jid}`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async sendAudio(sessionId: string, audio: Buffer, mimeType: string): Promise<void> {
|
|
345
|
+
if (!this.socket || this.connectionState.status !== "connected") {
|
|
346
|
+
throw new Error("WhatsApp not connected");
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const jid = this.getJid(sessionId);
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
await this.socket.sendMessage(jid, {
|
|
353
|
+
audio: audio,
|
|
354
|
+
mimetype: mimeType,
|
|
355
|
+
});
|
|
356
|
+
this.log.debug(`Sent audio to ${jid}`);
|
|
357
|
+
} catch (error) {
|
|
358
|
+
this.log.error(`Failed to send WhatsApp audio: ${(error as Error).message}`);
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private extractPeerId(sessionId: string): string {
|
|
364
|
+
const parts = sessionId.split(":");
|
|
365
|
+
return parts[parts.length - 1] ?? "";
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
getState(): WhatsAppConnectionState {
|
|
369
|
+
return { ...this.connectionState };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export function createWhatsAppChannel(config: WhatsAppConfig): WhatsAppChannel {
|
|
374
|
+
return new WhatsAppChannel(config);
|
|
375
|
+
}
|