// ========================== // 3. Manual Fetch Function (Updated) // ========================== function serpapi_run_each_keyword_manual() { $serpapi_key = get_option('serpapi_api_key'); $openai_key = get_option('serpapi_openai_api_key'); $recipient_emails = get_option('serpapi_recipient_emails'); $max_urls = intval(get_option('serpapi_max_urls', 5000)); $max_posts_per_keyword = 10; // limit posts per keyword $keywords = array_map('trim', explode(',', get_option('serpapi_keywords', ''))); if (!$serpapi_key || !$recipient_emails || empty($keywords)) return; $emailed_urls = get_option('serpapi_emailed_urls', []); // Split recipients $recipients = array_map('trim', explode(',', $recipient_emails)); foreach ($keywords as $keyword) { $results_data = serpapi_search_keyword_manual($keyword, $emailed_urls, $serpapi_key, $openai_key, $max_posts_per_keyword); if (!empty(trim($results_data['results']))) { $subject = ucfirst($keyword) . " Construction News"; $headers = ['Content-Type: text/plain; charset=UTF-8']; foreach ($recipients as $email) { if (filter_var($email, FILTER_VALIDATE_EMAIL)) { wp_mail($email, $subject, $results_data['results'], $headers); } } } $emailed_urls = $results_data['emailed_urls']; } // Trim stored URLs to max limit $emailed_urls = array_slice($emailed_urls, -$max_urls); update_option('serpapi_emailed_urls', $emailed_urls); } // ========================== // 4. Fetch and Filter Articles (Updated) // ========================== function serpapi_search_keyword_manual($keyword, $emailed_urls, $serpapi_key, $openai_key, $max_posts = 10) { $query = sanitize_text_field($keyword) . " construction project"; $url = "https://serpapi.com/search?engine=google_news&q=" . urlencode($query) . "&tbs=qdr:d&hl=en&api_key=" . $serpapi_key; $response = wp_remote_get($url); if (is_wp_error($response)) return ['results'=>'','emailed_urls'=>$emailed_urls]; $data = json_decode(wp_remote_retrieve_body($response), true); if (!isset($data['news_results'])) return ['results'=>'','emailed_urls'=>$emailed_urls]; $cutoff = strtotime('-24 hours'); $results = ""; $count = 0; foreach ($data['news_results'] as $article) { if ($count >= $max_posts) break; // stop after max_posts if (!isset($article['title']) || !isset($article['link'])) continue; $title = sanitize_text_field($article['title']); $link = esc_url_raw($article['link']); if (in_array($link, $emailed_urls)) continue; // Check published date $published_str = $article['date'] ?? ''; $published_time = strtotime($published_str); if ($published_time && $published_time < $cutoff) continue; // OpenAI filtering: relevance + real project if ($openai_key) { $prompt = " Determine if this news article is a REAL construction project update published in the last 24 hours. Return ONLY 'YES' or 'NO'. Title: $title URL: $link Return YES only if: - It is a real construction project (solar, wind, data center, airport, rail, stadium, mall, housing, etc.) - AND it includes a milestone such as: groundbreaking, approval, planning, funding, contract award, construction start, expansion, commissioning, completion. Return NO if: - It is corporate news, political news, general business, opinion, policy, or non-project content. "; $is_project = serpapi_filter_with_openai_prompt($prompt, $openai_key); if (!$is_project) continue; } $results .= "$title\n$link\n\n"; $emailed_urls[] = $link; $count++; } return ['results'=>$results,'emailed_urls'=>$emailed_urls]; } // ========================== // 5. OpenAI Project Filter (Updated) // ========================== function serpapi_filter_with_openai_prompt($prompt, $api_key) { $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [ 'headers' => [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $api_key, ], 'body' => json_encode([ 'model' => 'gpt-4o-mini', 'messages' => [ ['role'=>'user','content'=>$prompt] ], 'temperature'=>0, 'max_tokens'=>5 ]) ]); if (is_wp_error($response)) return false; $data = json_decode(wp_remote_retrieve_body($response), true); if (!isset($data['choices'][0]['message']['content'])) return false; return strtoupper(trim($data['choices'][0]['message']['content'])) === 'YES'; }