false, 'error' => 'DB not found']); exit; } try { $db = new SQLite3($dbPath, SQLITE3_OPEN_READWRITE); } catch (Exception $e) { http_response_code(500); echo json_encode(['ok' => false, 'error' => 'DB open failed']); exit; } $action = $_GET['action'] ?? 'domains'; function dh_strip_html_text(string $html): string { $html = preg_replace('/]*>[\s\S]*?<\/script>/i', ' ', $html); $html = preg_replace('/]*>[\s\S]*?<\/style>/i', ' ', $html); $text = strip_tags($html ?? ''); $text = preg_replace('/\s+/u', ' ', $text ?? ''); return trim($text ?? ''); } function dh_fetch_text(string $url): ?string { $ctx = stream_context_create([ 'http' => [ 'timeout' => 12, 'user_agent' => 'Mozilla/5.0 DomainHunterPanel/1.0', 'follow_location' => 1, ], 'ssl' => [ 'verify_peer' => true, 'verify_peer_name' => true, ] ]); $raw = @file_get_contents($url, false, $ctx); if ($raw === false) return null; return dh_strip_html_text($raw); } function dh_find_price(string $text, string $tld, string $kind): ?float { $hintsRegister = ['rejestr', 'rejestracja', 'new', 'first year', '1 rok']; $hintsRenew = ['odnow', 'renew', 'renewal', 'kolejny', 'next year', '2 rok']; $hints = $kind === 'renew' ? $hintsRenew : $hintsRegister; $token = '.' . strtolower($tld); $len = mb_strlen($text, 'UTF-8'); $pos = 0; while (($idx = mb_stripos($text, $token, $pos, 'UTF-8')) !== false) { $start = max(0, $idx - 240); $chunk = mb_substr($text, $start, 520, 'UTF-8'); $chunkLow = mb_strtolower($chunk, 'UTF-8'); $ok = false; foreach ($hints as $h) { if (mb_strpos($chunkLow, $h, 0, 'UTF-8') !== false) { $ok = true; break; } } if ($ok && preg_match('/(\d{1,4}(?:[\.,]\d{1,2})?)\s*(zł|pln|eur|€|usd|\$)/iu', $chunkLow, $m)) { return floatval(str_replace(',', '.', $m[1])); } $pos = $idx + mb_strlen($token, 'UTF-8'); if ($pos >= $len) break; } foreach ($hints as $h) { $pos = 0; while (($idx = mb_stripos($text, $h, $pos, 'UTF-8')) !== false) { $start = max(0, $idx - 100); $chunk = mb_substr($text, $start, 320, 'UTF-8'); if (preg_match('/(\d{1,4}(?:[\.,]\d{1,2})?)\s*(zł|pln|eur|€|usd|\$)/iu', $chunk, $m)) { return floatval(str_replace(',', '.', $m[1])); } $pos = $idx + mb_strlen($h, 'UTF-8'); if ($pos >= $len) break; } } return null; } if ($action === 'domains') { $q = trim($_GET['q'] ?? ''); $tld = trim($_GET['tld'] ?? ''); $onlyNew = (($_GET['only_new'] ?? '0') === '1'); $runId = $db->querySingle("SELECT value FROM metadata WHERE key='latest_run_id'"); $scannedAt = $db->querySingle("SELECT value FROM metadata WHERE key='latest_scanned_at'"); $stPrev = $db->prepare("SELECT run_id FROM domains WHERE run_id != :run GROUP BY run_id ORDER BY MAX(scanned_at) DESC LIMIT 1"); $stPrev->bindValue(':run', $runId, SQLITE3_TEXT); $prevRes = $stPrev->execute(); $prev = $prevRes ? $prevRes->fetchArray(SQLITE3_ASSOC) : null; $prevRunId = $prev['run_id'] ?? ''; $sql = "SELECT d.domain, d.tld, d.score, d.status, d.keywords_json, ls.llm_score, ls.decision, ls.reason, CASE WHEN :prev = '' THEN 1 WHEN EXISTS (SELECT 1 FROM domains p WHERE p.run_id = :prev AND p.domain = d.domain) THEN 0 ELSE 1 END AS is_new FROM domains d LEFT JOIN llm_scores ls ON ls.run_id = d.run_id AND ls.domain = d.domain WHERE d.run_id = :run"; if ($q !== '') $sql .= " AND d.domain LIKE :q"; if ($tld !== '') $sql .= " AND d.tld = :tld"; if ($onlyNew) { $sql .= " AND (CASE WHEN :prev = '' THEN 1 WHEN EXISTS (SELECT 1 FROM domains p WHERE p.run_id = :prev AND p.domain = d.domain) THEN 0 ELSE 1 END) = 1"; } $sql .= " ORDER BY d.score DESC, d.domain ASC LIMIT 500"; $st = $db->prepare($sql); $st->bindValue(':run', $runId, SQLITE3_TEXT); $st->bindValue(':prev', $prevRunId, SQLITE3_TEXT); if ($q !== '') $st->bindValue(':q', '%' . $q . '%', SQLITE3_TEXT); if ($tld !== '') $st->bindValue(':tld', $tld, SQLITE3_TEXT); $res = $st->execute(); $rows = []; $newCount = 0; while ($r = $res->fetchArray(SQLITE3_ASSOC)) { $r['keywords'] = json_decode($r['keywords_json'] ?: '[]', true) ?: []; $r['is_new'] = intval($r['is_new']) === 1; if ($r['is_new']) $newCount++; unset($r['keywords_json']); $rows[] = $r; } echo json_encode([ 'ok' => true, 'meta' => [ 'runId' => $runId, 'prevRunId' => $prevRunId, 'scannedAt' => $scannedAt, 'count' => count($rows), 'newCount' => $newCount ], 'domains' => $rows, ], JSON_UNESCAPED_UNICODE); exit; } if ($action === 'live_prices') { $domain = trim($_GET['domain'] ?? ''); $tld = strtolower(trim($_GET['tld'] ?? '')); if ($domain !== '' && $tld === '' && strpos($domain, '.') !== false) { $parts = explode('.', $domain); $tld = strtolower(end($parts)); } if ($tld === '') { http_response_code(400); echo json_encode(['ok' => false, 'error' => 'domain or tld required']); exit; } $regPath = __DIR__ . '/data/registrars.json'; if (!file_exists($regPath)) { http_response_code(500); echo json_encode(['ok' => false, 'error' => 'registrars.json missing']); exit; } $regData = json_decode(file_get_contents($regPath), true) ?: []; $cachePath = __DIR__ . '/data/live-price-cache.json'; $cacheTtl = 1800; $cache = []; if (file_exists($cachePath)) { $cache = json_decode(file_get_contents($cachePath), true) ?: []; } $now = time(); $items = []; foreach (($regData['registrars'] ?? []) as $reg) { $name = $reg['name'] ?? ''; if ($name === '') continue; $autoUrl = $reg['autoPricing'][$tld]['url'] ?? null; $baseUrl = $reg['url'] ?? null; $key = strtolower($name) . '|' . $tld; $entry = $cache[$key] ?? null; if (!$entry || (($entry['ts'] ?? 0) + $cacheTtl < $now)) { $register = null; $renew = null; $sourceUrl = $autoUrl ?: $baseUrl; if ($sourceUrl) { $text = dh_fetch_text($sourceUrl); if ($text !== null) { $register = dh_find_price($text, $tld, 'register'); $renew = dh_find_price($text, $tld, 'renew'); } } $entry = [ 'register_price' => $register, 'renew_price' => $renew, 'source_url' => $sourceUrl, 'ts' => $now, ]; $cache[$key] = $entry; } $items[] = [ 'registrar' => $name, 'tld' => $tld, 'register_price' => $entry['register_price'] ?? null, 'renew_price' => $entry['renew_price'] ?? null, 'source_url' => $entry['source_url'] ?? null, 'live' => true, 'checked_at' => gmdate('c', intval($entry['ts'] ?? $now)), ]; } @file_put_contents($cachePath, json_encode($cache, JSON_UNESCAPED_UNICODE)); echo json_encode([ 'ok' => true, 'domain' => $domain, 'tld' => $tld, 'cached_seconds' => $cacheTtl, 'items' => $items, ], JSON_UNESCAPED_UNICODE); exit; } if ($action === 'prices') { if ($_SERVER['REQUEST_METHOD'] === 'POST') { $raw = json_decode(file_get_contents('php://input'), true) ?: []; $registrar = trim($raw['registrar'] ?? ''); $tld = trim($raw['tld'] ?? ''); $registerPrice = $raw['register_price'] ?? null; $renewPrice = $raw['renew_price'] ?? null; if ($registrar === '' || $tld === '') { http_response_code(400); echo json_encode(['ok' => false, 'error' => 'registrar/tld required']); exit; } $st = $db->prepare("UPDATE registrar_prices SET register_price=:rp, renew_price=:rr, updated_at=datetime('now') WHERE registrar=:r AND tld=:t"); if ($registerPrice === null || $registerPrice === '') $st->bindValue(':rp', null, SQLITE3_NULL); else $st->bindValue(':rp', floatval($registerPrice), SQLITE3_FLOAT); if ($renewPrice === null || $renewPrice === '') $st->bindValue(':rr', null, SQLITE3_NULL); else $st->bindValue(':rr', floatval($renewPrice), SQLITE3_FLOAT); $st->bindValue(':r', $registrar, SQLITE3_TEXT); $st->bindValue(':t', $tld, SQLITE3_TEXT); $st->execute(); echo json_encode(['ok' => true]); exit; } $res = $db->query("SELECT registrar, url, tld, register_price, renew_price, updated_at FROM registrar_prices ORDER BY registrar, tld"); $items = []; while ($r = $res->fetchArray(SQLITE3_ASSOC)) $items[] = $r; echo json_encode(['ok' => true, 'items' => $items], JSON_UNESCAPED_UNICODE); exit; } http_response_code(400); echo json_encode(['ok' => false, 'error' => 'unknown action']);