whatsapp-pi 1.0.43 → 1.0.45
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.45",
|
|
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,92 @@ 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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
+
this.hasAuthState = this.hasAuthState || await this.hasCredentialsFile();
|
|
140
|
+
const config = {
|
|
141
|
+
allowList: this.allowList,
|
|
142
|
+
blockList: this.blockList,
|
|
143
|
+
ignoredNumbers: this.ignoredNumbers,
|
|
77
144
|
status: this.status,
|
|
78
145
|
hasAuthState: this.hasAuthState,
|
|
79
|
-
openaiKey: this.openaiKey,
|
|
80
|
-
visionModel: this.visionModel
|
|
81
|
-
};
|
|
82
|
-
await
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
146
|
+
openaiKey: this.openaiKey,
|
|
147
|
+
visionModel: this.visionModel
|
|
148
|
+
};
|
|
149
|
+
await mkdir(this.baseDir, { recursive: true });
|
|
150
|
+
await writeFile(tempPath, JSON.stringify(config, null, 2));
|
|
151
|
+
await rename(tempPath, this.configPath);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
await rm(tempPath, { force: true }).catch(() => {});
|
|
154
|
+
console.error('Failed to save config:', error);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
87
157
|
|
|
88
158
|
getAllowList(): Contact[] {
|
|
89
159
|
return this.allowList;
|
|
@@ -205,17 +275,10 @@ export class SessionManager {
|
|
|
205
275
|
}
|
|
206
276
|
}
|
|
207
277
|
|
|
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
|
-
}
|
|
278
|
+
public async isRegistered(): Promise<boolean> {
|
|
279
|
+
await this.syncAuthStateFromDisk();
|
|
280
|
+
return this.hasAuthState;
|
|
281
|
+
}
|
|
219
282
|
|
|
220
283
|
async markAuthStateAvailable() {
|
|
221
284
|
if (!this.hasAuthState) {
|
|
@@ -229,19 +292,27 @@ export class SessionManager {
|
|
|
229
292
|
return await useMultiFileAuthState(this.authStateDir);
|
|
230
293
|
}
|
|
231
294
|
|
|
232
|
-
private async syncAuthStateFromDisk() {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
295
|
+
private async syncAuthStateFromDisk() {
|
|
296
|
+
const nextHasAuthState = await this.hasCredentialsFile();
|
|
297
|
+
const nextStatus = nextHasAuthState || this.status !== 'connected'
|
|
298
|
+
? this.status
|
|
299
|
+
: 'disconnected';
|
|
300
|
+
|
|
301
|
+
if (nextHasAuthState !== this.hasAuthState || nextStatus !== this.status) {
|
|
302
|
+
this.hasAuthState = nextHasAuthState;
|
|
303
|
+
this.status = nextStatus;
|
|
304
|
+
await this.saveConfig();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private async hasCredentialsFile(): Promise<boolean> {
|
|
309
|
+
try {
|
|
310
|
+
await readFile(join(this.authStateDir, 'creds.json'));
|
|
311
|
+
return true;
|
|
312
|
+
} catch {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
245
316
|
|
|
246
317
|
async deleteAuthState() {
|
|
247
318
|
try {
|
|
@@ -71,12 +71,16 @@ interface BoomLikeError {
|
|
|
71
71
|
message?: string;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
export class WhatsAppService {
|
|
75
|
-
private
|
|
76
|
-
private
|
|
77
|
-
|
|
78
|
-
private
|
|
79
|
-
private
|
|
74
|
+
export class WhatsAppService {
|
|
75
|
+
private static readonly INITIAL_RECONNECT_DELAY_MS = 5_000;
|
|
76
|
+
private static readonly MAX_RECONNECT_DELAY_MS = 120_000;
|
|
77
|
+
|
|
78
|
+
private socket?: WhatsAppSocketLike;
|
|
79
|
+
private sessionManager: SessionManager;
|
|
80
|
+
private messageSender: MessageSender;
|
|
81
|
+
private isReconnecting = false;
|
|
82
|
+
private reconnectAttempts = 0;
|
|
83
|
+
private verboseMode = false;
|
|
80
84
|
private onIncomingMessageRecorded?: (message: IncomingMessage) => void | Promise<void>;
|
|
81
85
|
private saveCreds?: () => Promise<void>;
|
|
82
86
|
private restoreBaileysConsoleFilter?: () => void;
|
|
@@ -164,12 +168,17 @@ export class WhatsAppService {
|
|
|
164
168
|
return '';
|
|
165
169
|
}
|
|
166
170
|
|
|
167
|
-
private clearReconnectTimeout() {
|
|
168
|
-
if (this.reconnectTimeout) {
|
|
169
|
-
clearTimeout(this.reconnectTimeout);
|
|
170
|
-
this.reconnectTimeout = undefined;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
171
|
+
private clearReconnectTimeout() {
|
|
172
|
+
if (this.reconnectTimeout) {
|
|
173
|
+
clearTimeout(this.reconnectTimeout);
|
|
174
|
+
this.reconnectTimeout = undefined;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private getReconnectDelayMs(): number {
|
|
179
|
+
const delay = WhatsAppService.INITIAL_RECONNECT_DELAY_MS * (2 ** Math.max(0, this.reconnectAttempts - 1));
|
|
180
|
+
return Math.min(delay, WhatsAppService.MAX_RECONNECT_DELAY_MS);
|
|
181
|
+
}
|
|
173
182
|
|
|
174
183
|
private cleanupSocket() {
|
|
175
184
|
this.clearReconnectTimeout();
|
|
@@ -295,7 +304,7 @@ export class WhatsAppService {
|
|
|
295
304
|
private async handlePairingQr(qr: string) {
|
|
296
305
|
this.sessionManager.setStatus('pairing');
|
|
297
306
|
this.onQRCode?.(qr);
|
|
298
|
-
this.onStatusUpdate?.('| WhatsApp:
|
|
307
|
+
this.onStatusUpdate?.('| WhatsApp: Disconnected');
|
|
299
308
|
}
|
|
300
309
|
|
|
301
310
|
private async handleConnectionOpen() {
|
|
@@ -303,10 +312,11 @@ export class WhatsAppService {
|
|
|
303
312
|
console.log('WhatsApp connection successfully opened');
|
|
304
313
|
}
|
|
305
314
|
|
|
306
|
-
this.isReconnecting = false;
|
|
307
|
-
this.
|
|
308
|
-
|
|
309
|
-
await this.
|
|
315
|
+
this.isReconnecting = false;
|
|
316
|
+
this.reconnectAttempts = 0;
|
|
317
|
+
this.clearReconnectTimeout();
|
|
318
|
+
await this.saveCreds?.();
|
|
319
|
+
await this.sessionManager.markAuthStateAvailable();
|
|
310
320
|
this.sessionManager.setStatus('connected');
|
|
311
321
|
this.onStatusUpdate?.('| WhatsApp: Connected');
|
|
312
322
|
}
|
|
@@ -339,56 +349,58 @@ export class WhatsAppService {
|
|
|
339
349
|
console.error(`Connection closed [${statusCode}]. Reconnecting: ${shouldReconnect}`);
|
|
340
350
|
}
|
|
341
351
|
|
|
342
|
-
if (shouldTreatAsLoggedOut) {
|
|
343
|
-
if (
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if (
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
this.
|
|
382
|
-
this.
|
|
383
|
-
this.
|
|
384
|
-
this.
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
this.
|
|
391
|
-
|
|
352
|
+
if (shouldTreatAsLoggedOut) {
|
|
353
|
+
if (this.verboseMode) {
|
|
354
|
+
console.error(`Session rejected [${statusCode}] - preserving auth state`);
|
|
355
|
+
}
|
|
356
|
+
if (isBadMac) {
|
|
357
|
+
if (this.verboseMode) {
|
|
358
|
+
console.error('[WhatsApp-Pi] Bad MAC error detected. Your session keys are corrupted.');
|
|
359
|
+
console.error('[WhatsApp-Pi] Use Logoff (Delete Session) only if reconnect keeps failing.');
|
|
360
|
+
}
|
|
361
|
+
this.onStatusUpdate?.('| WhatsApp: Session Error (Bad MAC)');
|
|
362
|
+
} else if (isAuthRejected && allowPairingOnAuthFailure) {
|
|
363
|
+
this.onStatusUpdate?.('| WhatsApp: Session Preserved (Reconnect Failed)');
|
|
364
|
+
}
|
|
365
|
+
this.cleanupSocket();
|
|
366
|
+
this.isReconnecting = false;
|
|
367
|
+
this.reconnectAttempts = 0;
|
|
368
|
+
await this.sessionManager.setStatus('disconnected');
|
|
369
|
+
if (!isBadMac) {
|
|
370
|
+
this.onStatusUpdate?.('| WhatsApp: Disconnected');
|
|
371
|
+
}
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (statusCode === DisconnectReason.connectionReplaced) {
|
|
376
|
+
if (this.verboseMode) {
|
|
377
|
+
console.error('Connection replaced - another instance connected');
|
|
378
|
+
}
|
|
379
|
+
this.cleanupSocket();
|
|
380
|
+
this.isReconnecting = false;
|
|
381
|
+
this.reconnectAttempts = 0;
|
|
382
|
+
await this.sessionManager.setStatus('disconnected');
|
|
383
|
+
this.onStatusUpdate?.('| WhatsApp: Conflict (Another Instance)');
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (shouldReconnect && !this.isReconnecting) {
|
|
388
|
+
this.isReconnecting = true;
|
|
389
|
+
this.reconnectAttempts++;
|
|
390
|
+
const reconnectDelayMs = this.getReconnectDelayMs();
|
|
391
|
+
this.onStatusUpdate?.('| WhatsApp: Reconnecting...');
|
|
392
|
+
this.clearReconnectTimeout();
|
|
393
|
+
await this.saveCreds?.();
|
|
394
|
+
this.cleanupSocket();
|
|
395
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
396
|
+
this.isReconnecting = false;
|
|
397
|
+
void this.start(options);
|
|
398
|
+
}, reconnectDelayMs);
|
|
399
|
+
} else if (!shouldReconnect) {
|
|
400
|
+
this.reconnectAttempts = 0;
|
|
401
|
+
this.sessionManager.setStatus('logged-out');
|
|
402
|
+
this.onStatusUpdate?.('| WhatsApp: Disconnected');
|
|
403
|
+
}
|
|
392
404
|
}
|
|
393
405
|
|
|
394
406
|
private extractText(message: IncomingMessageContent | undefined): string {
|
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");
|
|
@@ -100,12 +100,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
100
100
|
const savedStateEntry = [...ctx.sessionManager.getEntries()]
|
|
101
101
|
.reverse()
|
|
102
102
|
.find(entry => entry.type === "custom" && entry.customType === "whatsapp-state");
|
|
103
|
-
const isWhatsappPiOn =
|
|
103
|
+
const isWhatsappPiOn = pi.getFlag("whatsapp-pi-online") === true;
|
|
104
|
+
const registered = await sessionManager.isRegistered();
|
|
104
105
|
|
|
105
106
|
if (savedStateEntry) {
|
|
106
107
|
const data = (savedStateEntry as { data?: any }).data;
|
|
107
108
|
if (data.status) {
|
|
108
|
-
const restoredStatus = data.status === 'connected' && !isWhatsappPiOn
|
|
109
|
+
const restoredStatus = data.status === 'connected' && !(isWhatsappPiOn && registered)
|
|
109
110
|
? 'disconnected'
|
|
110
111
|
: data.status;
|
|
111
112
|
await sessionManager.setStatus(restoredStatus);
|
|
@@ -119,9 +120,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
119
120
|
}
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
// Check whatsapp flag — only auto-connect on initial startup, not reloads/new sessions
|
|
123
|
-
const registered = await sessionManager.isRegistered();
|
|
124
|
-
|
|
125
123
|
if (isWhatsappPiOn && registered) {
|
|
126
124
|
ctx.ui.setStatus('whatsapp', '| WhatsApp: Auto-connecting...');
|
|
127
125
|
|
|
@@ -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,6 +143,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
145
143
|
};
|
|
146
144
|
|
|
147
145
|
await tryConnect();
|
|
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
148
|
} else {
|
|
149
149
|
ctx.ui.notify('WhatsApp: Use Connect / Reconnect WhatsApp. QR code will appear only if pairing is needed.', 'info');
|
|
150
150
|
}
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|