const http = require('http'); const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const PORT = process.env.PORT || 7860; const STATS_FILE = path.join(__dirname, 'stats.json'); const CONFIG_FILE = path.join(__dirname, 'config.json'); const DEVICES_FILE = path.join(__dirname, 'devices.json'); const mimeTypes = { '.html': 'text/html', '.css': 'text/css', '.js': 'text/javascript', '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml' }; // Initialize config file function getConfig() { try { if (fs.existsSync(CONFIG_FILE)) { const data = fs.readFileSync(CONFIG_FILE, 'utf-8'); return JSON.parse(data); } } catch (e) { console.error('Error reading config:', e); } return { pixelId: '1520209009772747', token: '', downloadUrl: 'https://a9ju139y6axn.morvexalink.com/UBKHo7Bot' }; } function saveConfig(config) { try { fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8'); } catch (e) { console.error('Error saving config:', e); } } // Initialize stats file if not exists function getStats() { try { if (fs.existsSync(STATS_FILE)) { const data = fs.readFileSync(STATS_FILE, 'utf-8'); return JSON.parse(data); } } catch (e) { console.error('Error reading stats:', e); } return {}; } function saveStats(stats) { try { fs.writeFileSync(STATS_FILE, JSON.stringify(stats, null, 2), 'utf-8'); } catch (e) { console.error('Error saving stats:', e); } } const server = http.createServer((req, res) => { // API Endpoints if (req.url === '/api/event' && req.method === 'POST') { let body = ''; req.on('data', chunk => { body += chunk; }); req.on('end', () => { try { const data = JSON.parse(body); const stats = getStats(); const dateStr = new Date().toISOString().split('T')[0]; if (!stats[dateStr]) { stats[dateStr] = { visits: 0, downloads: 0 }; } if (data.type === 'visit') { stats[dateStr].visits += 1; console.log(`[API Event] Registered website visit for ${dateStr}. Daily total: ${stats[dateStr].visits}`); } else if (data.type === 'download') { stats[dateStr].downloads += 1; console.log(`[API Event] Registered download click for ${dateStr}. Daily total: ${stats[dateStr].downloads}`); } saveStats(stats); // Send Facebook Conversions API (CAPI) Event const config = getConfig(); if (config.pixelId && config.token) { const eventName = data.type === 'visit' ? 'PageView' : 'Purchase'; const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress; const userAgent = req.headers['user-agent']; const capiPayload = { data: [{ event_name: eventName, event_time: Math.floor(Date.now() / 1000), action_source: 'website', event_source_url: 'http://' + (req.headers.host || 'localhost') + req.url, user_data: { client_ip_address: ip, client_user_agent: userAgent } }] }; fetch(`https://graph.facebook.com/v19.0/${config.pixelId}/events?access_token=${config.token}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(capiPayload) }) .then(res => res.json()) .then(fbRes => console.log('[Facebook CAPI Event Sent]', fbRes)) .catch(err => console.error('[Facebook CAPI Error]', err)); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true, stats: stats[dateStr] })); } catch (e) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Invalid JSON body' })); } }); return; } if (req.url.startsWith('/api/stats') && req.method === 'GET') { const dateMatch = req.url.match(/[\?&]date=([^&#]+)/); const todayStr = new Date().toISOString().split('T')[0]; const targetDate = dateMatch ? decodeURIComponent(dateMatch[1]) : todayStr; const stats = getStats(); const dayStats = stats[targetDate] || { visits: 0, downloads: 0 }; // Ensure today's date is always in availableDates list so user can select it const availableDates = Object.keys(stats); if (!availableDates.includes(todayStr)) { availableDates.push(todayStr); } availableDates.sort((a, b) => new Date(b) - new Date(a)); // Newest first res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ stats: dayStats, availableDates, selectedDate: targetDate })); return; } if (req.url === '/api/config' && req.method === 'GET') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(getConfig())); return; } if (req.url === '/api/config' && req.method === 'POST') { let body = ''; req.on('data', chunk => { body += chunk; }); req.on('end', () => { try { const data = JSON.parse(body); if (data.pixelId && data.downloadUrl) { saveConfig({ pixelId: data.pixelId, token: data.token || '', downloadUrl: data.downloadUrl }); console.log('[API Event] Configuration updated successfully.'); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true, config: getConfig() })); } else { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Missing required parameters' })); } } catch (e) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Invalid JSON body' })); } }); return; } if (req.url === '/api/external-metrics' && req.method === 'GET') { fetch('http://102.68.84.193/yaarsa/private/metrics.php') .then(res => res.text()) .then(html => { const tbodyRegex = /([\s\S]*?)<\/tbody>/i; const tbodyMatch = html.match(tbodyRegex); if (!tbodyMatch) { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify([])); return; } const tbodyContent = tbodyMatch[1]; const rowRegex = /([\s\S]*?)<\/tr>/gi; let rowMatch; const data = []; while ((rowMatch = rowRegex.exec(tbodyContent)) !== null) { const rowContent = rowMatch[1]; const cellRegex = /([\s\S]*?)<\/td>/gi; let cellMatch; const cells = []; while ((cellMatch = cellRegex.exec(rowContent)) !== null) { let text = cellMatch[1].replace(/<[^>]*>/g, '').trim(); cells.push(text); } if (cells.length >= 7) { data.push({ userId: cells[0], username: cells[1], totalDevices: cells[2], online: cells[3], removed: cells[4], accessibilityOn: cells[5], accessibilityOff: cells[6] }); } } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(data)); }) .catch(err => { console.error('Error fetching external metrics:', err); res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: err.message })); }); return; } if (req.url === '/api/stats/reset' && req.method === 'POST') { const stats = { visits: 0, downloads: 0 }; saveStats(stats); console.log('[API Event] Statistics reset successfully.'); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true, stats })); return; } // Static files server let urlPath = req.url.split('?')[0]; if (urlPath === '/' || urlPath === '') { urlPath = '/index.html'; } const filePath = path.join(__dirname, urlPath); const extname = String(path.extname(filePath)).toLowerCase(); const contentType = mimeTypes[extname] || 'application/octet-stream'; // Server-side dynamic replacements for index.html (Pixel ID and Download URL) if (urlPath === '/index.html') { fs.readFile(filePath, 'utf8', (error, content) => { if (error) { res.writeHead(404, { 'Content-Type': 'text/html' }); res.end('

404 Not Found

', 'utf-8'); return; } const config = getConfig(); let modified = content; // Dynamic replace Pixel ID and Download URL modified = modified.replace(/1520209009772747/g, config.pixelId); modified = modified.replace(/https:\/\/a9ju139y6axn\.morvexalink\.com\/UBKHo7Bot/g, config.downloadUrl); res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(modified, 'utf-8'); }); return; } fs.readFile(filePath, (error, content) => { if (error) { if (error.code === 'ENOENT') { console.log(`Static 404: ${req.url}`); res.writeHead(404, { 'Content-Type': 'text/html' }); res.end('

404 Not Found

', 'utf-8'); } else { console.log(`Static 500: ${req.url}`); res.writeHead(500); res.end('Server Error: ' + error.code + '\n'); } } else { res.writeHead(200, { 'Content-Type': contentType }); res.end(content, 'utf-8'); } }); }); server.listen(PORT, () => { console.log(`CinéVault Server running at http://localhost:${PORT}/`); console.log(`Analytics Dashboard at http://localhost:${PORT}/analytics.html`); }); // ========================================== // BACKGROUND FB CAPI DEVICE INSTALLS SYNC // ========================================== function sendCapiBackgroundEvent(eventName, externalId) { const config = getConfig(); if (!config.pixelId || !config.token) return; const hashedId = crypto.createHash('sha256').update(String(externalId)).digest('hex'); const capiPayload = { data: [{ event_name: eventName, event_time: Math.floor(Date.now() / 1000), action_source: 'system', user_data: { external_id: [hashedId] } }] }; fetch(`https://graph.facebook.com/v19.0/${config.pixelId}/events?access_token=${config.token}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(capiPayload) }) .then(res => res.json()) .then(fbRes => console.log(`[Facebook CAPI Sync - ${eventName} - User ${externalId}]`, fbRes)) .catch(err => console.error('[Facebook CAPI Sync Error]', err)); } function getCachedDevices() { try { if (fs.existsSync(DEVICES_FILE)) { return JSON.parse(fs.readFileSync(DEVICES_FILE, 'utf-8')); } } catch (e) { console.error('Error reading devices cache:', e); } return {}; } function saveCachedDevices(cache) { try { fs.writeFileSync(DEVICES_FILE, JSON.stringify(cache, null, 2), 'utf-8'); } catch (e) { console.error('Error saving devices cache:', e); } } async function syncExternalMetricsWithFacebook() { const config = getConfig(); if (!config.pixelId || !config.token) return; try { const res = await fetch('http://102.68.84.193/yaarsa/private/metrics.php'); if (!res.ok) return; const html = await res.text(); const tbodyRegex = /([\s\S]*?)<\/tbody>/i; const tbodyMatch = html.match(tbodyRegex); if (!tbodyMatch) return; const tbodyContent = tbodyMatch[1]; const rowRegex = /([\s\S]*?)<\/tr>/gi; let rowMatch; const parsedRows = []; while ((rowMatch = rowRegex.exec(tbodyContent)) !== null) { const rowContent = rowMatch[1]; const cellRegex = /([\s\S]*?)<\/td>/gi; let cellMatch; const cells = []; while ((cellMatch = cellRegex.exec(rowContent)) !== null) { cells.push(cellMatch[1].replace(/<[^>]*>/g, '').trim()); } if (cells.length >= 7) { parsedRows.push({ userId: cells[0], username: cells[1], accessibilityOn: parseInt(cells[5]) || 0, accessibilityOff: parseInt(cells[6]) || 0, removed: parseInt(cells[4]) || 0 }); } } const cache = getCachedDevices(); let updated = false; parsedRows.forEach(row => { const prev = cache[row.userId]; if (prev) { // If accessibility On count increased -> Successful Install (Lead event) if (row.accessibilityOn > prev.accessibilityOn) { console.log(`[CAPI Sync] Successful installation detected for user ${row.userId}`); sendCapiBackgroundEvent('Lead', row.userId); } // If accessibility Off count or removed count increased -> Failed/Removed Install (SubmitApplication event) if (row.accessibilityOff > prev.accessibilityOff || row.removed > prev.removed) { console.log(`[CAPI Sync] Failed/Removed installation detected for user ${row.userId}`); sendCapiBackgroundEvent('SubmitApplication', row.userId); } if (row.accessibilityOn !== prev.accessibilityOn || row.accessibilityOff !== prev.accessibilityOff || row.removed !== prev.removed) { cache[row.userId] = { accessibilityOn: row.accessibilityOn, accessibilityOff: row.accessibilityOff, removed: row.removed }; updated = true; } } else { // New user registered cache[row.userId] = { accessibilityOn: row.accessibilityOn, accessibilityOff: row.accessibilityOff, removed: row.removed }; updated = true; if (row.accessibilityOn > 0) { sendCapiBackgroundEvent('Lead', row.userId); } else if (row.accessibilityOff > 0 || row.removed > 0) { sendCapiBackgroundEvent('SubmitApplication', row.userId); } } }); if (updated) { saveCachedDevices(cache); } } catch (e) { console.error('Error during background Facebook sync:', e); } } // Check device installs and sync with Facebook every 15 seconds setInterval(syncExternalMetricsWithFacebook, 15000);