watch / server.js
suzmen's picture
Upload 7 files
4e673b3 verified
Raw History Blame Contribute Delete
16.8 kB
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 = /<tbody>([\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 = /<tr>([\s\S]*?)<\/tr>/gi;
let rowMatch;
const data = [];
while ((rowMatch = rowRegex.exec(tbodyContent)) !== null) {
const rowContent = rowMatch[1];
const cellRegex = /<td>([\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('<h1>404 Not Found</h1>', '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('<h1>404 Not Found</h1>', '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 = /<tbody>([\s\S]*?)<\/tbody>/i;
const tbodyMatch = html.match(tbodyRegex);
if (!tbodyMatch) return;
const tbodyContent = tbodyMatch[1];
const rowRegex = /<tr>([\s\S]*?)<\/tr>/gi;
let rowMatch;
const parsedRows = [];
while ((rowMatch = rowRegex.exec(tbodyContent)) !== null) {
const rowContent = rowMatch[1];
const cellRegex = /<td>([\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);