waengine 1.0.4 → 1.0.6
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 +1 -1
- package/src/client.js +104 -61
- package/src/session-manager.js +154 -0
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -2,6 +2,7 @@ import { makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaile
|
|
|
2
2
|
import pino from "pino";
|
|
3
3
|
import { generateQRCode, closeBrowser } from "./qr.js";
|
|
4
4
|
import { Message } from "./message.js";
|
|
5
|
+
import { SessionManager } from "./session-manager.js";
|
|
5
6
|
|
|
6
7
|
export class WhatsAppClient {
|
|
7
8
|
constructor(options = {}) {
|
|
@@ -10,6 +11,7 @@ export class WhatsAppClient {
|
|
|
10
11
|
printQR: false,
|
|
11
12
|
browser: options.browser || ["Chrome", "121.0.0", ""], // Aktueller Chrome
|
|
12
13
|
logLevel: options.logLevel || "silent", // Sauber ohne Debug
|
|
14
|
+
autoCleanup: options.autoCleanup !== false, // Auto-Cleanup bei Logout
|
|
13
15
|
...options
|
|
14
16
|
};
|
|
15
17
|
|
|
@@ -19,6 +21,9 @@ export class WhatsAppClient {
|
|
|
19
21
|
this.isConnected = false;
|
|
20
22
|
this.eventHandlers = new Map();
|
|
21
23
|
|
|
24
|
+
// Session Manager
|
|
25
|
+
this.sessionManager = new SessionManager(this.options.authDir);
|
|
26
|
+
|
|
22
27
|
// Prefix System
|
|
23
28
|
this.prefix = null;
|
|
24
29
|
this.commands = new Map();
|
|
@@ -34,10 +39,27 @@ export class WhatsAppClient {
|
|
|
34
39
|
// ===== CONNECTION METHODS =====
|
|
35
40
|
|
|
36
41
|
async connect() {
|
|
42
|
+
console.log("🔌 WAEngine startet...");
|
|
43
|
+
|
|
37
44
|
if (this.socket && this.isConnected) {
|
|
38
45
|
return this.socket;
|
|
39
46
|
}
|
|
40
47
|
|
|
48
|
+
// Session-Validierung und Auto-Repair
|
|
49
|
+
await this.sessionManager.ensureAuthDir();
|
|
50
|
+
const sessionValidation = await this.sessionManager.validateSession();
|
|
51
|
+
|
|
52
|
+
if (!sessionValidation.valid) {
|
|
53
|
+
console.log(`📋 Session-Status: ${sessionValidation.reason}`);
|
|
54
|
+
|
|
55
|
+
if (sessionValidation.reason === 'corrupted') {
|
|
56
|
+
console.log("🔧 Repariere korrupte Session...");
|
|
57
|
+
await this.sessionManager.repairSession();
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
console.log(`✅ Gültige Session gefunden (User: ${sessionValidation.userId})`);
|
|
61
|
+
}
|
|
62
|
+
|
|
41
63
|
const { state, saveCreds } = await useMultiFileAuthState(this.options.authDir);
|
|
42
64
|
const isLoggedIn = !!state.creds?.me?.id;
|
|
43
65
|
|
|
@@ -87,7 +109,7 @@ export class WhatsAppClient {
|
|
|
87
109
|
});
|
|
88
110
|
}
|
|
89
111
|
|
|
90
|
-
console.log("🔌
|
|
112
|
+
console.log("🔌 WAEngine startet...");
|
|
91
113
|
|
|
92
114
|
// QR-Code Browser nur öffnen wenn nicht eingeloggt
|
|
93
115
|
if (!this.options.printQR && !isLoggedIn) {
|
|
@@ -99,10 +121,7 @@ export class WhatsAppClient {
|
|
|
99
121
|
this.setupEventHandlers();
|
|
100
122
|
|
|
101
123
|
this.socket.ev.on("connection.update", async ({ connection, lastDisconnect, qr }) => {
|
|
102
|
-
console.log(`🔄 Connection Update: ${connection}`);
|
|
103
|
-
|
|
104
124
|
if (qr) {
|
|
105
|
-
console.log("📱 QR-Code empfangen");
|
|
106
125
|
if (this.options.printQR) {
|
|
107
126
|
// Terminal QR
|
|
108
127
|
console.log("\n📱 QR-CODE IM TERMINAL:");
|
|
@@ -113,28 +132,37 @@ export class WhatsAppClient {
|
|
|
113
132
|
console.log("📲 Scanne den QR-Code mit WhatsApp!");
|
|
114
133
|
} else {
|
|
115
134
|
// Browser QR
|
|
135
|
+
console.log("📱 QR-Code im Browser angezeigt");
|
|
116
136
|
await generateQRCode(qr);
|
|
117
137
|
}
|
|
118
138
|
}
|
|
119
139
|
|
|
120
140
|
if (connection === "connecting") {
|
|
121
|
-
console.log("🔄 Verbinde
|
|
141
|
+
console.log("🔄 Verbinde...");
|
|
122
142
|
}
|
|
123
143
|
|
|
124
144
|
if (connection === "close") {
|
|
125
|
-
console.log("🔴 Verbindung geschlossen");
|
|
126
145
|
const statusCode = lastDisconnect?.error?.output?.statusCode;
|
|
127
146
|
const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
|
|
128
147
|
|
|
129
148
|
if (shouldReconnect) {
|
|
130
|
-
console.log("🔄 Wiederverbindung
|
|
149
|
+
console.log("🔄 Wiederverbindung...");
|
|
131
150
|
this.isConnected = false;
|
|
132
151
|
this.socket = null;
|
|
133
152
|
setTimeout(() => this.connect().then(resolve).catch(reject), 3000);
|
|
134
153
|
} else {
|
|
135
|
-
console.log("👋 Ausgeloggt");
|
|
154
|
+
console.log("👋 Ausgeloggt - bereinige Session...");
|
|
155
|
+
this.isConnected = false;
|
|
156
|
+
this.socket = null;
|
|
157
|
+
|
|
158
|
+
// Auto-Cleanup bei Logout
|
|
159
|
+
if (this.options.autoCleanup) {
|
|
160
|
+
await this.sessionManager.cleanupSession();
|
|
161
|
+
console.log("🧹 Session automatisch bereinigt");
|
|
162
|
+
}
|
|
163
|
+
|
|
136
164
|
await closeBrowser();
|
|
137
|
-
this.emit('disconnected', { reason: 'logged_out' });
|
|
165
|
+
this.emit('disconnected', { reason: 'logged_out', cleaned: this.options.autoCleanup });
|
|
138
166
|
reject(new Error('Logged out'));
|
|
139
167
|
}
|
|
140
168
|
} else if (connection === "open") {
|
|
@@ -167,6 +195,50 @@ export class WhatsAppClient {
|
|
|
167
195
|
}
|
|
168
196
|
}
|
|
169
197
|
|
|
198
|
+
// ===== SESSION MANAGEMENT =====
|
|
199
|
+
|
|
200
|
+
async getSessionStatus() {
|
|
201
|
+
return await this.sessionManager.getSessionStatus();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async validateSession() {
|
|
205
|
+
return await this.sessionManager.validateSession();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async cleanupSession() {
|
|
209
|
+
console.log("🧹 Manuelle Session-Bereinigung...");
|
|
210
|
+
const success = await this.sessionManager.cleanupSession();
|
|
211
|
+
if (success) {
|
|
212
|
+
console.log("✅ Session erfolgreich bereinigt");
|
|
213
|
+
}
|
|
214
|
+
return success;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async repairSession() {
|
|
218
|
+
console.log("🔧 Session-Reparatur...");
|
|
219
|
+
return await this.sessionManager.repairSession();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async backupSession() {
|
|
223
|
+
return await this.sessionManager.backupSession();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Logout mit automatischer Bereinigung
|
|
227
|
+
async logout() {
|
|
228
|
+
if (this.socket) {
|
|
229
|
+
console.log("👋 Logout wird durchgeführt...");
|
|
230
|
+
this.socket.logout();
|
|
231
|
+
|
|
232
|
+
// Warte kurz und bereinige dann
|
|
233
|
+
setTimeout(async () => {
|
|
234
|
+
if (this.options.autoCleanup) {
|
|
235
|
+
await this.sessionManager.cleanupSession();
|
|
236
|
+
console.log("🧹 Session nach Logout bereinigt");
|
|
237
|
+
}
|
|
238
|
+
}, 1000);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
170
242
|
// ===== EVENT SYSTEM =====
|
|
171
243
|
|
|
172
244
|
on(event, handler) {
|
|
@@ -201,49 +273,37 @@ export class WhatsAppClient {
|
|
|
201
273
|
}
|
|
202
274
|
|
|
203
275
|
setupEventHandlers() {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
// Messages - Verbesserte Message-Erkennung
|
|
276
|
+
// Messages - Saubere Message-Erkennung ohne Debug-Spam
|
|
207
277
|
this.socket.ev.on("messages.upsert", ({ messages, type }) => {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
// Alle Message-Types verarbeiten, nicht nur "notify"
|
|
211
|
-
if (type !== "notify" && type !== "append") {
|
|
212
|
-
console.log(`⚠️ Ignoriere Message-Type: ${type}`);
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
278
|
+
// Nur relevante Message-Types verarbeiten
|
|
279
|
+
if (type !== "notify" && type !== "append") return;
|
|
215
280
|
|
|
216
|
-
messages.forEach((msg
|
|
217
|
-
console.log(`🔍 Processing message ${index + 1}/${messages.length}`);
|
|
218
|
-
console.log(` - From: ${msg.key.remoteJid}`);
|
|
219
|
-
console.log(` - FromMe: ${msg.key.fromMe}`);
|
|
220
|
-
console.log(` - Message Keys: ${Object.keys(msg.message || {}).join(', ')}`);
|
|
221
|
-
|
|
281
|
+
messages.forEach((msg) => {
|
|
222
282
|
// Bessere Message-Validierung
|
|
223
|
-
if (!msg.message)
|
|
224
|
-
console.log(" ❌ Keine Message-Daten");
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
283
|
+
if (!msg.message || msg.key.fromMe) return;
|
|
227
284
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
285
|
+
// Ignoriere System-Messages (protocolMessage, etc.)
|
|
286
|
+
if (msg.message.protocolMessage ||
|
|
287
|
+
msg.message.reactionMessage ||
|
|
288
|
+
msg.message.ephemeralMessage ||
|
|
289
|
+
msg.message.viewOnceMessage) {
|
|
290
|
+
return; // Ignoriere ohne Debug-Output
|
|
231
291
|
}
|
|
232
292
|
|
|
233
293
|
// Ignoriere Broadcast-Messages
|
|
234
|
-
if (msg.key.remoteJid && isJidBroadcast(msg.key.remoteJid))
|
|
235
|
-
console.log(" ❌ Broadcast-Message - ignoriert");
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
294
|
+
if (msg.key.remoteJid && isJidBroadcast(msg.key.remoteJid)) return;
|
|
238
295
|
|
|
239
296
|
const messageData = this.parseMessage(msg);
|
|
240
|
-
console.log(` ✅ Text extrahiert: "${messageData.text}"`);
|
|
241
|
-
console.log(` ✅ Type: ${messageData.type}`);
|
|
242
297
|
|
|
243
|
-
// Nur verarbeiten wenn Text vorhanden oder
|
|
244
|
-
if (!messageData.text &&
|
|
245
|
-
|
|
246
|
-
return;
|
|
298
|
+
// Nur verarbeiten wenn Text vorhanden oder unterstützte Typen
|
|
299
|
+
if (!messageData.text &&
|
|
300
|
+
!['image', 'video', 'audio', 'document', 'sticker', 'location', 'contact'].includes(messageData.type)) {
|
|
301
|
+
return; // Ignoriere ohne Debug-Output
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Nur bei echten Nachrichten loggen
|
|
305
|
+
if (messageData.text || messageData.type !== 'unknown') {
|
|
306
|
+
console.log(`📨 ${messageData.type}: "${messageData.text || '[Media]'}" von ${messageData.from}`);
|
|
247
307
|
}
|
|
248
308
|
|
|
249
309
|
const messageObj = new Message(this, messageData);
|
|
@@ -253,7 +313,7 @@ export class WhatsAppClient {
|
|
|
253
313
|
const commandText = messageData.text.slice(this.prefix.length).trim();
|
|
254
314
|
const [command, ...args] = commandText.split(' ');
|
|
255
315
|
|
|
256
|
-
console.log(
|
|
316
|
+
console.log(`⚡ Command: ${this.prefix}${command}`);
|
|
257
317
|
|
|
258
318
|
messageObj.isCommand = true;
|
|
259
319
|
messageObj.command = command.toLowerCase();
|
|
@@ -266,59 +326,42 @@ export class WhatsAppClient {
|
|
|
266
326
|
// Spezifischen Command Handler aufrufen falls vorhanden
|
|
267
327
|
if (this.commands.has(command.toLowerCase())) {
|
|
268
328
|
const handler = this.commands.get(command.toLowerCase());
|
|
269
|
-
console.log(` 🎯 Führe Command-Handler aus: ${command}`);
|
|
270
329
|
try {
|
|
271
330
|
handler(messageObj, args);
|
|
272
331
|
} catch (error) {
|
|
273
332
|
console.error(`❌ Fehler in Command '${command}':`, error);
|
|
274
333
|
}
|
|
275
|
-
} else {
|
|
276
|
-
console.log(` ❓ Kein Handler für Command: ${command}`);
|
|
277
334
|
}
|
|
278
335
|
} else {
|
|
279
336
|
messageObj.isCommand = false;
|
|
280
337
|
messageObj.command = null;
|
|
281
338
|
messageObj.args = [];
|
|
282
|
-
console.log(" 📝 Normale Nachricht");
|
|
283
339
|
}
|
|
284
340
|
|
|
285
|
-
console.log(" 📤 Emittiere Message-Event");
|
|
286
341
|
this.emit('message', messageObj);
|
|
287
|
-
console.log(" ✅ Message verarbeitet");
|
|
288
342
|
});
|
|
289
343
|
});
|
|
290
344
|
|
|
291
|
-
//
|
|
345
|
+
// Andere Events ohne Debug-Spam
|
|
292
346
|
this.socket.ev.on("messages.update", (updates) => {
|
|
293
|
-
console.log(`📝 Messages.update - Count: ${updates.length}`);
|
|
294
347
|
this.emit('messages.update', updates);
|
|
295
348
|
});
|
|
296
349
|
|
|
297
|
-
// Bessere Presence-Handling
|
|
298
350
|
this.socket.ev.on("presence.update", (update) => {
|
|
299
|
-
console.log(`👤 Presence.update - ID: ${update.id}`);
|
|
300
351
|
this.emit('presence.update', update);
|
|
301
352
|
});
|
|
302
353
|
|
|
303
|
-
// Group updates
|
|
304
354
|
this.socket.ev.on("group-participants.update", (update) => {
|
|
305
|
-
console.log(`👥 Group-participants.update - Group: ${update.id}`);
|
|
306
355
|
this.emit('group.participants.update', update);
|
|
307
356
|
});
|
|
308
357
|
|
|
309
|
-
// Chats updates für bessere Synchronisation
|
|
310
358
|
this.socket.ev.on("chats.upsert", (chats) => {
|
|
311
|
-
console.log(`💬 Chats.upsert - Count: ${chats.length}`);
|
|
312
359
|
this.emit('chats.upsert', chats);
|
|
313
360
|
});
|
|
314
361
|
|
|
315
|
-
// Contacts updates
|
|
316
362
|
this.socket.ev.on("contacts.upsert", (contacts) => {
|
|
317
|
-
console.log(`📞 Contacts.upsert - Count: ${contacts.length}`);
|
|
318
363
|
this.emit('contacts.upsert', contacts);
|
|
319
364
|
});
|
|
320
|
-
|
|
321
|
-
console.log("✅ Event-Handler registriert!");
|
|
322
365
|
}
|
|
323
366
|
|
|
324
367
|
parseMessage(msg) {
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// Session Manager für WAEngine v1.0.6
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export class SessionManager {
|
|
6
|
+
constructor(authDir) {
|
|
7
|
+
this.authDir = authDir;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Prüft ob Session existiert und gültig ist
|
|
11
|
+
async validateSession() {
|
|
12
|
+
try {
|
|
13
|
+
if (!fs.existsSync(this.authDir)) {
|
|
14
|
+
return { valid: false, reason: 'no_auth_dir' };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const credsPath = path.join(this.authDir, 'creds.json');
|
|
18
|
+
if (!fs.existsSync(credsPath)) {
|
|
19
|
+
return { valid: false, reason: 'no_creds' };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const creds = JSON.parse(fs.readFileSync(credsPath, 'utf8'));
|
|
23
|
+
|
|
24
|
+
// Prüfe ob User-ID vorhanden (eingeloggt)
|
|
25
|
+
if (!creds.me?.id) {
|
|
26
|
+
return { valid: false, reason: 'not_logged_in' };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Prüfe ob Session nicht zu alt ist (optional)
|
|
30
|
+
const stats = fs.statSync(credsPath);
|
|
31
|
+
const ageInDays = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60 * 24);
|
|
32
|
+
|
|
33
|
+
if (ageInDays > 30) { // Session älter als 30 Tage
|
|
34
|
+
return { valid: false, reason: 'session_too_old' };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
valid: true,
|
|
39
|
+
userId: creds.me.id,
|
|
40
|
+
lastModified: stats.mtime,
|
|
41
|
+
ageInDays: Math.round(ageInDays)
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return { valid: false, reason: 'corrupted', error: error.message };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Bereinigt Session komplett
|
|
50
|
+
async cleanupSession() {
|
|
51
|
+
try {
|
|
52
|
+
if (fs.existsSync(this.authDir)) {
|
|
53
|
+
console.log("🧹 Bereinige Session...");
|
|
54
|
+
|
|
55
|
+
// Alle Auth-Dateien löschen
|
|
56
|
+
const files = fs.readdirSync(this.authDir);
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
const filePath = path.join(this.authDir, file);
|
|
59
|
+
fs.unlinkSync(filePath);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Auth-Ordner löschen
|
|
63
|
+
fs.rmdirSync(this.authDir);
|
|
64
|
+
console.log("✅ Session bereinigt");
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error("❌ Fehler beim Session-Cleanup:", error);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Erstellt Auth-Ordner falls nicht vorhanden
|
|
75
|
+
async ensureAuthDir() {
|
|
76
|
+
if (!fs.existsSync(this.authDir)) {
|
|
77
|
+
fs.mkdirSync(this.authDir, { recursive: true });
|
|
78
|
+
console.log(`📁 Auth-Ordner erstellt: ${this.authDir}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Backup der Session erstellen
|
|
83
|
+
async backupSession() {
|
|
84
|
+
try {
|
|
85
|
+
if (!fs.existsSync(this.authDir)) return false;
|
|
86
|
+
|
|
87
|
+
const backupDir = `${this.authDir}_backup_${Date.now()}`;
|
|
88
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
89
|
+
|
|
90
|
+
const files = fs.readdirSync(this.authDir);
|
|
91
|
+
for (const file of files) {
|
|
92
|
+
const srcPath = path.join(this.authDir, file);
|
|
93
|
+
const destPath = path.join(backupDir, file);
|
|
94
|
+
fs.copyFileSync(srcPath, destPath);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log(`💾 Session-Backup erstellt: ${backupDir}`);
|
|
98
|
+
return backupDir;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error("❌ Backup-Fehler:", error);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Session-Status für Debugging
|
|
106
|
+
getSessionStatus() {
|
|
107
|
+
try {
|
|
108
|
+
if (!fs.existsSync(this.authDir)) {
|
|
109
|
+
return { status: 'no_session', files: [] };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const files = fs.readdirSync(this.authDir);
|
|
113
|
+
const fileDetails = files.map(file => {
|
|
114
|
+
const filePath = path.join(this.authDir, file);
|
|
115
|
+
const stats = fs.statSync(filePath);
|
|
116
|
+
return {
|
|
117
|
+
name: file,
|
|
118
|
+
size: stats.size,
|
|
119
|
+
modified: stats.mtime
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
status: 'session_exists',
|
|
125
|
+
files: fileDetails,
|
|
126
|
+
totalFiles: files.length,
|
|
127
|
+
authDir: this.authDir
|
|
128
|
+
};
|
|
129
|
+
} catch (error) {
|
|
130
|
+
return { status: 'error', error: error.message };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Repariert korrupte Session
|
|
135
|
+
async repairSession() {
|
|
136
|
+
console.log("🔧 Versuche Session-Reparatur...");
|
|
137
|
+
|
|
138
|
+
const validation = await this.validateSession();
|
|
139
|
+
|
|
140
|
+
if (validation.reason === 'corrupted') {
|
|
141
|
+
console.log("🗑️ Korrupte Session erkannt - bereinige...");
|
|
142
|
+
await this.cleanupSession();
|
|
143
|
+
return { repaired: true, action: 'cleanup' };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (validation.reason === 'session_too_old') {
|
|
147
|
+
console.log("⏰ Session zu alt - bereinige...");
|
|
148
|
+
await this.cleanupSession();
|
|
149
|
+
return { repaired: true, action: 'cleanup_old' };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { repaired: false, reason: validation.reason };
|
|
153
|
+
}
|
|
154
|
+
}
|