313 lines
11 KiB
PHP
313 lines
11 KiB
PHP
<?php
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
$dbPath = __DIR__ . '/data/domainhunter.db';
|
|
if (!file_exists($dbPath)) {
|
|
http_response_code(500);
|
|
echo json_encode(['ok' => 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('/<script\b[^>]*>[\s\S]*?<\/script>/i', ' ', $html);
|
|
$html = preg_replace('/<style\b[^>]*>[\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 === 'all_domains') {
|
|
$q = trim($_GET['q'] ?? '');
|
|
$tld = trim($_GET['tld'] ?? '');
|
|
$limit = intval($_GET['limit'] ?? 2000);
|
|
if ($limit < 1) $limit = 1;
|
|
if ($limit > 5000) $limit = 5000;
|
|
|
|
$sql = "SELECT d.run_id, d.scanned_at, d.domain, d.tld, d.score, d.status, d.keywords_json,
|
|
ls.llm_score, ls.decision, ls.reason
|
|
FROM domains d
|
|
LEFT JOIN llm_scores ls ON ls.run_id = d.run_id AND ls.domain = d.domain
|
|
WHERE 1=1";
|
|
|
|
if ($q !== '') $sql .= " AND d.domain LIKE :q";
|
|
if ($tld !== '') $sql .= " AND d.tld = :tld";
|
|
|
|
$sql .= " ORDER BY d.scanned_at DESC, d.score DESC, d.domain ASC LIMIT :limit";
|
|
|
|
$st = $db->prepare($sql);
|
|
if ($q !== '') $st->bindValue(':q', '%' . $q . '%', SQLITE3_TEXT);
|
|
if ($tld !== '') $st->bindValue(':tld', $tld, SQLITE3_TEXT);
|
|
$st->bindValue(':limit', $limit, SQLITE3_INTEGER);
|
|
|
|
$res = $st->execute();
|
|
$rows = [];
|
|
while ($r = $res->fetchArray(SQLITE3_ASSOC)) {
|
|
$r['keywords'] = json_decode($r['keywords_json'] ?: '[]', true) ?: [];
|
|
$r['is_new'] = false;
|
|
unset($r['keywords_json']);
|
|
$rows[] = $r;
|
|
}
|
|
|
|
echo json_encode([
|
|
'ok' => true,
|
|
'meta' => [
|
|
'mode' => 'all',
|
|
'count' => count($rows),
|
|
'limit' => $limit,
|
|
],
|
|
'domains' => $rows,
|
|
], 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']);
|