Files
Bookshelf/bd-bookshelf/inc/book-api.php
2026-10-18 19:01:01 -07:00

287 lines
7.7 KiB
PHP

<?php
// Copyright 2026 Umbra Tools (umbratools.com)
// SPDX-License-Identifier: Apache-2.0
/**
* External Book Data API Helpers
*
* @package BD_Murphy
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* Get book data from Goodreads
*
* @param string $author_id Goodreads author ID.
* @param int $timeout Response timeout in seconds.
* @return array|WP_Error
*/
function bdm_get_goodreads_books($author_id, $timeout = 30) {
if (!get_theme_mod('enable_goodreads_fetch', false)) {
return new WP_Error('consent_required', __('Goodreads integration is not enabled.', 'bd-bookshelf'));
}
if (empty($author_id)) {
return new WP_Error('missing_author_id', __('Goodreads author ID is required.', 'bd-bookshelf'));
}
$url = 'https://www.goodreads.com/author/show/' . urlencode($author_id);
$response = wp_safe_remote_get($url, array(
'timeout' => $timeout,
'headers' => array(
'User-Agent' => 'BDMurphy-BookShelf/2.0',
),
));
if (is_wp_error($response)) {
return $response;
}
$body = wp_remote_retrieve_body($response);
if (empty($body)) {
return new WP_Error('empty_response', __('Empty response from Goodreads.', 'bd-bookshelf'));
}
$books = array();
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML($body);
$xpath = new DOMXPath($doc);
$book_rows = $xpath->query("//table[@class='tableList']//tr[contains(@class, 'bookalike')]");
foreach ($book_rows as $row) {
$title_node = $xpath->query(".//a[contains(@class, 'bookTitle')]", $row)->item(0);
$asin_node = $xpath->query(".//input[@class='asin']", $row)->item(0);
$rating_node = $xpath->query(".//span[@class='minirating']", $row)->item(0);
$cover_node = $xpath->query(".//img[@class='bookCover']", $row)->item(0);
$title = $title_node ? trim($title_node->textContent) : '';
$url = $title_node ? $title_node->getAttribute('href') : '';
$asin = $asin_node ? $asin_node->getAttribute('value') : '';
$rating = '';
if ($rating_node) {
$rating_text = trim($rating_node->textContent);
if (preg_match('/([\d.]+) avg rating/i', $rating_text, $matches)) {
$rating = $matches[1];
}
}
$cover = $cover_node ? $cover_node->getAttribute('src') : '';
if ($title) {
$books[] = array(
'title' => $title,
'url' => $url,
'asin' => $asin,
'rating' => $rating,
'cover' => $cover,
);
}
}
return $books;
}
/**
* Get book data from Amazon Author Central
*
* @param string $author_id Amazon Author Central author slug/page ID.
* @param int $timeout Response timeout in seconds.
* @return array|WP_Error
*/
function bdm_get_amazon_books($author_id, $timeout = 30) {
if (!get_theme_mod('enable_goodreads_fetch', false)) {
return new WP_Error('consent_required', __('External book data fetching is not enabled.', 'bd-bookshelf'));
}
if (empty($author_id)) {
return new WP_Error('missing_author_id', __('Amazon author ID is required.', 'bd-bookshelf'));
}
$url = 'https://www.amazon.com/author/' . urlencode($author_id) . '/books';
$response = wp_safe_remote_get($url, array(
'timeout' => $timeout,
'headers' => array(
'User-Agent' => 'BDMurphy-BookShelf/2.0',
),
));
if (is_wp_error($response)) {
return $response;
}
$body = wp_remote_retrieve_body($response);
if (empty($body)) {
return new WP_Error('empty_response', __('Empty response from Amazon.', 'bd-bookshelf'));
}
$books = array();
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML($body);
$xpath = new DOMXPath($doc);
$product_nodes = $xpath->query("//div[contains(@class, 'product')]//a[contains(@class, 'product-title')]");
foreach ($product_nodes as $node) {
$title = trim($node->textContent);
$url = $node->getAttribute('href');
if ($title && $url) {
$books[] = array(
'title' => $title,
'url' => $url,
);
}
}
return $books;
}
/**
* Get ASIN from Amazon book URL
*
* @param string $url Amazon product URL.
* @return string|null
*/
function bdm_extract_asin_from_url($url) {
if (empty($url)) {
return null;
}
if (preg_match('/\/dp\/([A-Z0-9]{10})/i', $url, $matches)) {
return $matches[1];
}
if (preg_match('/\/gp\/product\/([A-Z0-9]{10})/i', $url, $matches)) {
return $matches[1];
}
if (preg_match('/(?:amazon\.com.*?)([A-Z0-9]{10})/i', $url, $matches)) {
return $matches[1];
}
return null;
}
/**
* Get product data from Amazon API (via ISBN/ASIN)
*
* @param string $isbn_or_asin ISBN-10, ISBN-13, or ASIN.
* @param string $region Amazon region (com, co.uk, de, etc).
* @param int $timeout Response timeout in seconds.
* @return array|WP_Error
*/
function bdm_get_amazon_product_data($isbn_or_asin, $region = 'com', $timeout = 30) {
if (!get_theme_mod('enable_goodreads_fetch', false)) {
return new WP_Error('consent_required', __('External book data fetching is not enabled.', 'bd-bookshelf'));
}
$asin = strtoupper($isbn_or_asin);
if (strlen($asin) === 13 && is_numeric($asin)) {
$asin = bdm_isbn13_to_asin($asin);
if (!$asin) {
return new WP_Error('invalid_isbn', __('Could not convert ISBN-13 to ASIN.', 'bd-bookshelf'));
}
}
if (strlen($asin) !== 10) {
$asin = strtoupper($isbn_or_asin);
}
$domain = 'amazon.' . $region;
$url = 'https://' . $domain . '/gp/product/' . $asin;
$response = wp_safe_remote_get($url, array(
'timeout' => $timeout,
'headers' => array(
'User-Agent' => 'BDMurphy-BookShelf/2.0',
),
));
if (is_wp_error($response)) {
return $response;
}
$body = wp_remote_retrieve_body($response);
if (empty($body)) {
return new WP_Error('empty_response', __('Empty response from Amazon.', 'bd-bookshelf'));
}
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML($body);
$xpath = new DOMXPath($doc);
$title = '';
$title_node = $xpath->query("//span[@id='productTitle']")->item(0);
if ($title_node) {
$title = trim($title_node->textContent);
}
$price = '';
$price_nodes = $xpath->query("//span[contains(@class, 'a-price-whole')]");
if ($price_nodes->length > 0) {
$price = trim($price_nodes->item(0)->textContent);
}
$rating = '';
$rating_nodes = $xpath->query("//span[contains(@class, 'a-icon-alt')]");
if ($rating_nodes->length > 0) {
$rating_text = trim($rating_nodes->item(0)->textContent);
if (preg_match('/^([\d.]+)/', $rating_text, $matches)) {
$rating = $matches[1];
}
}
return array(
'asin' => $asin,
'title' => $title,
'price' => $price,
'rating' => $rating,
'url' => $url,
);
}
/**
* Convert ISBN-13 to ISBN-10/ASIN
*
* @param string $isbn13 ISBN-13.
* @return string|false
*/
function bdm_isbn13_to_asin($isbn13) {
$isbn13 = preg_replace('/[^0-9X]/i', '', $isbn13);
if (strlen($isbn13) !== 13) {
return false;
}
$isbn10 = substr($isbn13, 3, 9);
$sum = 0;
for ($i = 0; $i < 9; $i++) {
$sum += ($i + 1) * intval($isbn10[$i]);
}
$remainder = $sum % 11;
$check_digit = (11 - $remainder) % 11;
if ($check_digit === 10) {
$isbn10 .= 'X';
} else {
$isbn10 .= $check_digit;
}
return $isbn10;
}