Files
wevads-platform/scripts/api_gan-adversarial.php
2026-02-26 04:53:11 +01:00

217 lines
9.6 KiB
PHP
Executable File

<?php
/**
* GAN Adversarial Filter API
* Simulates Gmail/Outlook/GMX spam filters locally
* Tests templates BEFORE sending to avoid IP burns
*/
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
try {
$pdo = new PDO('pgsql:host=localhost;dbname=adx_system', 'admin', 'admin123');
$pdo->exec('SET search_path TO admin, public');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
echo json_encode(['error' => 'DB: ' . $e->getMessage()]); exit;
}
$input = json_decode(file_get_contents('php://input'), true) ?: $_REQUEST;
$action = $input['action'] ?? $_GET['action'] ?? 'status';
// ISP Filter simulation weights
function simulateFilter($pdo, $isp, $subject, $body, $headers = []) {
$score = 100; // Start at 100 (perfect)
$flags = [];
// Load rules for this ISP
$stmt = $pdo->prepare("SELECT * FROM gan_filter_rules WHERE (isp = ? OR isp = 'ALL') AND status = 'active' ORDER BY weight DESC");
$stmt->execute([$isp]);
$rules = $stmt->fetchAll(PDO::FETCH_ASSOC);
$fullContent = strtolower($subject . ' ' . $body);
foreach ($rules as $rule) {
$hit = false;
switch ($rule['rule_type']) {
case 'subject':
$keywords = explode(',', $rule['pattern']);
foreach ($keywords as $kw) {
if (stripos($subject, trim($kw)) !== false) { $hit = true; break; }
}
break;
case 'body':
if (preg_match('/href/i', $body)) {
$linkCount = substr_count(strtolower($body), 'href');
if ($linkCount > 5) $hit = true;
}
if (stripos($rule['pattern'], 'image ratio') !== false) {
$textLen = strlen(strip_tags($body));
if ($textLen < 100) $hit = true;
}
break;
case 'headers':
if (stripos($rule['pattern'], 'List-Unsubscribe') !== false) {
if (empty($headers['List-Unsubscribe'])) $hit = true;
}
if (stripos($rule['pattern'], 'X-Mailer') !== false) {
if (!empty($headers['X-Mailer'])) $hit = true;
}
break;
case 'auth':
// DKIM/SPF checks
if (stripos($rule['pattern'], 'DKIM=none') !== false) {
if (empty($headers['DKIM-Signature'])) $hit = true;
}
break;
case 'network':
// Datacenter detection
if (stripos($rule['pattern'], 'Hetzner') !== false) {
$hit = true; // We ARE on Hetzner
}
break;
}
if ($hit) {
$penalty = (float)$rule['weight'] * 15;
$score -= $penalty;
$flags[] = [
'rule' => $rule['rule_name'],
'type' => $rule['rule_type'],
'penalty' => round($penalty, 1),
'weight' => $rule['weight']
];
}
}
// Content analysis
$spamWords = ['gratuit','urgent','offre','promo','gagnez','felicitations','100%','garanti','sans engagement','cliquez ici'];
$spamCount = 0;
foreach ($spamWords as $w) {
if (stripos($fullContent, $w) !== false) $spamCount++;
}
if ($spamCount > 0) {
$penalty = $spamCount * 5;
$score -= $penalty;
$flags[] = ['rule' => "Spam words detected: $spamCount", 'type' => 'content', 'penalty' => $penalty];
}
// HTML ratio
$htmlLen = strlen($body);
$textLen = strlen(strip_tags($body));
if ($htmlLen > 0 && ($textLen / $htmlLen) < 0.3) {
$score -= 10;
$flags[] = ['rule' => 'Low text/HTML ratio', 'type' => 'structure', 'penalty' => 10];
}
// URL analysis
preg_match_all('/https?:\/\/[^\s"\'<]+/', $body, $urls);
$uniqueDomains = [];
foreach ($urls[0] ?? [] as $url) {
$host = parse_url($url, PHP_URL_HOST);
if ($host) $uniqueDomains[$host] = true;
}
if (count($uniqueDomains) > 3) {
$score -= 8;
$flags[] = ['rule' => 'Multiple domains in links: ' . count($uniqueDomains), 'type' => 'urls', 'penalty' => 8];
}
$score = max(0, min(100, $score));
$verdict = $score >= 80 ? 'INBOX' : ($score >= 50 ? 'PROMO/JUNK' : 'SPAM');
return [
'score' => round($score, 1),
'verdict' => $verdict,
'flags' => $flags,
'suggestions' => generateSuggestions($flags),
'rules_checked' => count($rules)
];
}
function generateSuggestions($flags) {
$suggestions = [];
foreach ($flags as $f) {
switch ($f['type'] ?? '') {
case 'subject': $suggestions[] = 'Rewrite subject without spam trigger words'; break;
case 'headers': $suggestions[] = 'Add List-Unsubscribe header, remove X-Mailer'; break;
case 'auth': $suggestions[] = 'Ensure DKIM signing and SPF pass'; break;
case 'network': $suggestions[] = 'Route via residential proxy or O365'; break;
case 'content': $suggestions[] = 'Replace spam words with neutral alternatives'; break;
case 'structure': $suggestions[] = 'Increase text content, reduce HTML boilerplate'; break;
case 'urls': $suggestions[] = 'Use single tracking domain for all links'; break;
}
}
return array_unique($suggestions);
}
function mutateTemplate($subject, $body) {
// Auto-improve template to pass filters
$replacements = [
'gratuit' => 'offert', 'urgent' => 'important', 'promo' => 'selection',
'gagnez' => 'decouvrez', 'cliquez ici' => 'en savoir plus',
'felicitations' => 'bonjour', '100%' => 'totalement',
'GRATUIT' => 'OFFERT', 'URGENT' => 'PRIORITAIRE'
];
$newSubject = str_ireplace(array_keys($replacements), array_values($replacements), $subject);
$newBody = str_ireplace(array_keys($replacements), array_values($replacements), $body);
// Add unsubscribe link if missing
if (stripos($newBody, 'unsubscribe') === false && stripos($newBody, 'desinscrire') === false) {
$newBody = str_ireplace('</body>', '<p style="font-size:11px;color:#999">Pour vous desinscrire: <a href="{unsubscribe_url}">cliquez ici</a></p></body>', $newBody);
}
return ['subject' => $newSubject, 'body' => $newBody, 'mutations' => count($replacements)];
}
switch ($action) {
case 'test':
$subject = $input['subject'] ?? '';
$body = $input['body'] ?? $input['body_html'] ?? '';
$isp = $input['isp'] ?? 'Gmail';
$headers = $input['headers'] ?? [];
if (empty($subject) && empty($body)) { echo json_encode(['error' => 'subject and body required']); exit; }
$result = simulateFilter($pdo, $isp, $subject, $body, $headers);
// Log
$pdo->prepare("INSERT INTO gan_test_results (template_name, isp_target, score_before, passed, details, tested_at) VALUES (?,?,?,?,?,NOW())")
->execute([substr($subject, 0, 200), $isp, $result['score'], $result['verdict'] === 'INBOX', json_encode($result)]);
echo json_encode($result);
break;
case 'test_all':
$subject = $input['subject'] ?? '';
$body = $input['body'] ?? '';
$results = [];
foreach (['Gmail','Outlook','GMX','T-Online','Yahoo'] as $isp) {
$results[$isp] = simulateFilter($pdo, $isp, $subject, $body, $input['headers'] ?? []);
}
echo json_encode(['results' => $results, 'timestamp' => date('c')]);
break;
case 'mutate':
$subject = $input['subject'] ?? '';
$body = $input['body'] ?? '';
$isp = $input['isp'] ?? 'Gmail';
$before = simulateFilter($pdo, $isp, $subject, $body);
$mutated = mutateTemplate($subject, $body);
$after = simulateFilter($pdo, $isp, $mutated['subject'], $mutated['body']);
$pdo->prepare("INSERT INTO gan_test_results (template_name, isp_target, score_before, score_after, mutations_applied, passed, details, tested_at) VALUES (?,?,?,?,?,?,?,NOW())")
->execute([substr($subject, 0, 200), $isp, $before['score'], $after['score'], $mutated['mutations'], $after['verdict'] === 'INBOX', json_encode(['before' => $before, 'after' => $after])]);
echo json_encode(['before' => $before, 'after' => $after, 'mutated' => $mutated, 'improvement' => round($after['score'] - $before['score'], 1)]);
break;
case 'rules':
$isp = $input['isp'] ?? $_GET['isp'] ?? null;
$q = "SELECT * FROM gan_filter_rules WHERE status = 'active'";
if ($isp) { $q .= " AND isp = '$isp'"; }
$q .= " ORDER BY isp, weight DESC";
echo json_encode(['rules' => $pdo->query($q)->fetchAll(PDO::FETCH_ASSOC)]);
break;
case 'stats':
$stats = $pdo->query("SELECT COUNT(*) as total, COUNT(*) FILTER (WHERE passed) as passed, COUNT(*) FILTER (WHERE NOT passed) as failed, ROUND(AVG(score_before),1) as avg_score, COUNT(*) FILTER (WHERE tested_at >= CURRENT_DATE) as today FROM gan_test_results")->fetch(PDO::FETCH_ASSOC);
$byIsp = $pdo->query("SELECT isp_target, COUNT(*) as tests, ROUND(AVG(score_before),1) as avg_score, COUNT(*) FILTER (WHERE passed) as passed FROM gan_test_results GROUP BY isp_target")->fetchAll(PDO::FETCH_ASSOC);
$rules = $pdo->query("SELECT isp, COUNT(*) as count FROM gan_filter_rules WHERE status='active' GROUP BY isp")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['stats' => $stats, 'by_isp' => $byIsp, 'rules' => $rules]);
break;
default:
echo json_encode(['status' => 'online', 'service' => 'GAN Adversarial Filter', 'version' => '1.0', 'actions' => ['test','test_all','mutate','rules','stats']]);
}