@link-os/whatsapp 0.1.0 → 0.1.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/dist/client.d.ts +24 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +261 -64
- package/dist/client.js.map +1 -1
- package/dist/normalize.d.ts +17 -0
- package/dist/normalize.d.ts.map +1 -0
- package/dist/normalize.js +61 -0
- package/dist/normalize.js.map +1 -0
- package/package.json +2 -2
- package/src/client.ts +291 -67
- package/src/normalize.ts +70 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/auth_info_baileys/app-state-sync-key-AAAAANpA.json +0 -1
- package/auth_info_baileys/app-state-sync-key-AAAAANpB.json +0 -1
- package/auth_info_baileys/app-state-sync-key-AAAAANpC.json +0 -1
- package/auth_info_baileys/app-state-sync-key-AAAAANpD.json +0 -1
- package/auth_info_baileys/app-state-sync-key-AAAAANpE.json +0 -1
- package/auth_info_baileys/app-state-sync-key-AAAAANpF.json +0 -1
- package/auth_info_baileys/app-state-sync-key-AAAAANpG.json +0 -1
- package/auth_info_baileys/app-state-sync-key-AAAAANpI.json +0 -1
- package/auth_info_baileys/app-state-sync-key-AAAAANpJ.json +0 -1
- package/auth_info_baileys/app-state-sync-key-ADMAANpI.json +0 -1
- package/auth_info_baileys/app-state-sync-version-regular_low.json +0 -1
- package/auth_info_baileys/creds.json +0 -1
- package/auth_info_baileys/pre-key-1.json +0 -1
- package/auth_info_baileys/pre-key-10.json +0 -1
- package/auth_info_baileys/pre-key-11.json +0 -1
- package/auth_info_baileys/pre-key-12.json +0 -1
- package/auth_info_baileys/pre-key-13.json +0 -1
- package/auth_info_baileys/pre-key-14.json +0 -1
- package/auth_info_baileys/pre-key-15.json +0 -1
- package/auth_info_baileys/pre-key-16.json +0 -1
- package/auth_info_baileys/pre-key-17.json +0 -1
- package/auth_info_baileys/pre-key-18.json +0 -1
- package/auth_info_baileys/pre-key-19.json +0 -1
- package/auth_info_baileys/pre-key-2.json +0 -1
- package/auth_info_baileys/pre-key-20.json +0 -1
- package/auth_info_baileys/pre-key-22.json +0 -1
- package/auth_info_baileys/pre-key-23.json +0 -1
- package/auth_info_baileys/pre-key-24.json +0 -1
- package/auth_info_baileys/pre-key-25.json +0 -1
- package/auth_info_baileys/pre-key-26.json +0 -1
- package/auth_info_baileys/pre-key-27.json +0 -1
- package/auth_info_baileys/pre-key-28.json +0 -1
- package/auth_info_baileys/pre-key-29.json +0 -1
- package/auth_info_baileys/pre-key-3.json +0 -1
- package/auth_info_baileys/pre-key-30.json +0 -1
- package/auth_info_baileys/pre-key-4.json +0 -1
- package/auth_info_baileys/pre-key-5.json +0 -1
- package/auth_info_baileys/pre-key-6.json +0 -1
- package/auth_info_baileys/pre-key-7.json +0 -1
- package/auth_info_baileys/pre-key-8.json +0 -1
- package/auth_info_baileys/pre-key-9.json +0 -1
- package/auth_info_baileys/session-918477881793.0.json +0 -1
package/src/client.ts
CHANGED
|
@@ -10,12 +10,21 @@ import qrcode from 'qrcode-terminal';
|
|
|
10
10
|
import pino from 'pino';
|
|
11
11
|
|
|
12
12
|
import type { PlatformClient, UnifiedMessage, Platform } from '@link-os/types';
|
|
13
|
+
import { normalizeWhatsAppTarget } from './normalize.js';
|
|
13
14
|
|
|
14
15
|
const VERSION = '0.1.0';
|
|
15
16
|
|
|
17
|
+
export interface AllowedContext {
|
|
18
|
+
allowedJid: string; // The JID is stored here
|
|
19
|
+
name: string;
|
|
20
|
+
type: string;
|
|
21
|
+
image?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
export interface WhatsAppClientOptions {
|
|
17
25
|
sessionId: string;
|
|
18
26
|
authDir?: string;
|
|
27
|
+
allowedContexts?: AllowedContext[];
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
export class WhatsAppClient implements PlatformClient {
|
|
@@ -27,12 +36,28 @@ export class WhatsAppClient implements PlatformClient {
|
|
|
27
36
|
private messageHandler?: (message: UnifiedMessage) => Promise<void>;
|
|
28
37
|
private statusHandler?: (status: { type: string; data?: any }) => void;
|
|
29
38
|
private stopped = false;
|
|
39
|
+
private isStarting = false;
|
|
40
|
+
private reconnectAttempts = 0;
|
|
41
|
+
private maxReconnectAttempts = 3;
|
|
42
|
+
private resetTimeout: NodeJS.Timeout | null = null;
|
|
43
|
+
private allowedJids: string[] = [];
|
|
44
|
+
// TODO: Implement persistent storage to save contacts across restarts.
|
|
45
|
+
// Currently, contacts are only in-memory and lost on restart.
|
|
30
46
|
|
|
31
47
|
constructor(options: WhatsAppClientOptions) {
|
|
32
48
|
this.options = {
|
|
33
49
|
authDir: options.authDir || `.auth/whatsapp/${options.sessionId}`,
|
|
34
50
|
...options
|
|
35
51
|
};
|
|
52
|
+
|
|
53
|
+
// Initialize allowedJids from allowedContexts
|
|
54
|
+
if (options.allowedContexts && options.allowedContexts.length > 0) {
|
|
55
|
+
this.allowedJids = options.allowedContexts
|
|
56
|
+
.map(ctx => normalizeWhatsAppTarget(ctx.allowedJid))
|
|
57
|
+
.filter((jid): jid is string => !!jid);
|
|
58
|
+
} else {
|
|
59
|
+
this.allowedJids = [];
|
|
60
|
+
}
|
|
36
61
|
}
|
|
37
62
|
|
|
38
63
|
on(event: 'message' | 'status', handler: any): void {
|
|
@@ -44,78 +69,130 @@ export class WhatsAppClient implements PlatformClient {
|
|
|
44
69
|
}
|
|
45
70
|
|
|
46
71
|
async start(): Promise<void> {
|
|
72
|
+
if (this.isStarting) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.isStarting = true;
|
|
47
76
|
this.stopped = false;
|
|
48
|
-
const { state, saveCreds } = await useMultiFileAuthState(this.options.authDir!);
|
|
49
|
-
const { version } = await fetchLatestBaileysVersion();
|
|
50
|
-
|
|
51
|
-
console.log(`Using Baileys version: ${version.join('.')}`);
|
|
52
|
-
|
|
53
|
-
this.sock = (makeWASocket as any).default ? (makeWASocket as any).default({
|
|
54
|
-
auth: {
|
|
55
|
-
creds: state.creds,
|
|
56
|
-
keys: (makeCacheableSignalKeyStore as any)(state.keys, this.logger),
|
|
57
|
-
},
|
|
58
|
-
version,
|
|
59
|
-
logger: this.logger,
|
|
60
|
-
printQRInTerminal: false,
|
|
61
|
-
browser: ['linkos', 'cli', VERSION],
|
|
62
|
-
syncFullHistory: false,
|
|
63
|
-
markOnlineOnConnect: false,
|
|
64
|
-
shouldIgnoreJid: (jid: string) => jid.includes('@broadcast') || jid.includes('@newsletter')
|
|
65
|
-
}) : (makeWASocket as any)({
|
|
66
|
-
auth: {
|
|
67
|
-
creds: state.creds,
|
|
68
|
-
keys: (makeCacheableSignalKeyStore as any)(state.keys, this.logger),
|
|
69
|
-
},
|
|
70
|
-
version,
|
|
71
|
-
logger: this.logger,
|
|
72
|
-
printQRInTerminal: false,
|
|
73
|
-
browser: ['linkos', 'cli', VERSION],
|
|
74
|
-
syncFullHistory: false,
|
|
75
|
-
markOnlineOnConnect: false,
|
|
76
|
-
shouldIgnoreJid: (jid: string) => jid.includes('@broadcast') || jid.includes('@newsletter')
|
|
77
|
-
});
|
|
78
77
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
// Clear any pending reset
|
|
79
|
+
if (this.resetTimeout) {
|
|
80
|
+
clearTimeout(this.resetTimeout);
|
|
81
|
+
this.resetTimeout = null;
|
|
83
82
|
}
|
|
84
83
|
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
try {
|
|
85
|
+
console.log(`[WhatsAppClient] Initializing auth state for ${this.options.sessionId}...`);
|
|
86
|
+
const { state, saveCreds } = await useMultiFileAuthState(this.options.authDir!);
|
|
87
|
+
console.log(`[WhatsAppClient] Fetching latest Baileys version...`);
|
|
88
|
+
const { version } = await fetchLatestBaileysVersion();
|
|
87
89
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
this.statusHandler({ type: 'qr', data: qr });
|
|
93
|
-
}
|
|
90
|
+
console.log(`Using Baileys version: ${version.join('.')}`);
|
|
91
|
+
// ... (rest of start)
|
|
92
|
+
if (this.allowedJids.length > 0) {
|
|
93
|
+
console.log(`🔒 Allowlist enabled: ${this.allowedJids.length} IDs allowed.`);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
this.sock = (makeWASocket as any).default ? (makeWASocket as any).default({
|
|
97
|
+
auth: {
|
|
98
|
+
creds: state.creds,
|
|
99
|
+
keys: (makeCacheableSignalKeyStore as any)(state.keys, this.logger),
|
|
100
|
+
},
|
|
101
|
+
version,
|
|
102
|
+
logger: this.logger,
|
|
103
|
+
printQRInTerminal: false,
|
|
104
|
+
browser: ['Ubuntu', 'Chrome', '20.0.04'],
|
|
105
|
+
syncFullHistory: true, // Recommended for better desktop state emulation
|
|
106
|
+
markOnlineOnConnect: false,
|
|
107
|
+
}) : (makeWASocket as any)({
|
|
108
|
+
auth: {
|
|
109
|
+
creds: state.creds,
|
|
110
|
+
keys: (makeCacheableSignalKeyStore as any)(state.keys, this.logger),
|
|
111
|
+
},
|
|
112
|
+
version,
|
|
113
|
+
logger: this.logger,
|
|
114
|
+
printQRInTerminal: false,
|
|
115
|
+
browser: ['Ubuntu', 'Chrome', '20.0.04'],
|
|
116
|
+
syncFullHistory: true,
|
|
117
|
+
markOnlineOnConnect: false,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (this.sock.ws && typeof this.sock.ws.on === 'function') {
|
|
121
|
+
this.sock.ws.on('error', (err: Error) => {
|
|
122
|
+
console.error('WebSocket error:', err.message);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
99
125
|
|
|
100
|
-
|
|
126
|
+
this.sock.ev.on('connection.update', async (update: any) => {
|
|
127
|
+
const { connection, lastDisconnect, qr } = update;
|
|
101
128
|
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
this.
|
|
107
|
-
|
|
108
|
-
}, 5000);
|
|
129
|
+
if (qr) {
|
|
130
|
+
console.log('\n📱 Scan this QR code with WhatsApp (Linked Devices):\n');
|
|
131
|
+
qrcode.generate(qr, { small: true });
|
|
132
|
+
if (this.statusHandler) {
|
|
133
|
+
this.statusHandler({ type: 'qr', data: qr });
|
|
134
|
+
}
|
|
109
135
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
136
|
+
|
|
137
|
+
if (connection === 'close') {
|
|
138
|
+
const statusCode = (lastDisconnect?.error as Boom)?.output?.statusCode;
|
|
139
|
+
const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
|
|
140
|
+
|
|
141
|
+
console.log(`Connection closed. Status: ${statusCode}, Will reconnect: ${shouldReconnect}`);
|
|
142
|
+
|
|
143
|
+
if (this.resetTimeout) {
|
|
144
|
+
clearTimeout(this.resetTimeout);
|
|
145
|
+
this.resetTimeout = null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (shouldReconnect && !this.reconnecting && !this.stopped) {
|
|
149
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
150
|
+
this.reconnectAttempts++;
|
|
151
|
+
this.reconnecting = true;
|
|
152
|
+
if (this.statusHandler) {
|
|
153
|
+
this.statusHandler({ type: 'reconnecting', data: { attempt: this.reconnectAttempts, max: this.maxReconnectAttempts } });
|
|
154
|
+
}
|
|
155
|
+
console.log(`Reconnecting (Attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts}) in 5 seconds...`);
|
|
156
|
+
setTimeout(() => {
|
|
157
|
+
this.reconnecting = false;
|
|
158
|
+
this.start();
|
|
159
|
+
}, 5000);
|
|
160
|
+
} else {
|
|
161
|
+
console.error(`[WhatsApp] Max reconnection attempts (${this.maxReconnectAttempts}) reached. Stopping.`);
|
|
162
|
+
if (this.statusHandler) {
|
|
163
|
+
this.statusHandler({ type: 'disconnected', data: { reason: 'max_retries' } });
|
|
164
|
+
}
|
|
165
|
+
this.stop();
|
|
166
|
+
}
|
|
167
|
+
} else if (this.statusHandler && !shouldReconnect) {
|
|
168
|
+
this.statusHandler({ type: 'disconnected' });
|
|
169
|
+
}
|
|
170
|
+
} else if (connection === 'open') {
|
|
171
|
+
console.log('✅ Connected to WhatsApp');
|
|
172
|
+
this.isStarting = false;
|
|
173
|
+
|
|
174
|
+
// Only reset attempts after 10 seconds of stable connection
|
|
175
|
+
if (this.resetTimeout) clearTimeout(this.resetTimeout);
|
|
176
|
+
this.resetTimeout = setTimeout(() => {
|
|
177
|
+
this.reconnectAttempts = 0;
|
|
178
|
+
this.resetTimeout = null;
|
|
179
|
+
}, 10000);
|
|
180
|
+
|
|
181
|
+
if (this.statusHandler) {
|
|
182
|
+
this.statusHandler({ type: 'connected' });
|
|
183
|
+
}
|
|
114
184
|
}
|
|
115
|
-
}
|
|
116
|
-
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
this.sock.ev.on('creds.update', saveCreds);
|
|
117
188
|
|
|
118
|
-
|
|
189
|
+
} catch (error) {
|
|
190
|
+
this.isStarting = false;
|
|
191
|
+
console.error('[WhatsApp] Failed to start:', error);
|
|
192
|
+
if (this.statusHandler) {
|
|
193
|
+
this.statusHandler({ type: 'error', data: error });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
119
196
|
|
|
120
197
|
this.sock.ev.on('messages.upsert', async ({ messages, type }: { messages: any[]; type: string }) => {
|
|
121
198
|
if (type !== 'notify') return;
|
|
@@ -124,28 +201,89 @@ export class WhatsAppClient implements PlatformClient {
|
|
|
124
201
|
if (msg.key.fromMe) continue;
|
|
125
202
|
if (msg.key.remoteJid === 'status@broadcast') continue;
|
|
126
203
|
|
|
204
|
+
const remoteJid = msg.key.remoteJid;
|
|
205
|
+
const participant = msg.key.participant || remoteJid;
|
|
206
|
+
const isGroup = remoteJid?.endsWith('@g.us') || false;
|
|
207
|
+
|
|
208
|
+
// Allowlist Check
|
|
209
|
+
if (this.allowedJids.length > 0) {
|
|
210
|
+
const isAllowed = this.allowedJids.some(allowed =>
|
|
211
|
+
remoteJid?.includes(allowed) || participant?.includes(allowed)
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
if (!isAllowed) {
|
|
215
|
+
// console.log(`🚫 Ignoring message from unauthorized source: ${remoteJid}`);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Group Mention Policy: Only respond if tagged
|
|
221
|
+
if (isGroup && this.sock?.user?.id) {
|
|
222
|
+
const botJid = normalizeWhatsAppTarget(this.sock.user.id);
|
|
223
|
+
const botLid = this.sock.user.lid ? normalizeWhatsAppTarget(this.sock.user.lid) : null;
|
|
224
|
+
|
|
225
|
+
const message = msg.message;
|
|
226
|
+
const contextInfo = message?.extendedTextMessage?.contextInfo ||
|
|
227
|
+
message?.imageMessage?.contextInfo ||
|
|
228
|
+
message?.videoMessage?.contextInfo ||
|
|
229
|
+
message?.documentMessage?.contextInfo ||
|
|
230
|
+
message?.audioMessage?.contextInfo ||
|
|
231
|
+
(message as any)?.contextInfo;
|
|
232
|
+
|
|
233
|
+
const mentions = contextInfo?.mentionedJid || [];
|
|
234
|
+
|
|
235
|
+
// Normalize all mentions and the bot ID for comparison
|
|
236
|
+
const isMentioned = mentions.some((m: string) => {
|
|
237
|
+
const normM = normalizeWhatsAppTarget(m);
|
|
238
|
+
const match = (botJid && normM === botJid) ||
|
|
239
|
+
(botLid && normM === botLid) ||
|
|
240
|
+
m === this.sock.user.id ||
|
|
241
|
+
(this.sock.user.lid && m === this.sock.user.lid);
|
|
242
|
+
|
|
243
|
+
return match;
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Check if it's a reply to the bot
|
|
247
|
+
const quotedParticipant = contextInfo?.participant;
|
|
248
|
+
const isReplyToBot = quotedParticipant && (
|
|
249
|
+
normalizeWhatsAppTarget(quotedParticipant) === botJid ||
|
|
250
|
+
normalizeWhatsAppTarget(quotedParticipant) === botLid ||
|
|
251
|
+
quotedParticipant === this.sock.user.id ||
|
|
252
|
+
(this.sock.user.lid && quotedParticipant === this.sock.user.lid)
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
if (!isMentioned && !isReplyToBot) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
127
260
|
const content = this.extractMessageContent(msg);
|
|
128
261
|
if (!content) continue;
|
|
129
262
|
|
|
130
263
|
if (!this.messageHandler) continue;
|
|
131
264
|
|
|
132
|
-
const isGroup = msg.key.remoteJid?.endsWith('@g.us') || false;
|
|
133
|
-
|
|
134
265
|
const unifiedMessage: UnifiedMessage = {
|
|
135
266
|
id: msg.key.id || `wa_${Date.now()}`,
|
|
136
267
|
platform: 'whatsapp',
|
|
137
|
-
userId:
|
|
268
|
+
userId: remoteJid || '',
|
|
138
269
|
sessionId: this.options.sessionId,
|
|
139
270
|
content,
|
|
140
271
|
messageType: 'text',
|
|
141
272
|
timestamp: new Date((msg.messageTimestamp as number) * 1000),
|
|
142
273
|
metadata: {
|
|
143
|
-
pushName: msg.pushName,
|
|
144
|
-
|
|
145
|
-
isGroup
|
|
274
|
+
pushName: msg.pushName || 'Unknown User',
|
|
275
|
+
isGroup,
|
|
276
|
+
participant: isGroup ? participant : undefined,
|
|
277
|
+
jid: remoteJid
|
|
146
278
|
}
|
|
147
279
|
};
|
|
148
280
|
|
|
281
|
+
// Typing indicator (optional, but keep for UX if stable)
|
|
282
|
+
try {
|
|
283
|
+
// Only start typing if jid is valid
|
|
284
|
+
if (remoteJid) await this.startTyping(remoteJid);
|
|
285
|
+
} catch (e) { /* ignore */ }
|
|
286
|
+
|
|
149
287
|
await this.messageHandler(unifiedMessage);
|
|
150
288
|
}
|
|
151
289
|
});
|
|
@@ -167,15 +305,101 @@ export class WhatsAppClient implements PlatformClient {
|
|
|
167
305
|
|
|
168
306
|
async sendMessage(to: string, text: string): Promise<void> {
|
|
169
307
|
if (!this.sock) throw new Error('Not connected');
|
|
170
|
-
|
|
308
|
+
|
|
309
|
+
const jid = normalizeWhatsAppTarget(to);
|
|
310
|
+
if (!jid) {
|
|
311
|
+
console.error(`[WhatsApp] Invalid message target: ${to}`);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
171
315
|
await this.sock.sendMessage(jid, { text });
|
|
172
316
|
}
|
|
173
317
|
|
|
318
|
+
async startTyping(jid: string): Promise<void> {
|
|
319
|
+
if (!this.sock) return;
|
|
320
|
+
await this.sock.sendPresenceUpdate('composing', jid);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async stopTyping(jid: string): Promise<void> {
|
|
324
|
+
if (!this.sock) return;
|
|
325
|
+
await this.sock.sendPresenceUpdate('paused', jid);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async reactToMessage(jid: string, key: any, emoji: string): Promise<void> {
|
|
329
|
+
if (!this.sock) return;
|
|
330
|
+
await this.sock.sendMessage(jid, { react: { text: emoji, key } });
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async markRead(jid: string, key: any): Promise<void> {
|
|
334
|
+
if (!this.sock) return;
|
|
335
|
+
await this.sock.readMessages([key]);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async updateConfiguration(config: Partial<WhatsAppClientOptions>): Promise<void> {
|
|
339
|
+
if (config.allowedContexts) {
|
|
340
|
+
this.allowedJids = config.allowedContexts
|
|
341
|
+
.map(ctx => normalizeWhatsAppTarget(ctx.allowedJid))
|
|
342
|
+
.filter((jid): jid is string => !!jid);
|
|
343
|
+
console.log(`🔄 Configuration updated: Allowlist now has ${this.allowedJids.length} normalized IDs.`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
174
347
|
async stop(): Promise<void> {
|
|
175
348
|
this.stopped = true;
|
|
349
|
+
this.isStarting = false;
|
|
350
|
+
if (this.resetTimeout) {
|
|
351
|
+
clearTimeout(this.resetTimeout);
|
|
352
|
+
this.resetTimeout = null;
|
|
353
|
+
}
|
|
176
354
|
if (this.sock) {
|
|
177
355
|
this.sock.end(undefined);
|
|
178
356
|
this.sock = null;
|
|
179
357
|
}
|
|
180
358
|
}
|
|
359
|
+
|
|
360
|
+
async deleteSession(): Promise<void> {
|
|
361
|
+
await this.stop();
|
|
362
|
+
if (this.options.authDir) {
|
|
363
|
+
const fs = await import('fs/promises');
|
|
364
|
+
try {
|
|
365
|
+
await fs.rm(this.options.authDir, { recursive: true, force: true });
|
|
366
|
+
console.log(`Deleted session directory: ${this.options.authDir}`);
|
|
367
|
+
} catch (error: any) {
|
|
368
|
+
console.error(`Failed to delete session directory: ${error.message}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async getAvailableContexts(): Promise<{ id: string; name: string; type: 'group' | 'user'; image?: string }[]> {
|
|
374
|
+
if (!this.sock) return [];
|
|
375
|
+
|
|
376
|
+
const contexts: { id: string; name: string; type: 'group' | 'user'; image?: string }[] = [];
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
// 1. Groups from direct fetch (most reliable for groups)
|
|
380
|
+
const groups = await this.sock.groupFetchAllParticipating();
|
|
381
|
+
for (const [id, metadata] of Object.entries(groups)) {
|
|
382
|
+
contexts.push({
|
|
383
|
+
id,
|
|
384
|
+
name: (metadata as any).subject || 'Unknown Group',
|
|
385
|
+
type: 'group'
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
} catch (error) {
|
|
389
|
+
console.error('Failed to fetch contexts:', error);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Fetch Profile Pictures (best effort, parallel)
|
|
393
|
+
await Promise.all(contexts.map(async (ctx) => {
|
|
394
|
+
try {
|
|
395
|
+
// Only fetch if we don't have one or to refresh
|
|
396
|
+
ctx.image = await this.sock.profilePictureUrl(ctx.id, 'preview');
|
|
397
|
+
} catch (e) {
|
|
398
|
+
// Ignore error (no profile pic or privacy restricted)
|
|
399
|
+
ctx.image = undefined;
|
|
400
|
+
}
|
|
401
|
+
}));
|
|
402
|
+
|
|
403
|
+
return contexts;
|
|
404
|
+
}
|
|
181
405
|
}
|
package/src/normalize.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* WhatsApp JID Normalization following OpenClaw/Baileys patterns.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const WHATSAPP_USER_JID_RE = /^(\d+)(?::\d+)?@s\.whatsapp\.net$/i;
|
|
7
|
+
const WHATSAPP_LID_RE = /^(\d+)(?::\d+)?@lid$/i;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Clean phone numbers to digits only.
|
|
11
|
+
*/
|
|
12
|
+
export function normalizeE164(value: string): string {
|
|
13
|
+
return value.replace(/\D/g, "");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function stripWhatsAppTargetPrefixes(value: string): string {
|
|
17
|
+
let candidate = value.trim();
|
|
18
|
+
for (; ;) {
|
|
19
|
+
const before = candidate;
|
|
20
|
+
candidate = candidate.replace(/^whatsapp:/i, "").trim();
|
|
21
|
+
if (candidate === before) {
|
|
22
|
+
return candidate;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if the JID belongs to a group.
|
|
29
|
+
*/
|
|
30
|
+
export function isWhatsAppGroupJid(value: string): boolean {
|
|
31
|
+
const candidate = stripWhatsAppTargetPrefixes(value);
|
|
32
|
+
const lower = candidate.toLowerCase();
|
|
33
|
+
return lower.endsWith("@g.us");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Normalize input string into a valid Baileys JID.
|
|
38
|
+
* Handles phone numbers, group IDs, and existing JIDs with device suffixes.
|
|
39
|
+
*/
|
|
40
|
+
export function normalizeWhatsAppTarget(value: string): string | null {
|
|
41
|
+
const candidate = stripWhatsAppTargetPrefixes(value);
|
|
42
|
+
if (!candidate) return null;
|
|
43
|
+
|
|
44
|
+
// Handle Groups
|
|
45
|
+
if (isWhatsAppGroupJid(candidate)) {
|
|
46
|
+
const localPart = candidate.slice(0, candidate.length - "@g.us".length);
|
|
47
|
+
return `${localPart}@g.us`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Handle standard user JIDs (strip device suffixes like :5)
|
|
51
|
+
if (WHATSAPP_USER_JID_RE.test(candidate)) {
|
|
52
|
+
const match = candidate.match(WHATSAPP_USER_JID_RE);
|
|
53
|
+
return `${match![1]}@s.whatsapp.net`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Handle LIDs
|
|
57
|
+
if (WHATSAPP_LID_RE.test(candidate)) {
|
|
58
|
+
const match = candidate.match(WHATSAPP_LID_RE);
|
|
59
|
+
return `${match![1]}@lid`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Handle raw phone numbers or strings without @
|
|
63
|
+
if (!candidate.includes("@")) {
|
|
64
|
+
const normalized = normalizeE164(candidate);
|
|
65
|
+
// Only accept if it has at least 5 digits (sanity check for phone numbers)
|
|
66
|
+
return normalized.length >= 5 ? `${normalized}@s.whatsapp.net` : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return null;
|
|
70
|
+
}
|