whatsapp-pi 1.0.42 → 1.0.44
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whatsapp-pi",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.44",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "WhatsApp integration extension for Pi",
|
|
6
6
|
"main": "whatsapp-pi.ts",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"author": "Rapha",
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"scripts": {
|
|
30
|
+
"lint": "eslint whatsapp-pi.ts \"src/**/*.ts\" \"tests/**/*.ts\"",
|
|
30
31
|
"test": "vitest run",
|
|
31
32
|
"typecheck": "tsc --noEmit"
|
|
32
33
|
},
|
|
@@ -36,10 +37,14 @@
|
|
|
36
37
|
"qrcode-terminal": "^0.12.0"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
40
|
+
"@eslint/js": "^9.39.4",
|
|
39
41
|
"@mariozechner/pi-coding-agent": "latest",
|
|
40
42
|
"@types/node": "^20.11.0",
|
|
41
43
|
"@types/qrcode-terminal": "^0.12.2",
|
|
44
|
+
"@typescript-eslint/eslint-plugin": "^8.58.1",
|
|
45
|
+
"@typescript-eslint/parser": "^8.58.1",
|
|
42
46
|
"@vitest/coverage-v8": "^1.6.1",
|
|
47
|
+
"eslint": "^9.39.4",
|
|
43
48
|
"ts-node": "^10.9.2",
|
|
44
49
|
"tsx": "^4.7.0",
|
|
45
50
|
"typescript": "^5.3.0",
|
|
@@ -102,7 +102,7 @@ export class IncomingMediaService {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
private async saveDocument(fileName: string, buffer: Buffer): Promise<string> {
|
|
105
|
-
const sanitized = fileName.replace(/[^a-z0-9
|
|
105
|
+
const sanitized = fileName.replace(/[^a-z0-9._-]/gi, '_');
|
|
106
106
|
const savedFileName = `${Date.now()}_${sanitized}`;
|
|
107
107
|
const documentDir = join(process.cwd(), '.pi-data', 'whatsapp', 'documents');
|
|
108
108
|
const absolutePath = join(documentDir, savedFileName);
|
|
@@ -1,27 +1,33 @@
|
|
|
1
|
-
import { useMultiFileAuthState } from '@whiskeysockets/baileys';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { readFile, writeFile, mkdir, rm,
|
|
1
|
+
import { useMultiFileAuthState } from '@whiskeysockets/baileys';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { readFile, writeFile, mkdir, rm, rename } from 'fs/promises';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
import { SessionStatus } from '../models/whatsapp.types.js';
|
|
6
6
|
|
|
7
|
-
export interface Contact {
|
|
8
|
-
number: string;
|
|
9
|
-
name?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class SessionManager {
|
|
13
|
-
// Data is stored in the user's home directory to persist across updates
|
|
14
|
-
private readonly baseDir
|
|
15
|
-
private readonly authStateDir
|
|
16
|
-
private readonly configPath
|
|
7
|
+
export interface Contact {
|
|
8
|
+
number: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class SessionManager {
|
|
13
|
+
// Data is stored in the user's home directory to persist across updates
|
|
14
|
+
private readonly baseDir: string;
|
|
15
|
+
private readonly authStateDir: string;
|
|
16
|
+
private readonly configPath: string;
|
|
17
17
|
|
|
18
18
|
private status: SessionStatus = 'logged-out';
|
|
19
19
|
private allowList: Contact[] = [];
|
|
20
20
|
private blockList: Contact[] = [];
|
|
21
21
|
private ignoredNumbers: Contact[] = [];
|
|
22
22
|
private hasAuthState = false;
|
|
23
|
-
private openaiKey: string = '';
|
|
24
|
-
private visionModel: string = 'gpt-4o';
|
|
23
|
+
private openaiKey: string = '';
|
|
24
|
+
private visionModel: string = 'gpt-4o';
|
|
25
|
+
|
|
26
|
+
constructor(baseDir = join(homedir(), '.pi', 'whatsapp-pi')) {
|
|
27
|
+
this.baseDir = baseDir;
|
|
28
|
+
this.authStateDir = join(this.baseDir, 'auth');
|
|
29
|
+
this.configPath = join(this.baseDir, 'config.json');
|
|
30
|
+
}
|
|
25
31
|
|
|
26
32
|
private async ensureStorageDirectories() {
|
|
27
33
|
await mkdir(this.baseDir, { recursive: true });
|
|
@@ -33,15 +39,17 @@ export class SessionManager {
|
|
|
33
39
|
await this.ensureStorageDirectories();
|
|
34
40
|
await this.loadConfig();
|
|
35
41
|
await this.syncAuthStateFromDisk();
|
|
36
|
-
} catch
|
|
42
|
+
} catch {
|
|
43
|
+
// Initialization is best-effort; callers can continue with defaults.
|
|
44
|
+
}
|
|
37
45
|
}
|
|
38
46
|
|
|
39
|
-
private async loadConfig() {
|
|
40
|
-
try {
|
|
41
|
-
const data = await readFile(this.configPath, 'utf-8');
|
|
42
|
-
const config =
|
|
43
|
-
|
|
44
|
-
const cleanContact = (item: any): Contact | null => {
|
|
47
|
+
private async loadConfig() {
|
|
48
|
+
try {
|
|
49
|
+
const data = await readFile(this.configPath, 'utf-8');
|
|
50
|
+
const { config, recovered } = this.parseConfig(data);
|
|
51
|
+
|
|
52
|
+
const cleanContact = (item: any): Contact | null => {
|
|
45
53
|
if (typeof item === 'string') return { number: item };
|
|
46
54
|
if (item && typeof item === 'object') {
|
|
47
55
|
let num = item.number;
|
|
@@ -60,30 +68,91 @@ export class SessionManager {
|
|
|
60
68
|
this.blockList = (config.blockList || []).map(cleanContact).filter(Boolean) as Contact[];
|
|
61
69
|
this.ignoredNumbers = (config.ignoredNumbers || []).map(cleanContact).filter(Boolean) as Contact[];
|
|
62
70
|
this.status = config.status || 'logged-out';
|
|
63
|
-
this.hasAuthState = Boolean(config.hasAuthState);
|
|
64
|
-
this.openaiKey = config.openaiKey || '';
|
|
65
|
-
this.visionModel = config.visionModel || 'gpt-4o';
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
this.hasAuthState = Boolean(config.hasAuthState);
|
|
72
|
+
this.openaiKey = config.openaiKey || '';
|
|
73
|
+
this.visionModel = config.visionModel || 'gpt-4o';
|
|
74
|
+
|
|
75
|
+
if (recovered) {
|
|
76
|
+
await this.saveConfig();
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
// File not found is fine
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private parseConfig(data: string): { config: any; recovered: boolean } {
|
|
84
|
+
try {
|
|
85
|
+
return { config: JSON.parse(data), recovered: false };
|
|
86
|
+
} catch (error) {
|
|
87
|
+
const objectEnd = this.findFirstJsonObjectEnd(data);
|
|
88
|
+
if (objectEnd < 0) {
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
config: JSON.parse(data.slice(0, objectEnd + 1)),
|
|
94
|
+
recovered: true
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private findFirstJsonObjectEnd(data: string): number {
|
|
100
|
+
let depth = 0;
|
|
101
|
+
let inString = false;
|
|
102
|
+
let escaped = false;
|
|
103
|
+
|
|
104
|
+
for (let i = 0; i < data.length; i++) {
|
|
105
|
+
const char = data[i];
|
|
106
|
+
|
|
107
|
+
if (inString) {
|
|
108
|
+
if (escaped) {
|
|
109
|
+
escaped = false;
|
|
110
|
+
} else if (char === '\\') {
|
|
111
|
+
escaped = true;
|
|
112
|
+
} else if (char === '"') {
|
|
113
|
+
inString = false;
|
|
114
|
+
}
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (char === '"') {
|
|
119
|
+
inString = true;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (char === '{') {
|
|
124
|
+
depth++;
|
|
125
|
+
} else if (char === '}') {
|
|
126
|
+
depth--;
|
|
127
|
+
if (depth === 0) {
|
|
128
|
+
return i;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return -1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public async saveConfig() {
|
|
137
|
+
const tempPath = `${this.configPath}.${process.pid}.${Date.now()}.tmp`;
|
|
138
|
+
try {
|
|
139
|
+
const config = {
|
|
140
|
+
allowList: this.allowList,
|
|
75
141
|
blockList: this.blockList,
|
|
76
142
|
ignoredNumbers: this.ignoredNumbers,
|
|
77
143
|
status: this.status,
|
|
78
144
|
hasAuthState: this.hasAuthState,
|
|
79
|
-
openaiKey: this.openaiKey,
|
|
80
|
-
visionModel: this.visionModel
|
|
81
|
-
};
|
|
82
|
-
await
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
145
|
+
openaiKey: this.openaiKey,
|
|
146
|
+
visionModel: this.visionModel
|
|
147
|
+
};
|
|
148
|
+
await mkdir(this.baseDir, { recursive: true });
|
|
149
|
+
await writeFile(tempPath, JSON.stringify(config, null, 2));
|
|
150
|
+
await rename(tempPath, this.configPath);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
await rm(tempPath, { force: true }).catch(() => {});
|
|
153
|
+
console.error('Failed to save config:', error);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
87
156
|
|
|
88
157
|
getAllowList(): Contact[] {
|
|
89
158
|
return this.allowList;
|
|
@@ -205,17 +274,10 @@ export class SessionManager {
|
|
|
205
274
|
}
|
|
206
275
|
}
|
|
207
276
|
|
|
208
|
-
public async isRegistered(): Promise<boolean> {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
this.hasAuthState = true;
|
|
213
|
-
return true;
|
|
214
|
-
} catch {
|
|
215
|
-
await this.syncAuthStateFromDisk();
|
|
216
|
-
return this.hasAuthState;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
277
|
+
public async isRegistered(): Promise<boolean> {
|
|
278
|
+
await this.syncAuthStateFromDisk();
|
|
279
|
+
return this.hasAuthState;
|
|
280
|
+
}
|
|
219
281
|
|
|
220
282
|
async markAuthStateAvailable() {
|
|
221
283
|
if (!this.hasAuthState) {
|
|
@@ -229,19 +291,27 @@ export class SessionManager {
|
|
|
229
291
|
return await useMultiFileAuthState(this.authStateDir);
|
|
230
292
|
}
|
|
231
293
|
|
|
232
|
-
private async syncAuthStateFromDisk() {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
294
|
+
private async syncAuthStateFromDisk() {
|
|
295
|
+
const nextHasAuthState = await this.hasCredentialsFile();
|
|
296
|
+
const nextStatus = nextHasAuthState || this.status !== 'connected'
|
|
297
|
+
? this.status
|
|
298
|
+
: 'disconnected';
|
|
299
|
+
|
|
300
|
+
if (nextHasAuthState !== this.hasAuthState || nextStatus !== this.status) {
|
|
301
|
+
this.hasAuthState = nextHasAuthState;
|
|
302
|
+
this.status = nextStatus;
|
|
303
|
+
await this.saveConfig();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private async hasCredentialsFile(): Promise<boolean> {
|
|
308
|
+
try {
|
|
309
|
+
await readFile(join(this.authStateDir, 'creds.json'));
|
|
310
|
+
return true;
|
|
311
|
+
} catch {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
245
315
|
|
|
246
316
|
async deleteAuthState() {
|
|
247
317
|
try {
|
|
@@ -82,7 +82,7 @@ export class WhatsAppService {
|
|
|
82
82
|
private restoreBaileysConsoleFilter?: () => void;
|
|
83
83
|
private reconnectTimeout?: ReturnType<typeof setTimeout>;
|
|
84
84
|
private onQRCode?: (qr: string) => void;
|
|
85
|
-
private onMessage?: (m:
|
|
85
|
+
private onMessage?: (m: MessagesUpsertEvent) => void;
|
|
86
86
|
private onStatusUpdate?: (status: string) => void;
|
|
87
87
|
private lastRemoteJid: string | null = null;
|
|
88
88
|
|
|
@@ -295,7 +295,7 @@ export class WhatsAppService {
|
|
|
295
295
|
private async handlePairingQr(qr: string) {
|
|
296
296
|
this.sessionManager.setStatus('pairing');
|
|
297
297
|
this.onQRCode?.(qr);
|
|
298
|
-
this.onStatusUpdate?.('| WhatsApp:
|
|
298
|
+
this.onStatusUpdate?.('| WhatsApp: Disconnected');
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
private async handleConnectionOpen() {
|
|
@@ -339,35 +339,27 @@ export class WhatsAppService {
|
|
|
339
339
|
console.error(`Connection closed [${statusCode}]. Reconnecting: ${shouldReconnect}`);
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
-
if (shouldTreatAsLoggedOut) {
|
|
343
|
-
if (
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if (isBadMac) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
this.onStatusUpdate?.('| WhatsApp: Session Error (Bad MAC)');
|
|
364
|
-
}
|
|
365
|
-
this.sessionManager.setStatus('logged-out');
|
|
366
|
-
if (!isBadMac) {
|
|
367
|
-
this.onStatusUpdate?.('| WhatsApp: Logged out');
|
|
368
|
-
}
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
342
|
+
if (shouldTreatAsLoggedOut) {
|
|
343
|
+
if (this.verboseMode) {
|
|
344
|
+
console.error(`Session rejected [${statusCode}] - preserving auth state`);
|
|
345
|
+
}
|
|
346
|
+
if (isBadMac) {
|
|
347
|
+
if (this.verboseMode) {
|
|
348
|
+
console.error('[WhatsApp-Pi] Bad MAC error detected. Your session keys are corrupted.');
|
|
349
|
+
console.error('[WhatsApp-Pi] Use Logoff (Delete Session) only if reconnect keeps failing.');
|
|
350
|
+
}
|
|
351
|
+
this.onStatusUpdate?.('| WhatsApp: Session Error (Bad MAC)');
|
|
352
|
+
} else if (isAuthRejected && allowPairingOnAuthFailure) {
|
|
353
|
+
this.onStatusUpdate?.('| WhatsApp: Session Preserved (Reconnect Failed)');
|
|
354
|
+
}
|
|
355
|
+
this.cleanupSocket();
|
|
356
|
+
this.isReconnecting = false;
|
|
357
|
+
await this.sessionManager.setStatus('disconnected');
|
|
358
|
+
if (!isBadMac) {
|
|
359
|
+
this.onStatusUpdate?.('| WhatsApp: Disconnected');
|
|
360
|
+
}
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
371
363
|
|
|
372
364
|
if (statusCode === DisconnectReason.connectionReplaced) {
|
|
373
365
|
if (this.verboseMode) {
|
|
@@ -465,7 +457,7 @@ export class WhatsAppService {
|
|
|
465
457
|
this.onQRCode = callback;
|
|
466
458
|
}
|
|
467
459
|
|
|
468
|
-
setMessageCallback(callback: (m:
|
|
460
|
+
setMessageCallback(callback: (m: MessagesUpsertEvent) => void) {
|
|
469
461
|
this.onMessage = callback;
|
|
470
462
|
}
|
|
471
463
|
|
package/src/ui/menu.handler.ts
CHANGED
|
@@ -63,13 +63,14 @@ export class MenuHandler {
|
|
|
63
63
|
await this.whatsappService.stop();
|
|
64
64
|
ctx.ui.notify('WhatsApp Agent Disconnected', 'warning');
|
|
65
65
|
break;
|
|
66
|
-
case 'Logoff (Delete Session)':
|
|
67
|
-
const confirmLogoff = await ctx.ui.confirm('Logoff', 'Delete all credentials?');
|
|
68
|
-
if (confirmLogoff) {
|
|
69
|
-
await this.whatsappService.logout();
|
|
70
|
-
ctx.ui.notify('Logged off and credentials deleted', 'info');
|
|
71
|
-
}
|
|
72
|
-
break;
|
|
66
|
+
case 'Logoff (Delete Session)': {
|
|
67
|
+
const confirmLogoff = await ctx.ui.confirm('Logoff', 'Delete all credentials?');
|
|
68
|
+
if (confirmLogoff) {
|
|
69
|
+
await this.whatsappService.logout();
|
|
70
|
+
ctx.ui.notify('Logged off and credentials deleted', 'info');
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
73
74
|
case 'Allowed Numbers':
|
|
74
75
|
await this.manageAllowList(ctx);
|
|
75
76
|
break;
|
|
@@ -29,11 +29,10 @@ export class MessageDetailView {
|
|
|
29
29
|
) {
|
|
30
30
|
this.props.onClose();
|
|
31
31
|
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
render(width: number): string[] {
|
|
35
|
-
const
|
|
36
|
-
const bodyText = this.props.text.length > 0 ? this.props.text : '[No readable text available]';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
render(width: number): string[] {
|
|
35
|
+
const bodyText = this.props.text.length > 0 ? this.props.text : '[No readable text available]';
|
|
37
36
|
|
|
38
37
|
const availableWidth = Math.max(20, width - 4);
|
|
39
38
|
const rawHeaderLines = [
|
package/whatsapp-pi.ts
CHANGED
|
@@ -60,7 +60,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
// Initial status setup
|
|
63
|
-
pi.on("session_start", async (
|
|
63
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
64
64
|
_ctx = ctx;
|
|
65
65
|
// Check verbose mode
|
|
66
66
|
const isVerboseFlagSet = process.argv.includes("--verbose");
|
|
@@ -86,10 +86,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
86
86
|
await whatsappService.stop();
|
|
87
87
|
}
|
|
88
88
|
};
|
|
89
|
-
whatsappService.setIncomingMessageRecorder(async (message) => {
|
|
90
|
-
await recentsService.recordMessage({
|
|
91
|
-
messageId: message.id,
|
|
92
|
-
senderNumber: `+${message.remoteJid.split('@')[0]}`,
|
|
89
|
+
whatsappService.setIncomingMessageRecorder(async (message) => {
|
|
90
|
+
await recentsService.recordMessage({
|
|
91
|
+
messageId: message.id,
|
|
92
|
+
senderNumber: `+${message.remoteJid.split('@')[0]}`,
|
|
93
93
|
senderName: message.pushName,
|
|
94
94
|
text: message.text || '',
|
|
95
95
|
direction: 'incoming',
|
|
@@ -97,19 +97,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
-
const savedStateEntry = [...ctx.sessionManager.getEntries()]
|
|
101
|
-
.reverse()
|
|
102
|
-
.find(entry => entry.type === "custom" && entry.customType === "whatsapp-state");
|
|
103
|
-
const isWhatsappPiOn =
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
100
|
+
const savedStateEntry = [...ctx.sessionManager.getEntries()]
|
|
101
|
+
.reverse()
|
|
102
|
+
.find(entry => entry.type === "custom" && entry.customType === "whatsapp-state");
|
|
103
|
+
const isWhatsappPiOn = pi.getFlag("whatsapp-pi-online") === true;
|
|
104
|
+
const registered = await sessionManager.isRegistered();
|
|
105
|
+
|
|
106
|
+
if (savedStateEntry) {
|
|
107
|
+
const data = (savedStateEntry as { data?: any }).data;
|
|
108
|
+
if (data.status) {
|
|
109
|
+
const restoredStatus = data.status === 'connected' && !(isWhatsappPiOn && registered)
|
|
110
|
+
? 'disconnected'
|
|
111
|
+
: data.status;
|
|
112
|
+
await sessionManager.setStatus(restoredStatus);
|
|
113
|
+
}
|
|
113
114
|
if (Array.isArray(data.allowList)) {
|
|
114
115
|
for (const n of data.allowList) {
|
|
115
116
|
const num = typeof n === "string" ? n : n.number;
|
|
@@ -117,13 +118,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
117
118
|
await sessionManager.addNumber(num, name);
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (isWhatsappPiOn && registered) {
|
|
126
|
-
ctx.ui.setStatus('whatsapp', '| WhatsApp: Auto-connecting...');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (isWhatsappPiOn && registered) {
|
|
124
|
+
ctx.ui.setStatus('whatsapp', '| WhatsApp: Auto-connecting...');
|
|
127
125
|
|
|
128
126
|
// Retry logic (max 3 attempts, 3s delay)
|
|
129
127
|
let attempts = 0;
|
|
@@ -133,7 +131,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
133
131
|
attempts++;
|
|
134
132
|
try {
|
|
135
133
|
await whatsappService.start({ allowPairingOnAuthFailure: false });
|
|
136
|
-
} catch
|
|
134
|
+
} catch {
|
|
137
135
|
if (attempts < maxAttempts) {
|
|
138
136
|
ctx.ui.notify(`WhatsApp: Connection attempt ${attempts} failed. Retrying...`, 'warning');
|
|
139
137
|
setTimeout(tryConnect, 3000);
|
|
@@ -145,9 +143,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
145
143
|
};
|
|
146
144
|
|
|
147
145
|
await tryConnect();
|
|
148
|
-
} else {
|
|
149
|
-
ctx.ui.notify('WhatsApp:
|
|
150
|
-
}
|
|
146
|
+
} else if (isWhatsappPiOn) {
|
|
147
|
+
ctx.ui.notify('WhatsApp: Auto-connect requested, but no saved WhatsApp credentials were found. Use Connect WhatsApp once to scan the QR code.', 'warning');
|
|
148
|
+
} else {
|
|
149
|
+
ctx.ui.notify('WhatsApp: Use Connect / Reconnect WhatsApp. QR code will appear only if pairing is needed.', 'info');
|
|
150
|
+
}
|
|
151
151
|
|
|
152
152
|
ctx.ui.notify('WhatsApp: Session reset via /new is now fully supported.', 'info');
|
|
153
153
|
|
|
@@ -157,7 +157,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
157
157
|
if (code !== 0 && code !== 99) { // 99 is a common exit code for -v in some versions
|
|
158
158
|
throw new Error(`pdftotext returned code ${code}`);
|
|
159
159
|
}
|
|
160
|
-
} catch
|
|
160
|
+
} catch {
|
|
161
161
|
ctx.ui.notify('WhatsApp: pdftotext not found. PDF document support will be limited to storage only.', 'warning');
|
|
162
162
|
logger.warn('[WhatsApp-Pi] Warning: pdftotext not found in system PATH.');
|
|
163
163
|
}
|
|
@@ -165,8 +165,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
165
165
|
|
|
166
166
|
// Handle incoming messages by injecting them as user prompts
|
|
167
167
|
whatsappService.setMessageCallback(async (m) => {
|
|
168
|
-
const msg = m.messages[0];
|
|
169
|
-
if (!msg
|
|
168
|
+
const msg = m.messages?.[0];
|
|
169
|
+
if (!msg?.message) return;
|
|
170
170
|
|
|
171
171
|
const sender = msg.key.remoteJid?.split('@')[0] || "unknown";
|
|
172
172
|
const pushName = msg.pushName || "WhatsApp User";
|
|
@@ -333,7 +333,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
333
333
|
} else {
|
|
334
334
|
ctx.ui.notify(`Failed to send WhatsApp reply`, 'error');
|
|
335
335
|
}
|
|
336
|
-
} catch
|
|
336
|
+
} catch {
|
|
337
337
|
ctx.ui.notify(`Failed to send WhatsApp reply`, 'error');
|
|
338
338
|
}
|
|
339
339
|
}
|