lljz66 commited on
Commit
b18bc1b
·
verified ·
1 Parent(s): 8d64ef5

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +270 -117
server.js CHANGED
@@ -11,7 +11,6 @@ import { parse } from 'tldts';
11
  import { chromium } from 'playwright';
12
  import axios from 'axios';
13
  import cron from 'node-cron';
14
- import dns from 'node:dns/promises'; // أضفنا هذا لتحليل الـ IP
15
 
16
  const __filename = fileURLToPath(import.meta.url);
17
  const __dirname = path.dirname(__filename);
@@ -19,59 +18,17 @@ const __dirname = path.dirname(__filename);
19
  const app = express();
20
  const PORT = process.env.PORT || 7860;
21
 
22
- // إعدادات الأمان
23
  app.set('trust proxy', 1);
24
  app.use(helmet({ contentSecurityPolicy: false, frameguard: false }));
25
  app.use(cors());
26
  app.use(express.json({ limit: '10mb' }));
27
 
28
- // --- وظائف المساعدة الجديدة ---
29
-
30
- // 1. وظيفة تحديد الموقع الجغرافي (Geo Mapping)
31
- async function getGeoInfo(domain) {
32
- try {
33
- // تحويل الدومين إلى IP
34
- const lookup = await dns.resolve4(domain);
35
- const ip = lookup[0];
36
-
37
- // استدعاء API مجاني للمواقع الجغرافية (ip-api.com)
38
- const response = await axios.get(`http://ip-api.com/json/${ip}?fields=status,message,country,city,isp,query`, { timeout: 2000 });
39
-
40
- if (response.data.status === 'success') {
41
- return {
42
- ip: response.data.query,
43
- country: response.data.country,
44
- city: response.data.city,
45
- isp: response.data.isp
46
- };
47
- }
48
- } catch (e) {
49
- // console.error(`Geo lookup failed for ${domain}`);
50
- }
51
- return { country: 'Unknown', city: 'Unknown', isp: 'Unknown' };
52
- }
53
-
54
- // 2. وظيفة اكتشاف تسريب البيانات (Leakage Detection)
55
- function detectPIILeak(dataString) {
56
- if (!dataString) return [];
57
-
58
- const leaks = [];
59
- const patterns = {
60
- email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
61
- phone: /\+?\d{10,15}/g,
62
- // يمكنك إضافة أنماط أخرى مثل بطاقات الائتمان هنا
63
- };
64
-
65
- for (const [type, regex] of Object.entries(patterns)) {
66
- const matches = dataString.match(regex);
67
- if (matches) {
68
- leaks.push({ type, count: matches.length });
69
- }
70
- }
71
- return leaks;
72
- }
73
-
74
- // --- الكود الأصلي مع التعديلات المدمجة ---
75
 
76
  let ghosteryDB = null;
77
  let ddgTrackerRadar = { domains: {} };
@@ -93,11 +50,15 @@ function loadDDGTrackerRadar() {
93
  const ddgPath = path.join(__dirname, 'data', 'ddg-tracker-radar.json');
94
  if (existsSync(ddgPath)) {
95
  ddgTrackerRadar = JSON.parse(readFileSync(ddgPath, 'utf-8'));
96
- console.log(`✅ DDG Tracker Radar loaded`);
97
  }
98
  } catch (e) {}
99
  }
100
 
 
 
 
 
101
  async function downloadDDGTrackerRadar() {
102
  try {
103
  const response = await axios.get('https://downloads.vivaldi.com/ddg/tds-v2-current.json', { timeout: 30000 });
@@ -115,12 +76,14 @@ async function downloadDDGTrackerRadar() {
115
  if (!existsSync(dir)) mkdirSync(dir);
116
  writeFileSync(path.join(dir, 'ddg-tracker-radar.json'), JSON.stringify(simplified));
117
  ddgTrackerRadar = simplified;
118
- } catch (e) {}
 
 
 
119
  }
120
 
121
  function normalizeUrl(inputUrl) {
122
- let url = inputUrl?.trim();
123
- if (!url) return null;
124
  if (!url.startsWith('http://') && !url.startsWith('https://')) url = 'https://' + url;
125
  try { return new URL(url).toString(); } catch { return null; }
126
  }
@@ -158,128 +121,318 @@ async function getTosdrGrade(url) {
158
  try {
159
  const hostname = new URL(url).hostname;
160
  if (tosdrCache.has(hostname)) return tosdrCache.get(hostname);
 
161
  const searchResponse = await axios.get(`https://api.tosdr.org/search/v5?query=${encodeURIComponent(hostname)}`, { timeout: 5000 });
162
  const services = searchResponse.data?.services || [];
163
- if (services.length === 0) return null;
 
 
 
 
164
  const serviceId = services[0].id;
165
  const detailResponse = await axios.get(`https://api.tosdr.org/service/v3?id=${serviceId}`, { timeout: 5000 });
166
- const result = { grade: detailResponse.data.rating || 'N/A', name: detailResponse.data.name };
 
 
 
 
 
 
 
167
  tosdrCache.set(hostname, result);
168
  return result;
169
- } catch (e) { return null; }
 
 
170
  }
171
 
172
  async function takeScreenshot(url) {
173
  let browser = null;
174
  try {
175
- browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
176
  const page = await browser.newPage();
177
- await page.goto(url, { waitUntil: 'networkidle', timeout: 20000 });
178
- const buffer = await page.screenshot({ type: 'jpeg', quality: 70 });
 
179
  return `data:image/jpeg;base64,${buffer.toString('base64')}`;
180
- } catch (e) { return null; } finally { if (browser) await browser.close(); }
 
 
 
 
 
181
  }
182
 
183
  async function performBlacklightScan(url) {
184
  try {
185
  const options = {
186
  inUrl: url,
187
- blTests: ['cookies', 'third_party_trackers', 'canvas_fingerprinters', 'key_logging'],
188
  numPages: 1,
189
  defaultWaitUntil: 'networkidle2',
190
  captureHar: true,
 
191
  headless: true,
 
 
192
  };
193
  return await collect(url, options);
194
- } catch (e) { return { error: e.message }; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  }
196
 
197
- // --- API Endpoint ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
 
199
  app.get('/api/scan', async (req, res) => {
200
  const url = normalizeUrl(req.query.url);
201
  if (!url) return res.status(400).json({ error: 'Invalid URL' });
202
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  try {
204
  const startTime = Date.now();
205
 
206
- // تنفيذ الفحوصات الأساسية
207
- const [screenshotData, blacklightData, tosdrData] = await Promise.allSettled([
208
  takeScreenshot(url),
209
  performBlacklightScan(url),
210
- getTosdrGrade(url)
 
 
211
  ]);
212
-
213
- const bl = blacklightData.status === 'fulfilled' ? blacklightData.value : {};
214
 
215
- // 1. معالجة المتتبعات مع الموقع الجغرافي (Geo Mapping)
216
- const rawTrackers = bl.third_party_trackers || [];
217
- const uniqueDomains = [...new Set(rawTrackers.map(t => parse(new URL(t.url).hostname).getHostname()))];
 
 
 
 
 
 
 
 
 
218
 
 
219
  const enrichedTrackers = [];
220
- for (const domain of uniqueDomains.slice(0, 15)) { // تحديد العدد لسرعة الاستجابة
221
- const [ddg, ghostery, geo] = await Promise.all([
222
- getDDGInfo(domain),
223
- getGhosteryInfo(domain),
224
- getGeoInfo(domain) // الميزة الجديدة
225
- ]);
226
-
227
  enrichedTrackers.push({
228
  domain,
229
- owner: ghostery?.organization || ddg?.owner || 'Unknown',
230
- category: ghostery?.category || ddg?.category || 'unknown',
231
- location: geo // إضافة الموقع هنا
232
  });
233
  }
234
-
235
- // 2. اكتشاف تسريب البيانات (Leakage Detection)
236
- // سنفحص جميع روابط المتتبعات بحثاً عن بيانات مسربة
237
- const leaks = [];
238
- rawTrackers.forEach(tracker => {
239
- const foundInUrl = detectPIILeak(tracker.url);
240
- const foundInPostData = detectPIILeak(JSON.stringify(tracker.data || {}));
241
-
242
- if (foundInUrl.length > 0 || foundInPostData.length > 0) {
243
- leaks.push({
244
- target_domain: new URL(tracker.url).hostname,
245
- leaks: [...foundInUrl, ...foundInPostData]
246
- });
247
- }
248
- });
249
-
250
- // بناء الرد النهائي
251
  const summary = {
252
  success: true,
 
253
  url,
 
254
  scan_time_sec: (Date.now() - startTime) / 1000,
255
- privacy_score: { score: 50, grade: 'F' }, // يمكنك تحسين دالة الحساب
256
- trackers: {
257
- count: enrichedTrackers.length,
258
- list: enrichedTrackers
259
- },
260
- data_leaks: {
261
- detected: leaks.length > 0,
262
- count: leaks.length,
263
- details: leaks
264
  },
265
  fingerprinting: {
266
- detected: !!bl.canvas_fingerprinters?.length,
267
- details: bl.canvas_fingerprinters
268
  },
269
- screenshot: screenshotData.status === 'fulfilled' ? screenshotData.value : null,
270
- tosdr: tosdrData.status === 'fulfilled' ? tosdrData.value : null
 
 
 
 
 
 
 
 
 
 
271
  };
272
-
273
  res.json(summary);
274
-
275
  } catch (e) {
 
276
  res.status(500).json({ error: e.message });
277
  }
278
  });
279
 
280
- // تشغيل السيرفر
 
 
281
  (async () => {
282
  await initGhosteryDB();
283
  loadDDGTrackerRadar();
284
- app.listen(PORT, '0.0.0.0', () => console.log(`🚀 Server running on ${PORT}`));
285
- })();
 
 
 
 
 
11
  import { chromium } from 'playwright';
12
  import axios from 'axios';
13
  import cron from 'node-cron';
 
14
 
15
  const __filename = fileURLToPath(import.meta.url);
16
  const __dirname = path.dirname(__filename);
 
18
  const app = express();
19
  const PORT = process.env.PORT || 7860;
20
 
 
21
  app.set('trust proxy', 1);
22
  app.use(helmet({ contentSecurityPolicy: false, frameguard: false }));
23
  app.use(cors());
24
  app.use(express.json({ limit: '10mb' }));
25
 
26
+ app.use('/api/', rateLimit({
27
+ windowMs: 15 * 60 * 1000,
28
+ max: 100,
29
+ standardHeaders: true,
30
+ legacyHeaders: false,
31
+ }));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
  let ghosteryDB = null;
34
  let ddgTrackerRadar = { domains: {} };
 
50
  const ddgPath = path.join(__dirname, 'data', 'ddg-tracker-radar.json');
51
  if (existsSync(ddgPath)) {
52
  ddgTrackerRadar = JSON.parse(readFileSync(ddgPath, 'utf-8'));
53
+ console.log(`✅ DDG Tracker Radar: ${Object.keys(ddgTrackerRadar.domains).length} domains`);
54
  }
55
  } catch (e) {}
56
  }
57
 
58
+ cron.schedule('0 0 * * *', async () => {
59
+ await downloadDDGTrackerRadar();
60
+ });
61
+
62
  async function downloadDDGTrackerRadar() {
63
  try {
64
  const response = await axios.get('https://downloads.vivaldi.com/ddg/tds-v2-current.json', { timeout: 30000 });
 
76
  if (!existsSync(dir)) mkdirSync(dir);
77
  writeFileSync(path.join(dir, 'ddg-tracker-radar.json'), JSON.stringify(simplified));
78
  ddgTrackerRadar = simplified;
79
+ console.log('✅ DDG Tracker Radar updated');
80
+ } catch (e) {
81
+ console.error('DDG download failed:', e.message);
82
+ }
83
  }
84
 
85
  function normalizeUrl(inputUrl) {
86
+ let url = inputUrl.trim();
 
87
  if (!url.startsWith('http://') && !url.startsWith('https://')) url = 'https://' + url;
88
  try { return new URL(url).toString(); } catch { return null; }
89
  }
 
121
  try {
122
  const hostname = new URL(url).hostname;
123
  if (tosdrCache.has(hostname)) return tosdrCache.get(hostname);
124
+
125
  const searchResponse = await axios.get(`https://api.tosdr.org/search/v5?query=${encodeURIComponent(hostname)}`, { timeout: 5000 });
126
  const services = searchResponse.data?.services || [];
127
+ if (services.length === 0) {
128
+ tosdrCache.set(hostname, null);
129
+ return null;
130
+ }
131
+
132
  const serviceId = services[0].id;
133
  const detailResponse = await axios.get(`https://api.tosdr.org/service/v3?id=${serviceId}`, { timeout: 5000 });
134
+ const service = detailResponse.data;
135
+
136
+ const result = {
137
+ grade: service.rating || 'N/A',
138
+ name: service.name,
139
+ points: service.points?.length || 0,
140
+ reviewed: service.is_comprehensively_reviewed || false
141
+ };
142
  tosdrCache.set(hostname, result);
143
  return result;
144
+ } catch (e) {
145
+ return null;
146
+ }
147
  }
148
 
149
  async function takeScreenshot(url) {
150
  let browser = null;
151
  try {
152
+ browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'] });
153
  const page = await browser.newPage();
154
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15000 });
155
+ await page.waitForTimeout(1000);
156
+ const buffer = await page.screenshot({ type: 'jpeg', quality: 85, fullPage: false });
157
  return `data:image/jpeg;base64,${buffer.toString('base64')}`;
158
+ } catch (e) {
159
+ console.error('Screenshot failed:', e.message);
160
+ return null;
161
+ } finally {
162
+ if (browser) await browser.close().catch(() => {});
163
+ }
164
  }
165
 
166
  async function performBlacklightScan(url) {
167
  try {
168
  const options = {
169
  inUrl: url,
170
+ blTests: ['cookies', 'third_party_trackers', 'fb_pixel_events', 'canvas_fingerprinters', 'canvas_font_fingerprinters', 'key_logging', 'session_recorders', 'google_analytics_events', 'twitter_pixel', 'tiktok_pixel'],
171
  numPages: 1,
172
  defaultWaitUntil: 'networkidle2',
173
  captureHar: true,
174
+ saveScreenshots: false,
175
  headless: true,
176
+ defaultTimeout: 60000,
177
+ extraChromiumArgs: ['--disable-blink-features=AutomationControlled','--no-sandbox','--disable-setuid-sandbox','--disable-dev-shm-usage','--disable-gpu','--ignore-certificate-errors']
178
  };
179
  return await collect(url, options);
180
+ } catch (e) {
181
+ console.error('Blacklight scan failed:', e.message);
182
+ return { error: e.message };
183
+ }
184
+ }
185
+
186
+ async function checkHiddenStorage(url) {
187
+ let browser = null;
188
+ try {
189
+ browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'] });
190
+ const page = await browser.newPage();
191
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15000 });
192
+ const storageData = await page.evaluate(() => {
193
+ const ls = [];
194
+ const ss = [];
195
+ for (let i = 0; i < localStorage.length; i++) {
196
+ const key = localStorage.key(i);
197
+ ls.push({ key, value: localStorage.getItem(key) });
198
+ }
199
+ for (let i = 0; i < sessionStorage.length; i++) {
200
+ const key = sessionStorage.key(i);
201
+ ss.push({ key, value: sessionStorage.getItem(key) });
202
+ }
203
+ return {
204
+ localStorage: ls,
205
+ sessionStorage: ss,
206
+ indexedDB: !!window.indexedDB
207
+ };
208
+ });
209
+ return storageData;
210
+ } catch (e) {
211
+ console.error('Hidden storage check failed:', e.message);
212
+ return null;
213
+ } finally {
214
+ if (browser) await browser.close().catch(() => {});
215
+ }
216
+ }
217
+
218
+ async function performSecurityCheck(url) {
219
+ try {
220
+ const https = await import('node:https');
221
+ const { hostname } = new URL(url);
222
+
223
+ const cert = await new Promise((resolve, reject) => {
224
+ const req = https.request({ hostname, port: 443, method: 'HEAD', timeout: 5000 }, (res) => {
225
+ const cert = res.socket.getPeerCertificate();
226
+ resolve(cert);
227
+ });
228
+ req.on('error', reject);
229
+ req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
230
+ req.end();
231
+ });
232
+
233
+ const valid = cert && Object.keys(cert).length > 0;
234
+ const issuer = cert.issuer?.O || cert.issuer?.CN || 'Unknown';
235
+ const expires = cert.valid_to ? new Date(cert.valid_to) : null;
236
+ const daysRemaining = expires ? Math.floor((expires - Date.now()) / (1000 * 60 * 60 * 24)) : 0;
237
+
238
+ return {
239
+ ssl: {
240
+ valid,
241
+ issuer,
242
+ expires_in_days: daysRemaining,
243
+ protocol: 'TLS',
244
+ grade: valid ? (daysRemaining > 30 ? 'A' : 'B') : 'F'
245
+ },
246
+ headers: {}
247
+ };
248
+ } catch (e) {
249
+ return { ssl: { valid: false, error: e.message } };
250
+ }
251
  }
252
 
253
+ function calculatePrivacyScore(blacklight, enrichedTrackers, tosdrGrade, security) {
254
+ let score = 100;
255
+
256
+ const trackerCount = enrichedTrackers.length;
257
+ score -= Math.min(trackerCount * 4, 30);
258
+
259
+ const hasFingerprinting = !!(blacklight?.canvasFingerprinters?.length) || !!(blacklight?.canvasFontFingerprinters?.length);
260
+ if (hasFingerprinting) score -= 30;
261
+
262
+ const thirdPartyCookies = blacklight?.cookies?.filter(c => c.thirdParty)?.length || 0;
263
+ score -= Math.min(thirdPartyCookies * 5, 20);
264
+
265
+ if (blacklight?.sessionRecorders?.length > 0) score -= 10;
266
+ if (blacklight?.keyLogging?.length > 0) score -= 10;
267
+
268
+ if (!security?.ssl?.valid) score -= 20;
269
+
270
+ if (tosdrGrade) {
271
+ if (tosdrGrade.grade === 'A') score += 5;
272
+ else if (tosdrGrade.grade === 'B') score += 2;
273
+ else if (tosdrGrade.grade === 'D') score -= 5;
274
+ else if (tosdrGrade.grade === 'E') score -= 10;
275
+ }
276
+
277
+ score = Math.max(0, Math.min(100, Math.round(score)));
278
+
279
+ let grade;
280
+ if (score >= 95) grade = 'A+';
281
+ else if (score >= 85) grade = 'A';
282
+ else if (score >= 75) grade = 'B';
283
+ else if (score >= 65) grade = 'C';
284
+ else if (score >= 55) grade = 'D';
285
+ else grade = 'F';
286
+
287
+ return { score, grade };
288
+ }
289
 
290
  app.get('/api/scan', async (req, res) => {
291
  const url = normalizeUrl(req.query.url);
292
  if (!url) return res.status(400).json({ error: 'Invalid URL' });
293
+
294
+ const format = req.query.format || 'json'; // افتراضيًا JSON
295
+ const mode = req.query.mode || 'deep';
296
+
297
+ // إذا كان الوضع سريع، نعود ببيانات محدودة
298
+ if (mode === 'fast') {
299
+ try {
300
+ const startTime = Date.now();
301
+ const security = await performSecurityCheck(url);
302
+ const tosdr = await getTosdrGrade(url);
303
+
304
+ const hostname = new URL(url).hostname;
305
+ const baseDomain = getBaseDomain(hostname);
306
+ const trackers = [];
307
+
308
+ for (const domain in ddgTrackerRadar.domains) {
309
+ if (domain.includes(baseDomain) || baseDomain.includes(domain)) {
310
+ const info = ddgTrackerRadar.domains[domain];
311
+ trackers.push({
312
+ domain,
313
+ owner: info.owner || domain,
314
+ category: info.category || 'unknown',
315
+ prevalence: info.prevalence || 0
316
+ });
317
+ }
318
+ }
319
+
320
+ const { score, grade } = calculatePrivacyScore({}, trackers, tosdr, security);
321
+
322
+ const result = {
323
+ success: true,
324
+ mode: 'fast',
325
+ url,
326
+ final_url: url,
327
+ scan_time_sec: (Date.now() - startTime) / 1000,
328
+ privacy_score: { score, grade },
329
+ trackers: { count: trackers.length, list: trackers.slice(0, 20) },
330
+ security,
331
+ tosdr,
332
+ screenshot: null
333
+ };
334
+
335
+ return res.json(result);
336
+ } catch (e) {
337
+ return res.status(500).json({ error: e.message });
338
+ }
339
+ }
340
+
341
+ // الوضع العميق (deep) - تنفيذ متوازي
342
+ if (format === 'sse') {
343
+ // ... (نفس كود SSE السابق)
344
+ return res.status(501).json({ error: 'SSE not implemented in this version' });
345
+ }
346
+
347
+ // تنسيق JSON (الافتراضي)
348
  try {
349
  const startTime = Date.now();
350
 
351
+ // تنفيذ جميع المهام بالتوازي
352
+ const [screenshot, blacklight, tosdr, security, hiddenStorage] = await Promise.allSettled([
353
  takeScreenshot(url),
354
  performBlacklightScan(url),
355
+ getTosdrGrade(url),
356
+ performSecurityCheck(url),
357
+ checkHiddenStorage(url)
358
  ]);
 
 
359
 
360
+ const screenshotData = screenshot.status === 'fulfilled' ? screenshot.value : null;
361
+ const blacklightData = blacklight.status === 'fulfilled' ? blacklight.value : { error: blacklight.reason?.message };
362
+ const tosdrData = tosdr.status === 'fulfilled' ? tosdr.value : null;
363
+ const securityData = security.status === 'fulfilled' ? security.value : { ssl: { valid: false } };
364
+ const hiddenData = hiddenStorage.status === 'fulfilled' ? hiddenStorage.value : null;
365
+
366
+ // استخراج النطاقات الخارجية
367
+ const thirdPartyDomains = [
368
+ ...(blacklightData.hosts?.thirdParty || []),
369
+ ...(blacklightData.hosts?.requests?.third_party || [])
370
+ ];
371
+ const uniqueDomains = [...new Set(thirdPartyDomains)];
372
 
373
+ // إ��راء بيانات المتتبعين
374
  const enrichedTrackers = [];
375
+ for (const domain of uniqueDomains) {
376
+ const ddgInfo = getDDGInfo(domain);
377
+ const ghosteryInfo = await getGhosteryInfo(domain);
 
 
 
 
378
  enrichedTrackers.push({
379
  domain,
380
+ owner: ghosteryInfo?.organization || ddgInfo?.owner || getBaseDomain(domain),
381
+ category: ghosteryInfo?.category || ddgInfo?.category || 'unknown',
382
+ prevalence: ddgInfo?.prevalence || 0
383
  });
384
  }
385
+
386
+ const { score, grade } = calculatePrivacyScore(blacklightData, enrichedTrackers, tosdrData, securityData);
387
+
388
+ // بناء الملخص
 
 
 
 
 
 
 
 
 
 
 
 
 
389
  const summary = {
390
  success: true,
391
+ mode: 'deep',
392
  url,
393
+ final_url: blacklightData.uri_dest || url,
394
  scan_time_sec: (Date.now() - startTime) / 1000,
395
+ privacy_score: { score, grade },
396
+ trackers: { count: enrichedTrackers.length, list: enrichedTrackers.slice(0, 20) },
397
+ cookies: {
398
+ total: blacklightData.cookies?.length || 0,
399
+ third_party: blacklightData.cookies?.filter(c => c.thirdParty)?.length || 0
 
 
 
 
400
  },
401
  fingerprinting: {
402
+ canvas: !!(blacklightData.canvasFingerprinters?.length),
403
+ fonts: !!(blacklightData.canvasFontFingerprinters?.length)
404
  },
405
+ session_recording: !!(blacklightData.sessionRecorders?.length),
406
+ key_logging: !!(blacklightData.keyLogging?.length),
407
+ hidden_storage: hiddenData ? {
408
+ localStorage: hiddenData.localStorage?.length || 0,
409
+ sessionStorage: hiddenData.sessionStorage?.length || 0,
410
+ indexedDB: hiddenData.indexedDB
411
+ } : null,
412
+ security: securityData,
413
+ tosdr: tosdrData,
414
+ screenshot: screenshotData,
415
+ // تضمين البيانات الخام للمطورين
416
+ raw: blacklightData
417
  };
418
+
419
  res.json(summary);
420
+
421
  } catch (e) {
422
+ console.error('Scan error:', e);
423
  res.status(500).json({ error: e.message });
424
  }
425
  });
426
 
427
+ app.get('/health', (req, res) => res.json({ status: 'ok' }));
428
+ app.use(express.static('public'));
429
+
430
  (async () => {
431
  await initGhosteryDB();
432
  loadDDGTrackerRadar();
433
+ if (Object.keys(ddgTrackerRadar.domains).length === 0) {
434
+ await downloadDDGTrackerRadar();
435
+ }
436
+ app.listen(PORT, '0.0.0.0', () => console.log(`🚀 Private Eye V8.1 running on ${PORT}`));
437
+ })();
438
+ give you feedback in arabic