43200, // 12 hours in seconds 'display' => esc_html__('Every 12 Hours', 'bd-bookshelf'), ]; return $schedules; } /** * Sync all book ratings */ public function sync_all_ratings() { $books = BDM_Book_Manager::get_books(['number' => 999]); $synced = 0; $failed = 0; foreach ($books as $book) { $result = $this->sync_book_rating($book->id); if ($result) { $synced++; } else { $failed++; } // Delay between requests to avoid rate limiting sleep(2); } // Log results update_option('bd_bookshelf_last_sync', [ 'time' => current_time('mysql'), 'synced' => $synced, 'failed' => $failed, 'total' => count($books), ]); return ['synced' => $synced, 'failed' => $failed]; } /** * Sync rating for a single book */ public function sync_book_rating($book_id) { global $wpdb; $book = BDM_Book_Manager::get_book($book_id); if (!$book) { return false; } $amazon_rating = 0; $goodreads_rating = 0; // Sync Amazon rating if (!empty($book->amazon_asin) || !empty($book->isbn_ebook)) { $isbn = $book->amazon_asin ?: $book->isbn_ebook; $amazon_rating = $this->fetch_amazon_rating($isbn); // Keep last rating if fetch fails if ($amazon_rating === false) { $amazon_rating = $book->rating_amazon; } } // Sync Goodreads rating if (!empty($book->goodreads_id)) { $goodreads_rating = $this->fetch_goodreads_rating($book->goodreads_id); // Keep last rating if fetch fails if ($goodreads_rating === false) { $goodreads_rating = $book->rating_goodreads; } } // Update book ratings $table = BDM_Database::table('books'); $wpdb->update( $table, [ 'rating_amazon' => $amazon_rating, 'rating_goodreads' => $goodreads_rating, 'last_sync' => current_time('mysql'), ], ['id' => $book_id], ['%f', '%f', '%s'], ['%d'] ); return true; } /** * Fetch Amazon rating via web scraping */ private function fetch_amazon_rating($isbn) { $url = "https://www.amazon.com/dp/{$isbn}"; $response = wp_remote_get($url, [ 'timeout' => 15, 'headers' => [ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language' => 'en-US,en;q=0.5', ], ]); if (is_wp_error($response)) { error_log('BD Bookshelf: Amazon fetch error - ' . $response->get_error_message()); return false; } $body = wp_remote_retrieve_body($response); // Parse rating from HTML // Amazon uses data-hook="review-star-rating" or similar if (preg_match('/data-hook="review-star-rating"[^>]*>.*?(\d+\.?\d*)\s*out of\s*5/si', $body, $matches)) { return (float) $matches[1]; } // Alternative pattern if (preg_match('/a-icon-alt[^>]*>(\d+\.?\d*)\s*out of\s*\d+ stars/si', $body, $matches)) { return (float) $matches[1]; } return false; } /** * Fetch Goodreads rating via web scraping */ private function fetch_goodreads_rating($goodreads_id) { $url = "https://www.goodreads.com/book/show/{$goodreads_id}"; $response = wp_remote_get($url, [ 'timeout' => 15, 'headers' => [ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language' => 'en-US,en;q=0.5', ], ]); if (is_wp_error($response)) { error_log('BD Bookshelf: Goodreads fetch error - ' . $response->get_error_message()); return false; } $body = wp_remote_retrieve_body($response); // Parse rating from HTML // Goodreads uses ratingValue in meta or specific class if (preg_match('/ratingValue[^>]*>(\d+\.?\d*)]*>(\d+\.?\d*)sync_all_ratings(); } /** * Get last sync info */ public static function get_last_sync_info() { return get_option('bd_bookshelf_last_sync', []); } }