brave-real-browser-mcp-server 2.14.8 → 2.14.9
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/handlers/ai-powered-handlers.js +0 -197
- package/dist/handlers/data-extraction-handlers.js +0 -98
- package/dist/handlers/data-processing-handlers.js +0 -173
- package/dist/handlers/data-quality-handlers.js +0 -220
- package/dist/handlers/dynamic-session-handlers.js +0 -204
- package/dist/handlers/multi-element-handlers.js +0 -55
- package/dist/handlers/pagination-handlers.js +0 -191
- package/dist/index.js +8 -59
- package/dist/mcp-server.js +2 -8
- package/dist/tool-definitions.js +0 -216
- package/package.json +2 -11
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import { getPageInstance } from '../browser-manager.js';
|
|
3
|
-
import natural from 'natural';
|
|
4
|
-
import Sentiment from 'sentiment';
|
|
5
|
-
import { franc } from 'franc';
|
|
6
|
-
const sentiment = new Sentiment();
|
|
7
|
-
const tokenizer = new natural.WordTokenizer();
|
|
8
3
|
/**
|
|
9
4
|
* Smart Selector Generator - AI-powered CSS selector generation
|
|
10
5
|
*/
|
|
@@ -136,201 +131,9 @@ export async function handleContentClassification(args) {
|
|
|
136
131
|
/**
|
|
137
132
|
* Sentiment Analysis - Analyze sentiment of page content
|
|
138
133
|
*/
|
|
139
|
-
export async function handleSentimentAnalysis(args) {
|
|
140
|
-
const { url, selector, text } = args;
|
|
141
|
-
try {
|
|
142
|
-
let contentToAnalyze = text;
|
|
143
|
-
// If no text provided, get from page
|
|
144
|
-
if (!contentToAnalyze) {
|
|
145
|
-
const page = getPageInstance();
|
|
146
|
-
if (!page) {
|
|
147
|
-
throw new Error('Browser not initialized. Call browser_init first.');
|
|
148
|
-
}
|
|
149
|
-
// Navigate if URL provided and different from current
|
|
150
|
-
if (url && page.url() !== url) {
|
|
151
|
-
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
|
|
152
|
-
}
|
|
153
|
-
// Extract content from selector or entire page
|
|
154
|
-
if (selector) {
|
|
155
|
-
contentToAnalyze = await page.evaluate((sel) => {
|
|
156
|
-
const element = document.querySelector(sel);
|
|
157
|
-
return element ? element.textContent : '';
|
|
158
|
-
}, selector);
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
contentToAnalyze = await page.evaluate(() => document.body.innerText);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
if (!contentToAnalyze) {
|
|
165
|
-
throw new Error('No content to analyze');
|
|
166
|
-
}
|
|
167
|
-
const result = sentiment.analyze(contentToAnalyze);
|
|
168
|
-
// Additional analysis using natural
|
|
169
|
-
const tokens = tokenizer.tokenize(contentToAnalyze);
|
|
170
|
-
const sentenceTokenizer = new natural.SentenceTokenizer();
|
|
171
|
-
const sentences = sentenceTokenizer.tokenize(contentToAnalyze);
|
|
172
|
-
// Classify sentiment per sentence
|
|
173
|
-
const sentenceSentiments = sentences.map(sentence => {
|
|
174
|
-
const s = sentiment.analyze(sentence);
|
|
175
|
-
return {
|
|
176
|
-
sentence: sentence.substring(0, 100),
|
|
177
|
-
score: s.score,
|
|
178
|
-
sentiment: s.score > 0 ? 'positive' : s.score < 0 ? 'negative' : 'neutral'
|
|
179
|
-
};
|
|
180
|
-
});
|
|
181
|
-
const stats = {
|
|
182
|
-
totalSentences: sentences.length,
|
|
183
|
-
positiveSentences: sentenceSentiments.filter(s => s.sentiment === 'positive').length,
|
|
184
|
-
negativeSentences: sentenceSentiments.filter(s => s.sentiment === 'negative').length,
|
|
185
|
-
neutralSentences: sentenceSentiments.filter(s => s.sentiment === 'neutral').length
|
|
186
|
-
};
|
|
187
|
-
const resultText = `✅ Sentiment Analysis\n\nOverall Sentiment: ${result.score > 0 ? 'Positive' : result.score < 0 ? 'Negative' : 'Neutral'}\nScore: ${result.score}\nComparative: ${result.comparative.toFixed(4)}\n\nStatistics:\n- Total Sentences: ${stats.totalSentences}\n- Positive: ${stats.positiveSentences}\n- Negative: ${stats.negativeSentences}\n- Neutral: ${stats.neutralSentences}\n\nPositive Words: ${result.positive.join(', ')}\nNegative Words: ${result.negative.join(', ')}`;
|
|
188
|
-
return {
|
|
189
|
-
content: [{
|
|
190
|
-
type: 'text',
|
|
191
|
-
text: resultText
|
|
192
|
-
}]
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
catch (error) {
|
|
196
|
-
return { content: [{ type: 'text', text: `? Error: ${error.message}` }], isError: true };
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
134
|
/**
|
|
200
135
|
* Summary Generator - Generate summary of page content
|
|
201
136
|
*/
|
|
202
|
-
export async function handleSummaryGenerator(args) {
|
|
203
|
-
const { url, maxSentences = 5, selector } = args;
|
|
204
|
-
try {
|
|
205
|
-
const page = getPageInstance();
|
|
206
|
-
if (!page) {
|
|
207
|
-
throw new Error('Browser not initialized. Call browser_init first.');
|
|
208
|
-
}
|
|
209
|
-
if (url && page.url() !== url) {
|
|
210
|
-
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
|
|
211
|
-
}
|
|
212
|
-
let content;
|
|
213
|
-
if (selector) {
|
|
214
|
-
content = await page.evaluate((sel) => {
|
|
215
|
-
const element = document.querySelector(sel);
|
|
216
|
-
return element ? element.textContent : '';
|
|
217
|
-
}, selector);
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
content = await page.evaluate(() => {
|
|
221
|
-
// Extract main content
|
|
222
|
-
const main = document.querySelector('main, article, .content, .post, #content');
|
|
223
|
-
if (main)
|
|
224
|
-
return main.textContent || '';
|
|
225
|
-
return document.body.innerText;
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
if (!content) {
|
|
229
|
-
throw new Error('No content found');
|
|
230
|
-
}
|
|
231
|
-
// Use TF-IDF for extractive summarization
|
|
232
|
-
const TfIdf = natural.TfIdf;
|
|
233
|
-
const tfidf = new TfIdf();
|
|
234
|
-
const sentenceTokenizer = new natural.SentenceTokenizer();
|
|
235
|
-
const sentences = sentenceTokenizer.tokenize(content);
|
|
236
|
-
if (sentences.length === 0) {
|
|
237
|
-
throw new Error('No sentences found to summarize');
|
|
238
|
-
}
|
|
239
|
-
// Add each sentence as a document
|
|
240
|
-
sentences.forEach(sentence => {
|
|
241
|
-
tfidf.addDocument(sentence);
|
|
242
|
-
});
|
|
243
|
-
// Score each sentence
|
|
244
|
-
const sentenceScores = sentences.map((sentence, idx) => {
|
|
245
|
-
let score = 0;
|
|
246
|
-
tfidf.listTerms(idx).forEach(term => {
|
|
247
|
-
score += term.tfidf;
|
|
248
|
-
});
|
|
249
|
-
return { sentence, score, index: idx };
|
|
250
|
-
});
|
|
251
|
-
// Sort by score and take top N
|
|
252
|
-
sentenceScores.sort((a, b) => b.score - a.score);
|
|
253
|
-
const topSentences = sentenceScores.slice(0, maxSentences);
|
|
254
|
-
// Sort by original order
|
|
255
|
-
topSentences.sort((a, b) => a.index - b.index);
|
|
256
|
-
const summary = topSentences.map(s => s.sentence).join(' ');
|
|
257
|
-
const compressionRatio = (summary.length / content.length * 100).toFixed(2);
|
|
258
|
-
const resultText = `✅ Summary Generated\n\nSummary:\n${summary}\n\nStatistics:\n- Original Length: ${content.length} characters\n- Summary Length: ${summary.length} characters\n- Compression Ratio: ${compressionRatio}%\n- Original Sentences: ${sentences.length}\n- Summary Sentences: ${topSentences.length}`;
|
|
259
|
-
return {
|
|
260
|
-
content: [{
|
|
261
|
-
type: 'text',
|
|
262
|
-
text: resultText
|
|
263
|
-
}]
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
catch (error) {
|
|
267
|
-
return { content: [{ type: 'text', text: `? Error: ${error.message}` }], isError: true };
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
137
|
/**
|
|
271
138
|
* Translation Support - Detect language and provide translation info
|
|
272
139
|
*/
|
|
273
|
-
export async function handleTranslationSupport(args) {
|
|
274
|
-
const { url, selector, text, targetLanguage = 'en' } = args;
|
|
275
|
-
try {
|
|
276
|
-
let contentToTranslate = text;
|
|
277
|
-
if (!contentToTranslate && url) {
|
|
278
|
-
const page = getPageInstance();
|
|
279
|
-
if (!page) {
|
|
280
|
-
throw new Error('Browser not initialized. Call browser_init first.');
|
|
281
|
-
}
|
|
282
|
-
if (page.url() !== url) {
|
|
283
|
-
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
|
|
284
|
-
}
|
|
285
|
-
if (selector) {
|
|
286
|
-
contentToTranslate = await page.evaluate((sel) => {
|
|
287
|
-
const element = document.querySelector(sel);
|
|
288
|
-
return element ? element.textContent : '';
|
|
289
|
-
}, selector);
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
contentToTranslate = await page.evaluate(() => document.body.innerText);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
if (!contentToTranslate) {
|
|
296
|
-
throw new Error('No content to translate');
|
|
297
|
-
}
|
|
298
|
-
// Detect language using franc
|
|
299
|
-
const detectedLang = franc(contentToTranslate, { minLength: 10 });
|
|
300
|
-
// Get language name
|
|
301
|
-
const langNames = {
|
|
302
|
-
'eng': 'English',
|
|
303
|
-
'spa': 'Spanish',
|
|
304
|
-
'fra': 'French',
|
|
305
|
-
'deu': 'German',
|
|
306
|
-
'ita': 'Italian',
|
|
307
|
-
'por': 'Portuguese',
|
|
308
|
-
'rus': 'Russian',
|
|
309
|
-
'jpn': 'Japanese',
|
|
310
|
-
'kor': 'Korean',
|
|
311
|
-
'cmn': 'Chinese (Mandarin)',
|
|
312
|
-
'ara': 'Arabic',
|
|
313
|
-
'hin': 'Hindi',
|
|
314
|
-
'und': 'Undetermined'
|
|
315
|
-
};
|
|
316
|
-
const languageName = langNames[detectedLang] || detectedLang;
|
|
317
|
-
// Extract key phrases using TF-IDF
|
|
318
|
-
const TfIdf = natural.TfIdf;
|
|
319
|
-
const tfidf = new TfIdf();
|
|
320
|
-
tfidf.addDocument(contentToTranslate);
|
|
321
|
-
const keyPhrases = tfidf.listTerms(0)
|
|
322
|
-
.slice(0, 10)
|
|
323
|
-
.map(term => term.term);
|
|
324
|
-
const needsTranslation = detectedLang !== targetLanguage && detectedLang !== 'und';
|
|
325
|
-
const resultText = `✅ Translation Support\n\nDetected Language: ${languageName} (${detectedLang})\nTarget Language: ${targetLanguage}\nNeeds Translation: ${needsTranslation ? 'Yes' : 'No'}\n\nContent Length: ${contentToTranslate.length} characters\nContent Preview: ${contentToTranslate.substring(0, 200)}...\n\nKey Phrases: ${keyPhrases.join(', ')}\n\nNote: Use external translation API (Google Translate, DeepL) for actual translation`;
|
|
326
|
-
return {
|
|
327
|
-
content: [{
|
|
328
|
-
type: 'text',
|
|
329
|
-
text: resultText
|
|
330
|
-
}]
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
catch (error) {
|
|
334
|
-
return { content: [{ type: 'text', text: `? Error: ${error.message}` }], isError: true };
|
|
335
|
-
}
|
|
336
|
-
}
|
|
@@ -5,104 +5,6 @@
|
|
|
5
5
|
import { getCurrentPage } from '../browser-manager.js';
|
|
6
6
|
import { validateWorkflow } from '../workflow-validation.js';
|
|
7
7
|
import { withErrorHandling } from '../system-utils.js';
|
|
8
|
-
/**
|
|
9
|
-
* HTML Tables से structured data extract करता है
|
|
10
|
-
*/
|
|
11
|
-
export async function handleScrapeTable(args) {
|
|
12
|
-
return await withErrorHandling(async () => {
|
|
13
|
-
// Workflow validation
|
|
14
|
-
validateWorkflow('scrape_table', {
|
|
15
|
-
requireBrowser: true,
|
|
16
|
-
requirePage: true,
|
|
17
|
-
});
|
|
18
|
-
const page = getCurrentPage();
|
|
19
|
-
const selector = args.selector || 'table';
|
|
20
|
-
const includeHeaders = args.includeHeaders !== false;
|
|
21
|
-
const cleanData = args.cleanData !== false;
|
|
22
|
-
const maxRows = args.maxRows || 1000;
|
|
23
|
-
// Extract table data from page
|
|
24
|
-
const tableData = await page.evaluate(({ selector, includeHeaders, cleanData, maxRows }) => {
|
|
25
|
-
const tables = document.querySelectorAll(selector);
|
|
26
|
-
const results = [];
|
|
27
|
-
tables.forEach((table) => {
|
|
28
|
-
const headers = [];
|
|
29
|
-
const rows = [];
|
|
30
|
-
// Extract headers
|
|
31
|
-
if (includeHeaders) {
|
|
32
|
-
const headerCells = table.querySelectorAll('thead th, thead td');
|
|
33
|
-
headerCells.forEach((cell) => {
|
|
34
|
-
const text = cell.textContent?.trim() || '';
|
|
35
|
-
headers.push(cleanData ? text.replace(/\s+/g, ' ') : text);
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
// If no headers found in thead, try first row
|
|
39
|
-
if (headers.length === 0) {
|
|
40
|
-
const firstRow = table.querySelector('tr');
|
|
41
|
-
if (firstRow) {
|
|
42
|
-
const cells = firstRow.querySelectorAll('th, td');
|
|
43
|
-
cells.forEach((cell) => {
|
|
44
|
-
const text = cell.textContent?.trim() || '';
|
|
45
|
-
headers.push(cleanData ? text.replace(/\s+/g, ' ') : text);
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
// Extract rows
|
|
50
|
-
const bodyRows = table.querySelectorAll('tbody tr, tr');
|
|
51
|
-
let rowCount = 0;
|
|
52
|
-
bodyRows.forEach((row) => {
|
|
53
|
-
if (rowCount >= maxRows)
|
|
54
|
-
return;
|
|
55
|
-
const cells = row.querySelectorAll('td, th');
|
|
56
|
-
if (cells.length === 0)
|
|
57
|
-
return;
|
|
58
|
-
const rowData = {};
|
|
59
|
-
cells.forEach((cell, index) => {
|
|
60
|
-
const text = cell.textContent?.trim() || '';
|
|
61
|
-
const cleanedText = cleanData ? text.replace(/\s+/g, ' ') : text;
|
|
62
|
-
const header = headers[index] || `column_${index}`;
|
|
63
|
-
// Try to parse as number
|
|
64
|
-
const numValue = parseFloat(cleanedText);
|
|
65
|
-
rowData[header] = isNaN(numValue) ? cleanedText : numValue;
|
|
66
|
-
});
|
|
67
|
-
if (Object.keys(rowData).length > 0) {
|
|
68
|
-
rows.push(rowData);
|
|
69
|
-
rowCount++;
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
if (rows.length > 0) {
|
|
73
|
-
results.push({
|
|
74
|
-
headers,
|
|
75
|
-
rows,
|
|
76
|
-
summary: {
|
|
77
|
-
totalRows: rows.length,
|
|
78
|
-
totalColumns: headers.length,
|
|
79
|
-
extractedAt: new Date().toISOString(),
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
return results;
|
|
85
|
-
}, { selector, includeHeaders, cleanData, maxRows });
|
|
86
|
-
if (tableData.length === 0) {
|
|
87
|
-
return {
|
|
88
|
-
content: [
|
|
89
|
-
{
|
|
90
|
-
type: 'text',
|
|
91
|
-
text: `❌ No tables found with selector "${selector}"`,
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
return {
|
|
97
|
-
content: [
|
|
98
|
-
{
|
|
99
|
-
type: 'text',
|
|
100
|
-
text: `✅ Extracted ${tableData.length} table(s)\n\n${JSON.stringify(tableData, null, 2)}`,
|
|
101
|
-
},
|
|
102
|
-
],
|
|
103
|
-
};
|
|
104
|
-
}, 'Failed to scrape table');
|
|
105
|
-
}
|
|
106
8
|
/**
|
|
107
9
|
* Bullet lists और numbered lists से data extract करता है
|
|
108
10
|
*/
|
|
@@ -2,44 +2,6 @@
|
|
|
2
2
|
// Text cleaning, validation, formatting utilities
|
|
3
3
|
// @ts-nocheck
|
|
4
4
|
import { withErrorHandling } from '../system-utils.js';
|
|
5
|
-
import { parsePhoneNumber } from 'libphonenumber-js';
|
|
6
|
-
import Ajv from 'ajv/dist/2020.js';
|
|
7
|
-
/**
|
|
8
|
-
* Extra whitespace और special characters remove करता है
|
|
9
|
-
*/
|
|
10
|
-
export async function handleSmartTextCleaner(args) {
|
|
11
|
-
return await withErrorHandling(async () => {
|
|
12
|
-
let text = args.text;
|
|
13
|
-
const removeExtraWhitespace = args.removeExtraWhitespace !== false;
|
|
14
|
-
const removeSpecialChars = args.removeSpecialChars || false;
|
|
15
|
-
const toLowerCase = args.toLowerCase || false;
|
|
16
|
-
const trim = args.trim !== false;
|
|
17
|
-
// Remove extra whitespace
|
|
18
|
-
if (removeExtraWhitespace) {
|
|
19
|
-
text = text.replace(/\s+/g, ' ');
|
|
20
|
-
}
|
|
21
|
-
// Remove special characters
|
|
22
|
-
if (removeSpecialChars) {
|
|
23
|
-
text = text.replace(/[^\w\s]/g, '');
|
|
24
|
-
}
|
|
25
|
-
// Convert to lowercase
|
|
26
|
-
if (toLowerCase) {
|
|
27
|
-
text = text.toLowerCase();
|
|
28
|
-
}
|
|
29
|
-
// Trim
|
|
30
|
-
if (trim) {
|
|
31
|
-
text = text.trim();
|
|
32
|
-
}
|
|
33
|
-
return {
|
|
34
|
-
content: [
|
|
35
|
-
{
|
|
36
|
-
type: 'text',
|
|
37
|
-
text: `✅ Text cleaned\n\nOriginal length: ${args.text.length}\nCleaned length: ${text.length}\n\nCleaned text:\n${text}`,
|
|
38
|
-
},
|
|
39
|
-
],
|
|
40
|
-
};
|
|
41
|
-
}, 'Failed to clean text');
|
|
42
|
-
}
|
|
43
5
|
/**
|
|
44
6
|
* HTML tags intelligently remove करता है
|
|
45
7
|
*/
|
|
@@ -84,141 +46,6 @@ export async function handleHTMLToText(args) {
|
|
|
84
46
|
};
|
|
85
47
|
}, 'Failed to convert HTML to text');
|
|
86
48
|
}
|
|
87
|
-
/**
|
|
88
|
-
* Contact information automatically detect करता है
|
|
89
|
-
*/
|
|
90
|
-
export async function handleContactExtractor(args) {
|
|
91
|
-
return await withErrorHandling(async () => {
|
|
92
|
-
const text = args.text;
|
|
93
|
-
const types = args.types || ['phone', 'email'];
|
|
94
|
-
const defaultCountry = args.defaultCountry || 'US';
|
|
95
|
-
const results = {
|
|
96
|
-
phones: [],
|
|
97
|
-
emails: [],
|
|
98
|
-
};
|
|
99
|
-
// Extract emails
|
|
100
|
-
if (types.includes('email')) {
|
|
101
|
-
const emailPattern = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g;
|
|
102
|
-
const emails = text.match(emailPattern) || [];
|
|
103
|
-
results.emails = [...new Set(emails)].map((email) => ({
|
|
104
|
-
value: email,
|
|
105
|
-
domain: email.split('@')[1],
|
|
106
|
-
username: email.split('@')[0],
|
|
107
|
-
}));
|
|
108
|
-
}
|
|
109
|
-
// Extract phone numbers
|
|
110
|
-
if (types.includes('phone')) {
|
|
111
|
-
// Common phone patterns
|
|
112
|
-
const phonePatterns = [
|
|
113
|
-
/\+?\d{1,3}[-.\s]?\(?\d{1,4}\)?[-.\s]?\d{1,4}[-.\s]?\d{1,9}/g,
|
|
114
|
-
/\(\d{3}\)\s?\d{3}-?\d{4}/g,
|
|
115
|
-
/\d{3}[-.\s]?\d{3}[-.\s]?\d{4}/g,
|
|
116
|
-
];
|
|
117
|
-
const potentialPhones = [];
|
|
118
|
-
phonePatterns.forEach((pattern) => {
|
|
119
|
-
const matches = text.match(pattern) || [];
|
|
120
|
-
potentialPhones.push(...matches);
|
|
121
|
-
});
|
|
122
|
-
// Parse and validate phone numbers
|
|
123
|
-
potentialPhones.forEach((phone) => {
|
|
124
|
-
try {
|
|
125
|
-
const parsed = parsePhoneNumber(phone, defaultCountry);
|
|
126
|
-
if (parsed && parsed.isValid()) {
|
|
127
|
-
results.phones.push({
|
|
128
|
-
original: phone,
|
|
129
|
-
international: parsed.formatInternational(),
|
|
130
|
-
national: parsed.formatNational(),
|
|
131
|
-
country: parsed.country,
|
|
132
|
-
type: parsed.getType(),
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
catch (e) {
|
|
137
|
-
// Invalid phone number, skip
|
|
138
|
-
results.phones.push({
|
|
139
|
-
original: phone,
|
|
140
|
-
valid: false,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
return {
|
|
146
|
-
content: [
|
|
147
|
-
{
|
|
148
|
-
type: 'text',
|
|
149
|
-
text: `✅ Extracted contact info\n\nEmails: ${results.emails.length}\nPhones: ${results.phones.length}\n\n${JSON.stringify(results, null, 2)}`,
|
|
150
|
-
},
|
|
151
|
-
],
|
|
152
|
-
};
|
|
153
|
-
}, 'Failed to extract contact info');
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Extracted data की structure verify करता है
|
|
157
|
-
*/
|
|
158
|
-
export async function handleSchemaValidator(args) {
|
|
159
|
-
return await withErrorHandling(async () => {
|
|
160
|
-
const data = args.data;
|
|
161
|
-
const schema = args.schema;
|
|
162
|
-
const ajv = new Ajv({ allErrors: true });
|
|
163
|
-
const validate = ajv.compile(schema);
|
|
164
|
-
const valid = validate(data);
|
|
165
|
-
if (valid) {
|
|
166
|
-
return {
|
|
167
|
-
content: [
|
|
168
|
-
{
|
|
169
|
-
type: 'text',
|
|
170
|
-
text: '✅ Data validation passed\n\nAll fields match the schema.',
|
|
171
|
-
},
|
|
172
|
-
],
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
const errors = validate.errors?.map((err) => ({
|
|
177
|
-
field: err.instancePath,
|
|
178
|
-
message: err.message,
|
|
179
|
-
params: err.params,
|
|
180
|
-
}));
|
|
181
|
-
return {
|
|
182
|
-
content: [
|
|
183
|
-
{
|
|
184
|
-
type: 'text',
|
|
185
|
-
text: `❌ Data validation failed\n\n${JSON.stringify(errors, null, 2)}`,
|
|
186
|
-
},
|
|
187
|
-
],
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
}, 'Failed to validate schema');
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Missing data detect करता है
|
|
194
|
-
*/
|
|
195
|
-
export async function handleRequiredFieldsChecker(args) {
|
|
196
|
-
return await withErrorHandling(async () => {
|
|
197
|
-
const data = args.data;
|
|
198
|
-
const requiredFields = args.requiredFields;
|
|
199
|
-
const missing = [];
|
|
200
|
-
const present = [];
|
|
201
|
-
requiredFields.forEach((field) => {
|
|
202
|
-
// Support nested fields with dot notation
|
|
203
|
-
const value = field.split('.').reduce((obj, key) => obj?.[key], data);
|
|
204
|
-
if (value === undefined || value === null || value === '') {
|
|
205
|
-
missing.push(field);
|
|
206
|
-
}
|
|
207
|
-
else {
|
|
208
|
-
present.push(field);
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
const isValid = missing.length === 0;
|
|
212
|
-
return {
|
|
213
|
-
content: [
|
|
214
|
-
{
|
|
215
|
-
type: 'text',
|
|
216
|
-
text: `${isValid ? '✅' : '❌'} Required fields check\n\nPresent: ${present.length}/${requiredFields.length}\nMissing: ${missing.join(', ') || 'None'}\n\n${isValid ? 'All required fields are present!' : 'Some required fields are missing.'}`,
|
|
217
|
-
},
|
|
218
|
-
],
|
|
219
|
-
};
|
|
220
|
-
}, 'Failed to check required fields');
|
|
221
|
-
}
|
|
222
49
|
/**
|
|
223
50
|
* Repeated data filter करता है
|
|
224
51
|
*/
|