$value) { $search_url .= '&' . urlencode($key) . '=' . urlencode($value); } } // Get cookies (use provided or default) $cookies = isset($options['cookies']) ? $options['cookies'] : self::$default_cookies; // Get headers (use provided or default) $headers = isset($options['headers']) ? $options['headers'] : self::get_default_headers(); // Initialize curl $ch = curl_init($search_url); // Set curl options curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_TIMEOUT, isset($options['timeout']) ? $options['timeout'] : 30); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_COOKIE, $cookies); // Execute request $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curl_error = curl_error($ch); curl_close($ch); // Check for curl errors if ($response === false || !empty($curl_error)) { return new WP_Error('curl_error', __('cURL Error: ', 'ai-auto-tool') . $curl_error); } // Check HTTP status code if ($http_code !== 200) { return new WP_Error('http_error', sprintf(__('HTTP Error: %d', 'ai-auto-tool'), $http_code)); } // Check if response is valid HTML if (empty($response) || strlen($response) < 100) { return new WP_Error('empty_response', __('Empty or too short response from Google', 'ai-auto-tool')); } // Check for CAPTCHA, blocking, or JavaScript challenge $blocked_indicators = array( 'captcha', 'unusual traffic', 'automated queries', 'knitsail', // Google's anti-bot system 'enablejs', // JavaScript challenge redirect 'SG_REL', // Security challenge 'If you\'re having trouble accessing Google Search', // Challenge message 'window.google.c.e', // Google challenge JavaScript 'challenge_version' ); $is_blocked = false; foreach ($blocked_indicators as $indicator) { if (stripos($response, $indicator) !== false) { $is_blocked = true; break; } } // Also check if response is mostly JavaScript (challenge page) $js_ratio = substr_count(strtolower($response), 'function') / max(strlen($response) / 1000, 1); if ($js_ratio > 0.1 && strlen($response) < 50000) { // Likely a challenge page (lots of JS, small size) $is_blocked = true; } if ($is_blocked) { return new WP_Error('blocked', __('Google detected automated requests and returned a challenge page. Please use Google Custom Search API or update cookies/headers.', 'ai-auto-tool')); } // Save HTML for debugging if requested if (isset($options['save_html']) && $options['save_html']) { $debug_file = isset($options['debug_file']) ? $options['debug_file'] : sys_get_temp_dir() . '/google-search-debug-' . time() . '.html'; // Ensure we save as text, not binary file_put_contents($debug_file, $response); } // Parse results $results = self::parse_search_results($response); return array( 'success' => true, 'query' => $query, 'url' => $search_url, 'http_code' => $http_code, 'results' => $results, 'raw_html' => $response ); } /** * Parse Google search results from HTML * * @param string $html HTML content * @return array Parsed results */ private static function parse_search_results($html) { $results = array( 'organic' => array(), 'ads' => array(), 'featured_snippet' => null, 'people_also_ask' => array(), 'related_searches' => array() ); // First try: Regex parsing as fallback (more reliable) $regex_results = self::parse_search_results_regex($html); if (!empty($regex_results['organic'])) { $results['organic'] = $regex_results['organic']; } // Second try: DOM parsing (more accurate but can fail) libxml_use_internal_errors(true); $dom = new DOMDocument(); @$dom->loadHTML('' . $html); $xpath = new DOMXPath($dom); // Multiple selectors for organic results (Google changes structure frequently) $organic_selectors = array( "//div[contains(@class, 'g')]", "//div[@data-ved]", "//div[contains(@class, 'tF2Cxc')]", "//div[contains(@class, 'MjjYud')]", "//div[@jscontroller='SC7lYd']", "//div[contains(@class, 'hlcw0c')]", "//div[contains(@class, 'g-blk')]" ); $processed_nodes = array(); $dom_results = array(); foreach ($organic_selectors as $selector) { $organic_nodes = $xpath->query($selector); foreach ($organic_nodes as $node) { // Skip if already processed $node_id = spl_object_hash($node); if (isset($processed_nodes[$node_id])) { continue; } $processed_nodes[$node_id] = true; $result = array(); // Extract title - multiple possible locations $title_selectors = array( ".//h3", ".//h3[contains(@class, 'LC20lb')]", ".//h3[contains(@class, 'DKV0Md')]", ".//h3[contains(@class, 'zBAuLc')]", ".//a//h3", ".//div[contains(@class, 'yuRUbf')]//h3", ".//div[contains(@class, 'N54PNb')]//h3" ); foreach ($title_selectors as $title_sel) { $title_node = $xpath->query($title_sel, $node)->item(0); if ($title_node && !empty(trim($title_node->textContent))) { $result['title'] = trim($title_node->textContent); break; } } // Extract URL - multiple possible locations $link_selectors = array( ".//a[@href]", ".//div[contains(@class, 'yuRUbf')]//a[@href]", ".//a[contains(@class, 'BVG0Nb')][@href]", ".//div[contains(@class, 'N54PNb')]//a[@href]" ); foreach ($link_selectors as $link_sel) { $link_nodes = $xpath->query($link_sel, $node); foreach ($link_nodes as $link_node) { $href = $link_node->getAttribute('href'); if (!empty($href)) { // Clean Google redirect URLs if (strpos($href, '/url?q=') === 0 || strpos($href, '/url?') === 0) { $parsed = parse_url($href); if (isset($parsed['query'])) { parse_str($parsed['query'], $params); if (isset($params['q'])) { $href = $params['q']; } } else { $href = urldecode(substr($href, 7)); $href = explode('&', $href)[0]; } } // Skip Google internal URLs and validate if ((strpos($href, 'http://') === 0 || strpos($href, 'https://') === 0 || strpos($href, '//') === 0) && strpos($href, 'google.com') === false && strpos($href, 'googleusercontent.com') === false) { $result['url'] = $href; break 2; // Break both loops } } } } // Extract snippet - multiple possible locations $snippet_selectors = array( ".//span[contains(@class, 'aCOpRe')]", ".//div[contains(@class, 'VwiC3b')]", ".//span[contains(@class, 'st')]", ".//span[contains(@class, 'aST4p')]", ".//div[contains(@class, 's')]//span", ".//div[contains(@class, 'IsZvec')]//span", ".//div[contains(@class, 'yXK7lf')]//span" ); foreach ($snippet_selectors as $snippet_sel) { $snippet_nodes = $xpath->query($snippet_sel, $node); if ($snippet_nodes->length > 0) { $snippet_text = trim($snippet_nodes->item(0)->textContent); if (!empty($snippet_text) && strlen($snippet_text) > 20) { $result['snippet'] = $snippet_text; break; } } } // Only add if we have at least title and URL if (!empty($result['title']) && !empty($result['url'])) { $dom_results[] = $result; } } } // Use DOM results if we got more than regex, otherwise use regex if (count($dom_results) > count($results['organic'])) { $results['organic'] = $dom_results; } elseif (empty($results['organic']) && !empty($dom_results)) { $results['organic'] = $dom_results; } // Parse featured snippet (answer box) $featured_node = $xpath->query("//div[contains(@class, 'xpdopen')] | //div[@data-attrid]")->item(0); if ($featured_node) { $featured = array(); $featured_title = $xpath->query(".//h2 | .//h3", $featured_node)->item(0); if ($featured_title) { $featured['title'] = trim($featured_title->textContent); } $featured_text = $xpath->query(".//span[contains(@class, 'hgKElc')] | .//div[contains(@class, 'Z0LcW')]", $featured_node)->item(0); if ($featured_text) { $featured['text'] = trim($featured_text->textContent); } if (!empty($featured)) { $results['featured_snippet'] = $featured; } } // Parse "People Also Ask" section $paa_nodes = $xpath->query("//div[contains(@class, 'related-question-pair')] | //div[@data-ved and .//div[contains(@class, 'cbphWd')]]"); foreach ($paa_nodes as $paa_node) { $question = $xpath->query(".//div[contains(@class, 'cbphWd')] | .//span[contains(@class, 'CSkcDe')]", $paa_node)->item(0); if ($question) { $results['people_also_ask'][] = trim($question->textContent); } } // Parse related searches $related_nodes = $xpath->query("//div[contains(@class, 'brs_col')]//a | //a[contains(@class, 's75CSd')]"); foreach ($related_nodes as $related_node) { $related_text = trim($related_node->textContent); if (!empty($related_text)) { $results['related_searches'][] = $related_text; } } return $results; } /** * Parse search results using regex (fallback method) * More reliable when DOM parsing fails * * @param string $html HTML content * @return array Parsed results */ private static function parse_search_results_regex($html) { $results = array( 'organic' => array() ); // Pattern 1: Extract from data-ved divs with h3 and links // Look for divs containing h3 tags followed by links preg_match_all('/]*data-ved[^>]*>.*?]*>(.*?)<\/h3>.*?]*href=["\']([^"\']+)["\'][^>]*>/is', $html, $matches, PREG_SET_ORDER); foreach ($matches as $match) { if (count($match) >= 3) { $title = strip_tags($match[1]); $url = $match[2]; // Clean Google redirect URLs if (strpos($url, '/url?q=') === 0 || strpos($url, '/url?') === 0) { $parsed = parse_url($url); if (isset($parsed['query'])) { parse_str($parsed['query'], $params); if (isset($params['q'])) { $url = $params['q']; } } } // Validate URL if (!empty($title) && !empty($url) && (strpos($url, 'http://') === 0 || strpos($url, 'https://') === 0) && strpos($url, 'google.com') === false) { $results['organic'][] = array( 'title' => trim($title), 'url' => $url, 'snippet' => '' // Will try to extract separately ); } } } // Pattern 2: Extract from class="g" divs if (empty($results['organic'])) { preg_match_all('/]*class=["\'][^"\']*\\bg\\b[^"\']*["\'][^>]*>.*?]*>(.*?)<\/h3>.*?]*href=["\']([^"\']+)["\'][^>]*>/is', $html, $matches, PREG_SET_ORDER); foreach ($matches as $match) { if (count($match) >= 3) { $title = strip_tags($match[1]); $url = $match[2]; // Clean Google redirect URLs if (strpos($url, '/url?q=') === 0 || strpos($url, '/url?') === 0) { $parsed = parse_url($url); if (isset($parsed['query'])) { parse_str($parsed['query'], $params); if (isset($params['q'])) { $url = $params['q']; } } } // Validate URL if (!empty($title) && !empty($url) && (strpos($url, 'http://') === 0 || strpos($url, 'https://') === 0) && strpos($url, 'google.com') === false) { $results['organic'][] = array( 'title' => trim($title), 'url' => $url, 'snippet' => '' ); } } } } // Pattern 3: Simple pattern - any h3 followed by a link if (empty($results['organic'])) { preg_match_all('/]*>(.*?)<\/h3>.*?]*href=["\']([^"\']+)["\'][^>]*>/is', $html, $matches, PREG_SET_ORDER); foreach ($matches as $match) { if (count($match) >= 3) { $title = strip_tags($match[1]); $url = $match[2]; // Skip if it's a Google internal link if (strpos($url, 'google.com') !== false || strpos($url, 'googleusercontent.com') !== false) { continue; } // Clean Google redirect URLs if (strpos($url, '/url?q=') === 0 || strpos($url, '/url?') === 0) { $parsed = parse_url($url); if (isset($parsed['query'])) { parse_str($parsed['query'], $params); if (isset($params['q'])) { $url = $params['q']; } } } // Validate URL if (!empty($title) && strlen($title) > 5 && !empty($url) && (strpos($url, 'http://') === 0 || strpos($url, 'https://') === 0)) { // Check if we already have this URL $exists = false; foreach ($results['organic'] as $existing) { if ($existing['url'] === $url) { $exists = true; break; } } if (!$exists) { $results['organic'][] = array( 'title' => trim($title), 'url' => $url, 'snippet' => '' ); } } } } } // Remove duplicates $unique_results = array(); $seen_urls = array(); foreach ($results['organic'] as $result) { if (!in_array($result['url'], $seen_urls)) { $unique_results[] = $result; $seen_urls[] = $result['url']; } } $results['organic'] = array_slice($unique_results, 0, 10); // Limit to 10 results return $results; } /** * Get search results as simple array (title, url, snippet) * Falls back to Jina AI Search if Google is blocked * * @param string $query Search query * @param array $options Additional options * @return array|WP_Error Simple results array or error */ public static function get_simple_results($query, $options = array()) { $search_result = self::search($query, $options); // If Google search is blocked, try Jina AI Search as fallback if (is_wp_error($search_result) && $search_result->get_error_code() === 'blocked') { if (defined('WP_DEBUG') && WP_DEBUG) { } $jina_result = self::search_jina_ai($query, $options); if (!is_wp_error($jina_result) && isset($jina_result['results']['organic'])) { if (defined('WP_DEBUG') && WP_DEBUG) { } return $jina_result['results']['organic']; } // If Jina AI fails, return Jina AI error (especially if it's no_api_key) if (is_wp_error($jina_result)) { if (defined('WP_DEBUG') && WP_DEBUG) { } // Return Jina AI error if it's a critical error like missing API key if ($jina_result->get_error_code() === 'no_api_key') { return $jina_result; } } } if (is_wp_error($search_result)) { return $search_result; } return $search_result['results']['organic']; } /** * Update cookies for Google search * * @param string $cookies Cookie string */ public static function set_cookies($cookies) { self::$default_cookies = $cookies; } /** * Get current cookies * * @return string Cookie string */ public static function get_cookies() { return self::$default_cookies; } /** * Update headers for Google search * * @param array $headers Headers array */ public static function set_headers($headers) { // This would require storing headers, for now we use get_default_headers() // You can override headers in the search() method via options } /** * Get content from URL using PHP Readability (primary method) * * @param string $url URL to fetch content from * @param array $options Additional options (timeout, etc.) * @return array|WP_Error Content data or error */ private static function get_url_content_with_readability($url, $options = array()) { // Validate URL if (empty($url) || !filter_var($url, FILTER_VALIDATE_URL)) { return new WP_Error('invalid_url', __('Invalid URL provided', 'ai-auto-tool')); } // Check if Readability library exists - try multiple paths // Priority: lib folder (official) > tmp folder (reference) $possible_paths = array( // Primary: Official lib folder dirname(__FILE__) . '/readability/AIAutoTool_Readability.php', plugin_dir_path(__FILE__) . 'readability/AIAutoTool_Readability.php', // Fallback: Reference tmp folder (if exists) ); $readability_path = null; foreach ($possible_paths as $path) { if (file_exists($path)) { $readability_path = $path; break; } } if (!$readability_path) { if (defined('WP_DEBUG') && WP_DEBUG) { } return new WP_Error('readability_not_found', __('PHP Readability library not found', 'ai-auto-tool')); } // Fetch HTML content from URL $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_TIMEOUT, isset($options['timeout']) ? $options['timeout'] : 30); curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36'); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language: en-US,en;q=0.9', )); $html = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curl_error = curl_error($ch); curl_close($ch); // Check for curl errors if ($html === false || !empty($curl_error)) { return new WP_Error('curl_error', __('cURL Error: ', 'ai-auto-tool') . $curl_error); } // Check HTTP status code if ($http_code !== 200) { return new WP_Error('http_error', sprintf(__('HTTP Error: %d', 'ai-auto-tool'), $http_code), array('http_code' => $http_code)); } if (empty($html) || strlen($html) < 100) { return new WP_Error('empty_response', __('Empty or too short HTML response', 'ai-auto-tool')); } // Use Tidy if available to clean HTML if (function_exists('tidy_parse_string')) { $tidy_config = array( 'clean' => true, 'output-html' => true, 'wrap' => 0, 'indent' => false, 'show-body-only' => false ); $tidy = tidy_parse_string($html, $tidy_config, 'UTF8'); if ($tidy) { $tidy->cleanRepair(); $html = (string)$tidy; } } // Load Readability library (AIAutoTool_JSLikeHTMLElement is already required inside AIAutoTool_Readability) require_once $readability_path; // Extract content using Readability try { $readability = new AIAutoTool_Readability($html, $url); $readability->debug = false; $readability->convertLinksToFootnotes = false; $result = $readability->init(); if (defined('WP_DEBUG') && WP_DEBUG) { } // Try to get content even if init() returned false - sometimes content is still available // Get title $title_elem = $readability->getTitle(); $title = ''; if ($title_elem) { $title = isset($title_elem->textContent) ? trim($title_elem->textContent) : ''; } // Get content - try multiple methods (same as wp-automatic example) $content_elem = $readability->getContent(); $content_html = ''; $is_plain_text = false; if ($content_elem) { // Method 1: Try to get HTML using innerHTML magic property (primary method) // Access directly like wp-automatic does: $content_elem->innerHTML try { @$content_html = $content_elem->innerHTML; // Suppress warnings if (empty($content_html) || !is_string($content_html)) { $content_html = ''; } } catch (Exception $e) { $content_html = ''; } catch (Error $e) { $content_html = ''; } // Method 2: Try getting HTML using ownerDocument->saveHTML() (more reliable) if (empty($content_html) && isset($content_elem->ownerDocument)) { try { $content_html = $content_elem->ownerDocument->saveHTML($content_elem); $content_html = trim($content_html); // Remove wrapper tags if present $content_html = preg_replace('/^<[^>]+>/', '', $content_html); $content_html = preg_replace('/<\/[^>]+>$/', '', $content_html); $content_html = trim($content_html); if (defined('WP_DEBUG') && WP_DEBUG) { } } catch (Exception $e) { if (defined('WP_DEBUG') && WP_DEBUG) { } } } // Method 3: If no HTML, use textContent (plain text) - this often works when innerHTML doesn't if (empty($content_html) && isset($content_elem->textContent)) { $text_content = trim($content_elem->textContent); if (!empty($text_content) && strlen($text_content) > 100) { $content_html = $text_content; $is_plain_text = true; // Mark as plain text, no HTML conversion needed if (defined('WP_DEBUG') && WP_DEBUG) { } } } } if (defined('WP_DEBUG') && WP_DEBUG) { } if (!empty($content_html) && is_string($content_html) && strlen($content_html) > 100) { // Helper function to safely do preg_replace $safe_replace = function($pattern, $replacement, $subject) { if (!is_string($subject) || $subject === null) { return ''; } $result = @preg_replace($pattern, $replacement, $subject); return ($result !== null && is_string($result)) ? $result : $subject; }; if (!$is_plain_text) { // Process HTML content - convert to markdown // Clean up Readability attributes $content_html = $safe_replace('{ wp_automatic_Readability\=".*?"}s', '', $content_html); // Remove script and style tags $content_html = $safe_replace('/)<[^<]*)*<\/script>/is', '', $content_html); $content_html = $safe_replace('/)<[^<]*)*<\/style>/is', '', $content_html); // Convert HTML to markdown format // Headings - preserve level $content_html = $safe_replace('/]*>(.*?)<\/h1>/is', "\n\n# $1\n\n", $content_html); $content_html = $safe_replace('/]*>(.*?)<\/h2>/is', "\n\n## $1\n\n", $content_html); $content_html = $safe_replace('/]*>(.*?)<\/h3>/is', "\n\n### $1\n\n", $content_html); $content_html = $safe_replace('/]*>(.*?)<\/h4>/is', "\n\n#### $1\n\n", $content_html); $content_html = $safe_replace('/]*>(.*?)<\/h5>/is', "\n\n##### $1\n\n", $content_html); $content_html = $safe_replace('/]*>(.*?)<\/h6>/is', "\n\n###### $1\n\n", $content_html); // Paragraphs $content_html = $safe_replace('/]*>(.*?)<\/p>/is', "$1\n\n", $content_html); // Line breaks $content_html = $safe_replace('/]*\/?>/i', "\n", $content_html); // Strong/Bold $content_html = $safe_replace('/]*>(.*?)<\/strong>/is', "**$1**", $content_html); $content_html = $safe_replace('/]*>(.*?)<\/b>/is', "**$1**", $content_html); // Emphasis/Italic $content_html = $safe_replace('/]*>(.*?)<\/em>/is', "*$1*", $content_html); $content_html = $safe_replace('/]*>(.*?)<\/i>/is', "*$1*", $content_html); // Links - extract href and text $content_html = $safe_replace('/]*href=["\']([^"\']+)["\'][^>]*>(.*?)<\/a>/is', "[$2]($1)", $content_html); // Lists $content_html = $safe_replace('/]*>(.*?)<\/li>/is', "- $1\n", $content_html); $content_html = $safe_replace('/]*>|<\/ul>|]*>|<\/ol>/is', "\n", $content_html); // Blockquotes $content_html = $safe_replace('/]*>(.*?)<\/blockquote>/is', "\n> $1\n\n", $content_html); // Remove all remaining HTML tags $content_html = strip_tags($content_html); // Decode HTML entities $content_html = html_entity_decode($content_html, ENT_QUOTES | ENT_HTML5, 'UTF-8'); } // Clean up whitespace (for both HTML and plain text) $content_html = $safe_replace('/[ \t]+/u', ' ', $content_html); // Multiple spaces to single $content_html = $safe_replace('/\n{3,}/', "\n\n", $content_html); // Multiple newlines to double $content_html = trim($content_html); return array( 'success' => true, 'url' => $url, 'content' => $content_html, 'title' => $title, 'format' => 'readability', 'length' => strlen($content_html), 'method' => 'php_readability' ); } else { // Content is too short or empty if (defined('WP_DEBUG') && WP_DEBUG) { } } // If we get here, Readability failed to extract meaningful content if (defined('WP_DEBUG') && WP_DEBUG) { } return new WP_Error('readability_failed', __('PHP Readability could not extract content from this page', 'ai-auto-tool')); } catch (Exception $e) { return new WP_Error('readability_exception', __('PHP Readability error: ', 'ai-auto-tool') . $e->getMessage()); } } /** * Get content from URL - tries PHP Readability first, falls back to Jina AI Reader * * @param string $url URL to fetch content from * @param array $options Additional options (api_key, format, timeout, etc.) * @return array|WP_Error Content data or error */ public static function get_url_content($url, $options = array()) { // Validate URL if (empty($url) || !filter_var($url, FILTER_VALIDATE_URL)) { return new WP_Error('invalid_url', __('Invalid URL provided', 'ai-auto-tool')); } // Try PHP Readability first (primary method) if (defined('WP_DEBUG') && WP_DEBUG) { } $readability_result = self::get_url_content_with_readability($url, $options); // If Readability succeeded, return the result if (!is_wp_error($readability_result) && isset($readability_result['success']) && $readability_result['success']) { if (defined('WP_DEBUG') && WP_DEBUG) { } return $readability_result; } // Readability failed, fallback to Jina AI API if (defined('WP_DEBUG') && WP_DEBUG) { $error_msg = is_wp_error($readability_result) ? $readability_result->get_error_message() : 'Unknown error'; } // Build Jina AI Reader URL $jina_url = 'https://r.jina.ai/' . urlencode($url); // Prepare headers $headers = array( 'Accept: text/plain, text/markdown, application/json', 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36' ); // Add API key if provided if (isset($options['api_key']) && !empty($options['api_key'])) { $headers[] = 'X-Return-Format: markdown'; $headers[] = 'Authorization: Bearer ' . $options['api_key']; } else { // Try to get API key from options or settings $api_key = isset($options['jina_api_key']) ? $options['jina_api_key'] : (class_exists('AIAutotool_AI_Provider_API_Keys') ? AIAutotool_AI_Provider_API_Keys::get_jina_key() : get_option('aiautotool_jina_api_key', '')); if (!empty($api_key)) { $headers[] = 'X-Return-Format: markdown'; $headers[] = 'Authorization: Bearer ' . $api_key; } } // Add format option if (isset($options['format'])) { $headers[] = 'X-Return-Format: ' . $options['format']; } else { $headers[] = 'X-Return-Format: markdown'; } // Initialize curl $ch = curl_init($jina_url); // Set curl options curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_TIMEOUT, isset($options['timeout']) ? $options['timeout'] : 60); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); // Execute request $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curl_error = curl_error($ch); curl_close($ch); // Check for curl errors if ($response === false || !empty($curl_error)) { return new WP_Error('curl_error', __('cURL Error: ', 'ai-auto-tool') . $curl_error); } // Check HTTP status code if ($http_code !== 200) { $error_msg = sprintf(__('HTTP Error: %d', 'ai-auto-tool'), $http_code); if ($http_code === 429) { $error_msg = __('Rate limit exceeded. Please try again later or add API key.', 'ai-auto-tool'); } elseif ($http_code === 401) { $error_msg = __('Unauthorized. Please check your API key.', 'ai-auto-tool'); } return new WP_Error('http_error', $error_msg, array('http_code' => $http_code)); } // Parse response $content = trim($response); // Try to parse as JSON if it's JSON format $json_data = json_decode($content, true); if (json_last_error() === JSON_ERROR_NONE && is_array($json_data)) { return array( 'success' => true, 'url' => $url, 'content' => isset($json_data['content']) ? $json_data['content'] : $content, 'title' => isset($json_data['title']) ? $json_data['title'] : '', 'description' => isset($json_data['description']) ? $json_data['description'] : '', 'format' => 'json', 'raw_response' => $json_data ); } return array( 'success' => true, 'url' => $url, 'content' => $content, 'format' => isset($options['format']) ? $options['format'] : 'markdown', 'length' => strlen($content), 'raw_response' => $response ); } /** * Get content from multiple URLs using Jina AI Reader * * @param array $urls Array of URLs to fetch * @param array $options Additional options * @return array Array of results (each can be content or WP_Error) */ public static function get_multiple_urls_content($urls, $options = array()) { if (empty($urls) || !is_array($urls)) { return new WP_Error('invalid_input', __('Invalid URLs array provided', 'ai-auto-tool')); } $results = array(); $delay = isset($options['delay']) ? (int)$options['delay'] : 1; // Delay between requests in seconds foreach ($urls as $index => $url) { // Add delay between requests to avoid rate limiting if ($index > 0 && $delay > 0) { sleep($delay); } $result = self::get_url_content($url, $options); $results[] = array( 'url' => $url, 'result' => $result ); } return $results; } /** * Search using Jina AI Search API (fallback when Google is blocked) * * @param string $query Search query * @param array $options Additional options (api_key, limit, etc.) * @return array|WP_Error Search results or error */ public static function search_jina_ai($query, $options = array()) { // Check if Jina AI API key is available $api_key = isset($options['api_key']) ? $options['api_key'] : (isset($options['jina_api_key']) ? $options['jina_api_key'] : (class_exists('AIAutotool_AI_Provider_API_Keys') ? AIAutotool_AI_Provider_API_Keys::get_jina_key() : get_option('aiautotool_jina_api_key', ''))); if (empty($api_key)) { return new WP_Error('no_api_key', 'Jina AI API key is not configured. Please set up your Jina AI API token in the plugin settings to use this feature.'); } // Build Jina AI Search URL $jina_search_url = 'https://s.jina.ai/?q=' . urlencode($query); // Prepare headers $headers = array( 'Accept: application/json', 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36', 'X-Respond-With: no-content' // Don't fetch full content, just search results ); // Add API key to headers $headers[] = 'Authorization: Bearer ' . $api_key; // Initialize curl $ch = curl_init($jina_search_url); // Set curl options curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_TIMEOUT, isset($options['timeout']) ? $options['timeout'] : 30); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); // Execute request $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curl_error = curl_error($ch); curl_close($ch); // Check for curl errors if ($response === false || !empty($curl_error)) { return new WP_Error('curl_error', __('cURL Error: ', 'ai-auto-tool') . $curl_error); } // Check HTTP status code if ($http_code !== 200) { $error_msg = sprintf(__('HTTP Error: %d', 'ai-auto-tool'), $http_code); if ($http_code === 429) { $error_msg = __('Rate limit exceeded. Please try again later.', 'ai-auto-tool'); } elseif ($http_code === 401) { $error_msg = __('Unauthorized. Please check your Jina AI API key.', 'ai-auto-tool'); } return new WP_Error('http_error', $error_msg, array('http_code' => $http_code)); } // Parse JSON response $json_data = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE || !is_array($json_data)) { return new WP_Error('parse_error', __('Failed to parse Jina AI search response', 'ai-auto-tool')); } // Convert Jina AI format to our format $results = array( 'organic' => array(), 'ads' => array(), 'featured_snippet' => null, 'people_also_ask' => array(), 'related_searches' => array() ); // Jina AI returns results in different format, need to adapt if (isset($json_data['results']) && is_array($json_data['results'])) { foreach ($json_data['results'] as $item) { $result = array( 'title' => isset($item['title']) ? $item['title'] : (isset($item['name']) ? $item['name'] : ''), 'url' => isset($item['url']) ? $item['url'] : (isset($item['link']) ? $item['link'] : ''), 'snippet' => isset($item['snippet']) ? $item['snippet'] : (isset($item['description']) ? $item['description'] : '') ); if (!empty($result['title']) && !empty($result['url'])) { $results['organic'][] = $result; } } } elseif (isset($json_data['data']) && is_array($json_data['data'])) { // Alternative format foreach ($json_data['data'] as $item) { $result = array( 'title' => isset($item['title']) ? $item['title'] : '', 'url' => isset($item['url']) ? $item['url'] : '', 'snippet' => isset($item['snippet']) ? $item['snippet'] : '' ); if (!empty($result['title']) && !empty($result['url'])) { $results['organic'][] = $result; } } } return array( 'success' => true, 'query' => $query, 'url' => $jina_search_url, 'http_code' => $http_code, 'results' => $results, 'source' => 'jina_ai' ); } /** * Get content from Google search results URLs * * @param string $query Search query * @param int $limit Number of URLs to fetch content from (default: 5) * @param array $options Additional options * @return array|WP_Error Array of content results or error */ public static function search_and_get_content($query, $limit = 5, $options = array()) { // Perform search $search_result = self::search($query, $options); if (is_wp_error($search_result)) { return $search_result; } // Get URLs from search results $urls = array(); $organic_results = isset($search_result['results']['organic']) ? $search_result['results']['organic'] : array(); foreach (array_slice($organic_results, 0, $limit) as $result) { if (isset($result['url']) && !empty($result['url'])) { $urls[] = $result['url']; } } if (empty($urls)) { return new WP_Error('no_urls', __('No URLs found in search results', 'ai-auto-tool')); } // Get content from URLs $content_results = self::get_multiple_urls_content($urls, $options); // Combine search results with content $combined_results = array(); foreach ($content_results as $index => $item) { $combined_results[] = array( 'url' => $item['url'], 'search_title' => isset($organic_results[$index]['title']) ? $organic_results[$index]['title'] : '', 'search_snippet' => isset($organic_results[$index]['snippet']) ? $organic_results[$index]['snippet'] : '', 'content' => is_wp_error($item['result']) ? null : $item['result'], 'error' => is_wp_error($item['result']) ? $item['result']->get_error_message() : null ); } return array( 'success' => true, 'query' => $query, 'search_results_count' => count($organic_results), 'urls_fetched' => count($urls), 'results' => $combined_results ); } } }