contentEx = array('BookInfo_Plugin', 'parse'); Typecho_Plugin::factory('Widget_Abstract_Contents')->excerptEx = array('BookInfo_Plugin', 'parse'); Typecho_Plugin::factory('admin/write-post.php')->bottom = array('BookInfo_Plugin', 'renderButton'); Typecho_Plugin::factory('admin/write-page.php')->bottom = array('BookInfo_Plugin', 'renderButton'); $cacheDir = dirname(__FILE__) . '/cache/'; if (!file_exists($cacheDir)) mkdir($cacheDir, 0755, true); return '插件激活成功!'; } /** * 禁用插件 */ public static function deactivate() { return '插件已禁用'; } /** * 配置面板 */ public static function config(Typecho_Widget_Helper_Form $form) { $cacheEnable = new Typecho_Widget_Helper_Form_Element_Radio('cacheEnable', array('1' => '启用', '0' => '禁用'), '1', '启用缓存', '缓存图书信息,提升访问速度'); $form->addInput($cacheEnable); $cacheTime = new Typecho_Widget_Helper_Form_Element_Text('cacheTime', NULL, '30', '缓存时间(天)', '图书信息缓存保留天数'); $cacheTime->addRule('isInteger', '请输入整数'); $form->addInput($cacheTime); $imageProxy = new Typecho_Widget_Helper_Form_Element_Text('imageProxy', NULL, 'https://images.weserv.nl/?url=', '图片代理', '用于加载豆瓣图片'); $form->addInput($imageProxy); $defaultCover = new Typecho_Widget_Helper_Form_Element_Text('defaultCover', NULL, 'https://img9.doubanio.com/f/shire/5522dd1f5b742d1e1394a17f44d590646b63871d/pics/book-default-lpic.gif', '默认封面', '当无法获取封面时显示的图片'); $form->addInput($defaultCover); $summaryLength = new Typecho_Widget_Helper_Form_Element_Text('summaryLength', NULL, '200', '简介显示长度', '简介默认显示的最大字符数,超出部分可展开查看'); $summaryLength->addRule('isInteger', '请输入整数'); $form->addInput($summaryLength); $expandText = new Typecho_Widget_Helper_Form_Element_Text('expandText', NULL, '展开', '"展开"文字', '点击展开完整简介的文字'); $form->addInput($expandText); $collapseText = new Typecho_Widget_Helper_Form_Element_Text('collapseText', NULL, '收起', '"收起"文字', '点击收起简介的文字'); $form->addInput($collapseText); $expandColor = new Typecho_Widget_Helper_Form_Element_Text('expandColor', NULL, '#0073aa', '展开按钮颜色', '展开/收起按钮的文字颜色'); $form->addInput($expandColor); // 新增:独立页面每页显示条数设置 $pageSize = new Typecho_Widget_Helper_Form_Element_Text('pageSize', NULL, '10', '独立页面每页显示条数', '在独立页面中每页显示的图书数量(1-50)'); $pageSize->addRule('isInteger', '请输入整数'); $pageSize->addRule(array(new BookInfo_Plugin, 'validatePageSize'), '请输入1-50之间的整数'); $form->addInput($pageSize); } /** * 验证页面显示条数 */ public static function validatePageSize($value) { $value = intval($value); if ($value < 1 || $value > 50) { throw new Typecho_Widget_Exception('请输入1-50之间的整数'); } return true; } /** * 个人配置面板 */ public static function personalConfig(Typecho_Widget_Helper_Form $form) { } /** * 解析短代码 */ public static function parse($content, $widget, $lastResult) { $content = empty($lastResult) ? $content : $lastResult; // 如果是独立页面,且内容中包含[all_books]标记,则显示所有图书 if ($widget instanceof Widget_Archive && $widget->is('page')) { // 支持[all_books]和[all_books:page=1]格式 $pattern = '/\[all_books(?::page=(\d+))?\]/i'; if (preg_match_all($pattern, $content, $matches)) { foreach ($matches[0] as $key => $match) { $page = isset($matches[1][$key]) ? intval($matches[1][$key]) : 1; $allBooksHtml = self::renderAllBooks($page); $content = str_replace($match, $allBooksHtml, $content); } } } // 如果是单篇文章,解析图书短代码 if ($widget instanceof Widget_Archive && $widget->is('single')) { // 匹配 [book:数字] 或 [book:数字:短评] 格式 $pattern = '/\[book:(\d+)(?::([^\]]+))?\]/i'; if (preg_match_all($pattern, $content, $matches)) { foreach ($matches[0] as $key => $match) { $bookId = $matches[1][$key]; $reviewWithCustom = isset($matches[2][$key]) ? trim($matches[2][$key]) : ''; $review = ''; $customData = array(); // 解码短评和自定义数据 if (!empty($reviewWithCustom)) { // 分离短评和自定义数据 if (strpos($reviewWithCustom, '|CUSTOM:') !== false) { list($review, $customJson) = explode('|CUSTOM:', $reviewWithCustom, 2); // 解码自定义数据 if (!empty($customJson)) { // URL解码 $decodedJson = urldecode($customJson); // 解析JSON $customData = json_decode($decodedJson, true); if (!is_array($customData)) { $customData = array(); } } } else { // 检查是否是纯自定义数据(没有短评) if (preg_match('/^\(自定义:(.*)\)$/', $reviewWithCustom, $customMatch)) { // 这是旧格式的自定义数据,需要转换 $review = ''; $customData = self::parseLegacyCustomData($customMatch[1]); } else { // 重要修复:处理中文自定义数据(如:短评(自定义:开始阅读:2025.12.03)) // 检查是否包含中文括号格式的自定义数据 if (preg_match('/^(.*?)(自定义:(.*))$/u', $reviewWithCustom, $customMatch)) { // 第一部分是短评 $review = trim($customMatch[1]); // 第二部分是自定义数据 $customData = self::parseLegacyCustomData($customMatch[2]); } else { // 没有自定义数据,只有短评 $review = $reviewWithCustom; } } } // 解码短评(处理特殊字符)- 修复:只解码真正的短评部分 if (!empty($review)) { // 处理HTML实体转义 $review = str_replace(array('[', ']'), array('[', ']'), $review); } } // 获取图书数据(包含短评和自定义数据) $bookHtml = self::renderBook($bookId, $review, $customData); $content = str_replace($match, $bookHtml, $content); } } } return $content; } /** * 解析旧格式的自定义数据 */ private static function parseLegacyCustomData($customText) { $customData = array(); // 解析旧格式:开始阅读:2025.12.03,结束阅读:2025.12.07,阅读方法:速读,图书分类:小说,推荐指数:★★ $pairs = explode(',', $customText); foreach ($pairs as $pair) { if (strpos($pair, ':') !== false) { list($key, $value) = explode(':', $pair, 2); switch (trim($key)) { case '开始阅读': $customData['startDate'] = trim($value); break; case '结束阅读': $customData['readDate'] = trim($value); break; case '阅读方法': $customData['readMethod'] = trim($value); break; case '图书分类': $customData['bookCategory'] = trim($value); break; case '推荐指数': // 计算星星数量 $starCount = substr_count($value, '★'); $customData['recommendation'] = $starCount; break; } } } return $customData; } /** * 渲染单本图书信息 */ private static function renderBook($bookId, $review = '', $customData = array()) { // 获取图书数据(包含短评和自定义数据) $bookData = self::getBookData($bookId, $review, $customData); if (!$bookData || empty($bookData['title'])) { return '
获取图书信息失败,ID:' . htmlspecialchars($bookId) . '
'; } $options = Typecho_Widget::widget('Widget_Options')->plugin('BookInfo'); $imageProxy = isset($options->imageProxy) ? $options->imageProxy : 'https://images.weserv.nl/?url='; $defaultCover = isset($options->defaultCover) ? $options->defaultCover : 'https://img9.doubanio.com/f/shire/5522dd1f5b742d1e1394a17f44d590646b63871d/pics/book-default-lpic.gif'; $summaryLength = isset($options->summaryLength) ? intval($options->summaryLength) : 200; $expandText = isset($options->expandText) ? $options->expandText : '展开'; $collapseText = isset($options->collapseText) ? $options->collapseText : '收起'; $expandColor = isset($options->expandColor) ? $options->expandColor : '#0073aa'; $title = htmlspecialchars($bookData['title']); $author = is_array($bookData['author']) ? implode(', ', $bookData['author']) : htmlspecialchars($bookData['author']); $summary = isset($bookData['summary']) ? $bookData['summary'] : ''; // 重要修复:直接从传入的$review获取短评,确保与编辑器输入一致 $review = htmlspecialchars($review); // 豆瓣抓取字段 $publisher = isset($bookData['publisher']) ? htmlspecialchars($bookData['publisher']) : '未知'; $pubdate = isset($bookData['pubdate']) ? htmlspecialchars($bookData['pubdate']) : '未知'; $pages = isset($bookData['pages']) ? htmlspecialchars($bookData['pages']) : '未知'; $rating = isset($bookData['rating']) ? floatval($bookData['rating']) : 0; $ratingCount = isset($bookData['rating_count']) ? intval($bookData['rating_count']) : 0; // 自定义字段 - 修复:确保正确处理customData $startDate = isset($customData['startDate']) ? htmlspecialchars($customData['startDate']) : ''; $readDate = isset($customData['readDate']) ? htmlspecialchars($customData['readDate']) : ''; $readMethod = isset($customData['readMethod']) ? htmlspecialchars($customData['readMethod']) : ''; $bookCategory = isset($customData['bookCategory']) ? htmlspecialchars($customData['bookCategory']) : ''; $recommendation = isset($customData['recommendation']) ? intval($customData['recommendation']) : 0; // 如果customData中没有,尝试从bookData中获取 if (empty($startDate) && isset($bookData['custom_start_date']) && !empty($bookData['custom_start_date'])) { $startDate = htmlspecialchars($bookData['custom_start_date']); } if (empty($readDate) && isset($bookData['custom_read_date']) && !empty($bookData['custom_read_date'])) { $readDate = htmlspecialchars($bookData['custom_read_date']); } if (empty($readMethod) && isset($bookData['custom_read_method']) && !empty($bookData['custom_read_method'])) { $readMethod = htmlspecialchars($bookData['custom_read_method']); } if (empty($bookCategory) && isset($bookData['custom_book_category']) && !empty($bookData['custom_book_category'])) { $bookCategory = htmlspecialchars($bookData['custom_book_category']); } if ($recommendation == 0 && isset($bookData['custom_recommendation'])) { $recommendation = intval($bookData['custom_recommendation']); } // 检查简介是否超过当前设置的限制长度 $isSummaryLong = false; $summaryShort = $summary; // 移除HTML标签来计算纯文本长度 $plainSummary = strip_tags($summary); if (mb_strlen($plainSummary, 'UTF-8') > $summaryLength) { $isSummaryLong = true; // 截取纯文本 $plainShort = mb_substr($plainSummary, 0, $summaryLength, 'UTF-8'); // 尝试保持HTML结构,但这是一个简化的处理 $summaryShort = $plainShort . '...'; } $coverUrl = !empty($bookData['image']) ? $bookData['image'] : $defaultCover; $coverSrc = $imageProxy . urlencode($coverUrl); // 生成评分显示 $ratingHtml = ''; if ($rating > 0) { $ratingHtml = '
豆瓣评分: ' . number_format($rating, 1) . ''; if ($ratingCount > 0) { $ratingHtml .= ' (' . number_format($ratingCount) . '人评价)'; } $ratingHtml .= '
'; } // 生成推荐指数星星 $recommendationHtml = ''; if ($recommendation > 0) { $recommendationHtml = '
推荐指数:'; for ($i = 1; $i <= 5; $i++) { if ($i <= $recommendation) { $recommendationHtml .= ''; } else { $recommendationHtml .= ''; } } $recommendationHtml .= '
'; } // 生成自定义信息HTML $customInfoHtml = ''; $hasCustomInfo = false; // 生成右侧栏的自定义信息(无标题,样式与中间栏一致) if ($startDate || $readDate || $readMethod || $bookCategory) { $hasCustomInfo = true; if ($startDate) { $customInfoHtml .= '
开始阅读: ' . $startDate . '
'; } if ($readDate) { $customInfoHtml .= '
结束阅读: ' . $readDate . '
'; } if ($readMethod) { $customInfoHtml .= '
阅读方法: ' . $readMethod . '
'; } if ($bookCategory) { $customInfoHtml .= '
图书分类: ' . $bookCategory . '
'; } } // 生成右侧栏的完整HTML $rightColumnHtml = ''; if ($recommendationHtml) { $rightColumnHtml .= $recommendationHtml; } if ($customInfoHtml) { $rightColumnHtml .= $customInfoHtml; } else { // 如果没有自定义信息,显示占位符 $rightColumnHtml .= '
阅读记录: 暂无记录
'; } // 生成简介部分HTML,包含展开/收起功能 $summaryHtml = '
'; if ($isSummaryLong) { // 长简介:显示短版本 + 展开按钮 $summaryHtml .= '
' . nl2br($summaryShort) . ' ' . htmlspecialchars($expandText) . ' ↓
'; } else { // 短简介:直接显示完整内容 $summaryHtml .= '
' . $summary . '
'; } $summaryHtml .= '
'; // 生成短评HTML(如果有短评)- 只显示纯粹的短评 $reviewHtml = ''; if (!empty($review)) { $reviewHtml = '
💭 我的短评
' . nl2br($review) . '
'; } $html = <<
{$title}
📚
作者: {$author}
出版社: {$publisher}
出版年: {$pubdate}
页数: {$pages}
{$ratingHtml}
{$rightColumnHtml}
📖 内容简介
{$summaryHtml}
{$reviewHtml}
HTML; // 添加JavaScript切换函数 $html .= ' '; return $html; } /** * 获取所有图书数据(支持分页) */ private static function getAllBooksData($page = 1, $pageSize = 10) { $cacheDir = dirname(__FILE__) . '/cache/'; $allBooks = array(); // 扫描缓存目录 if (file_exists($cacheDir)) { $files = scandir($cacheDir); foreach ($files as $file) { if (pathinfo($file, PATHINFO_EXTENSION) === 'json') { $filePath = $cacheDir . $file; $content = file_get_contents($filePath); if ($content) { $data = json_decode($content, true); if ($data && isset($data['fetched_at'])) { // 添加文件名作为bookId $bookId = pathinfo($file, PATHINFO_FILENAME); $data['bookId'] = $bookId; // 使用文件修改时间作为添加时间(如果文件不存在,使用fetched_at) if (file_exists($filePath)) { $data['added_time'] = filemtime($filePath); } else { $data['added_time'] = isset($data['fetched_at']) ? $data['fetched_at'] : time(); } $allBooks[] = $data; } } } } } // 按added_time倒序排序(最新的在最前面) usort($allBooks, function($a, $b) { return $b['added_time'] - $a['added_time']; }); $total = count($allBooks); $totalPages = ceil($total / $pageSize); // 限制页码范围 $page = max(1, min($page, $totalPages)); // 分页处理 $startIndex = ($page - 1) * $pageSize; $paginatedBooks = array_slice($allBooks, $startIndex, $pageSize); return array( 'books' => $paginatedBooks, 'total' => $total, 'page' => $page, 'pageSize' => $pageSize, 'totalPages' => $totalPages, 'startIndex' => $startIndex + 1, 'endIndex' => min($startIndex + $pageSize, $total) ); } /** * 生成分页HTML */ private static function generatePagination($currentPage, $totalPages, $baseUrl = '') { if ($totalPages <= 1) { return ''; } $html = '
'; $html .= '
'; // 首页 if ($currentPage > 1) { $html .= '«'; } else { $html .= '«'; } // 上一页 if ($currentPage > 1) { $html .= ''; } else { $html .= ''; } // 页码显示 $startPage = max(1, $currentPage - 2); $endPage = min($totalPages, $currentPage + 2); for ($i = $startPage; $i <= $endPage; $i++) { if ($i == $currentPage) { $html .= '' . $i . ''; } else { $html .= '' . $i . ''; } } // 下一页 if ($currentPage < $totalPages) { $html .= ''; } else { $html .= ''; } // 末页 if ($currentPage < $totalPages) { $html .= '»'; } else { $html .= '»'; } $html .= '
'; // 删除了"共几页"的显示 $html .= '
'; return $html; } /** * 获取页面URL */ private static function getPageUrl($page, $baseUrl = '') { if (empty($baseUrl)) { // 获取当前页面URL $currentUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; // 移除现有的page参数 $currentUrl = preg_replace('/[&?]page=\d+/', '', $currentUrl); $currentUrl = rtrim($currentUrl, '?&'); // 添加分页参数 $separator = strpos($currentUrl, '?') === false ? '?' : '&'; return $currentUrl . $separator . 'page=' . $page; } return $baseUrl . (strpos($baseUrl, '?') === false ? '?' : '&') . 'page=' . $page; } /** * 渲染所有图书列表(支持深色模式和封面显示) */ public static function renderAllBooks($page = 1) { // 从GET参数获取页码(优先级高于参数) if (isset($_GET['page']) && is_numeric($_GET['page'])) { $page = intval($_GET['page']); } $options = Typecho_Widget::widget('Widget_Options')->plugin('BookInfo'); $pageSize = isset($options->pageSize) ? intval($options->pageSize) : 10; $imageProxy = isset($options->imageProxy) ? $options->imageProxy : 'https://images.weserv.nl/?url='; $defaultCover = isset($options->defaultCover) ? $options->defaultCover : 'https://img9.doubanio.com/f/shire/5522dd1f5b742d1e1394a17f44d590646b63871d/pics/book-default-lpic.gif'; // 获取分页数据 $paginationData = self::getAllBooksData($page, $pageSize); $allBooks = $paginationData['books']; $total = $paginationData['total']; $currentPage = $paginationData['page']; $totalPages = $paginationData['totalPages']; $startIndex = $paginationData['startIndex']; $endIndex = $paginationData['endIndex']; if (empty($allBooks)) { return '

暂无图书数据

请先在文章中使用[book:ID]短代码添加图书

'; } $html = '
'; // 标题模块 $html .= '
'; $html .= '

我的全部已读图书

'; $html .= '

已读' . $total . '本图书,本数据2025.12.08开始统计

'; $html .= '
'; // 页码信息 $html .= '
'; $html .= '
'; $html .= '
'; $html .= '显示:'; $html .= '' . $startIndex . '-' . $endIndex . ' / '; $html .= '' . $total . ''; $html .= '
'; $html .= '
'; $html .= '当前:'; $html .= '第 ' . $currentPage . ' 页 / '; $html .= '共 ' . $totalPages . ' 页'; $html .= '
'; $html .= '
'; $html .= '
'; // 计算倒序序号(最大的序号在最前面) $totalCount = $total; $currentIndex = $totalCount - (($currentPage - 1) * $pageSize); foreach ($allBooks as $book) { $bookId = $book['bookId']; $title = isset($book['title']) ? htmlspecialchars($book['title']) : '未知图书'; // 封面图片 $coverUrl = !empty($book['image']) ? $book['image'] : $defaultCover; $coverSrc = $imageProxy . urlencode($coverUrl); // 自定义字段 $startDate = isset($book['custom_start_date']) ? htmlspecialchars($book['custom_start_date']) : ''; $readDate = isset($book['custom_read_date']) ? htmlspecialchars($book['custom_read_date']) : ''; $readMethod = isset($book['custom_read_method']) ? htmlspecialchars($book['custom_read_method']) : ''; $bookCategory = isset($book['custom_book_category']) ? htmlspecialchars($book['custom_book_category']) : ''; // 作者处理 $author = '未知作者'; if (isset($book['author'])) { if (is_array($book['author'])) { $author = implode(', ', $book['author']); } else { $author = $book['author']; } } $author = htmlspecialchars($author); // 豆瓣信息 $publisher = isset($book['publisher']) ? htmlspecialchars($book['publisher']) : '未知'; $pubdate = isset($book['pubdate']) ? htmlspecialchars($book['pubdate']) : '未知'; $pages = isset($book['pages']) ? htmlspecialchars($book['pages']) : '未知'; // 豆瓣评分 $rating = isset($book['rating']) ? floatval($book['rating']) : 0; $ratingDisplay = $rating > 0 ? number_format($rating, 1) . '分' : '暂无评分'; // 短评 $review = isset($book['review']) ? htmlspecialchars($book['review']) : ''; // 构建日期范围显示 $dateRange = ''; if ($startDate && $readDate) { $dateRange = $startDate . '-' . $readDate; } elseif ($startDate) { $dateRange = $startDate . '-至今'; } elseif ($readDate) { $dateRange = '未知-' . $readDate; } // 构建图书分类显示 $categoryDisplay = $bookCategory ? $bookCategory : '未分类'; // 构建阅读方法显示 $methodDisplay = $readMethod ? $readMethod : '未知'; // 获取序号颜色 $indexColor = self::getIndexColor($currentIndex); $html .= '
'; $html .= '
'; // 左侧:封面图片带序号 $html .= '
'; $html .= '
'; $html .= ''; $html .= '
'; $html .= '' . $title . ''; $html .= '
'; // 序号显示在封面右上角 $html .= '
' . $currentIndex . '
'; $html .= '
'; $html .= '
'; $html .= '
'; // 右侧:图书信息 $html .= '
'; // 第一行:书名(带豆瓣链接)/分类/日期范围/阅读方法 $html .= '
'; $html .= '《' . $title . '》'; if ($categoryDisplay || $dateRange || $methodDisplay) { $html .= '/'; $html .= '' . $categoryDisplay . ''; if ($dateRange) { $html .= '/'; $html .= '' . $dateRange . ''; } if ($methodDisplay) { $html .= '/'; $html .= '' . $methodDisplay . ''; } } $html .= '
'; // 第二行:作者/出版社/出版年/页数/豆瓣评分 $html .= '
'; $html .= '作者:' . $author . ''; $html .= '|'; $html .= '出版社:' . $publisher . ''; $html .= '|'; $html .= '出版年:' . $pubdate . ''; $html .= '|'; $html .= '页数:' . $pages . '页'; $html .= '|'; // 豆瓣评分显示,如果是0分则显示"暂无评分",否则显示具体分数 if ($rating > 0) { $html .= '豆瓣评分:' . $ratingDisplay . ''; } else { $html .= '豆瓣评分:' . $ratingDisplay . ''; } $html .= '
'; // 第三行:短评 if ($review) { $html .= '
'; $html .= '
📝 短评:
'; $html .= '
'; $html .= nl2br($review); $html .= '
'; $html .= '
'; } $html .= '
'; $html .= '
'; $html .= '
'; $currentIndex--; } // 分页导航 if ($totalPages > 1) { $html .= self::generatePagination($currentPage, $totalPages); } $html .= '
'; return $html; } /** * 获取序号颜色 */ private static function getIndexColor($index) { $colors = [ 'linear-gradient(135deg, #0073aa, #0056b3)', // 蓝色 'linear-gradient(135deg, #28a745, #218838)', // 绿色 'linear-gradient(135deg, #e67e22, #d35400)', // 橙色 'linear-gradient(135deg, #9b59b6, #8e44ad)', // 紫色 'linear-gradient(135deg, #e74c3c, #c0392b)', // 红色 ]; return $colors[($index - 1) % count($colors)]; } /** * 获取图书数据(包含短评和自定义信息) */ private static function getBookData($bookId, $review = '', $customData = array()) { if (!is_numeric($bookId)) return null; $cacheFile = dirname(__FILE__) . '/cache/' . $bookId . '.json'; $options = Typecho_Widget::widget('Widget_Options')->plugin('BookInfo'); $cacheEnable = isset($options->cacheEnable) ? $options->cacheEnable : '1'; $cacheTime = isset($options->cacheTime) ? intval($options->cacheTime) : 7; $data = null; $needUpdate = false; // 检查缓存 if ($cacheEnable == '1' && file_exists($cacheFile)) { $fileTime = filemtime($cacheFile); $expireTime = $cacheTime * 24 * 3600; if (time() - $fileTime < $expireTime) { $cacheContent = file_get_contents($cacheFile); if ($cacheContent) { $data = json_decode($cacheContent, true); } } } // 如果缓存不存在或已过期,从豆瓣获取基本信息 if (!$data || empty($data['title'])) { $data = self::fetchFromDouban($bookId); $needUpdate = true; } // 重要修复:只在$review不为空且与当前数据不同时更新 if (!empty($review) && (!isset($data['review']) || $data['review'] !== $review)) { $data['review'] = $review; $data['review_updated'] = time(); $needUpdate = true; } // 更新自定义数据 - 使用传入的customData更新 if (!empty($customData)) { if (isset($customData['startDate']) && (!isset($data['custom_start_date']) || $data['custom_start_date'] !== $customData['startDate'])) { $data['custom_start_date'] = $customData['startDate']; $needUpdate = true; } if (isset($customData['readDate']) && (!isset($data['custom_read_date']) || $data['custom_read_date'] !== $customData['readDate'])) { $data['custom_read_date'] = $customData['readDate']; $needUpdate = true; } if (isset($customData['readMethod']) && (!isset($data['custom_read_method']) || $data['custom_read_method'] !== $customData['readMethod'])) { $data['custom_read_method'] = $customData['readMethod']; $needUpdate = true; } if (isset($customData['bookCategory']) && (!isset($data['custom_book_category']) || $data['custom_book_category'] !== $customData['bookCategory'])) { $data['custom_book_category'] = $customData['bookCategory']; $needUpdate = true; } if (isset($customData['recommendation']) && (!isset($data['custom_recommendation']) || $data['custom_recommendation'] != $customData['recommendation'])) { $data['custom_recommendation'] = intval($customData['recommendation']); $needUpdate = true; } } // 确保自定义字段存在 $customFields = array( 'custom_start_date' => '', 'custom_read_date' => '', 'custom_read_method' => '', 'custom_book_category' => '', 'custom_recommendation' => 0 ); foreach ($customFields as $field => $default) { if (!isset($data[$field])) { $data[$field] = $default; } } // 如果需要更新缓存,保存到文件 if ($needUpdate && $data && !empty($data['title'])) { file_put_contents($cacheFile, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); } return $data; } /** * 从豆瓣获取数据 - 修复:保留HTML标签的摘要抓取 */ private static function fetchFromDouban($bookId) { $url = "https://book.douban.com/subject/{$bookId}/"; $ch = curl_init(); curl_setopt_array($ch, array( CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 20, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', CURLOPT_ENCODING => 'gzip, deflate', CURLOPT_REFERER => 'https://book.douban.com/', CURLOPT_HTTPHEADER => array( 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8', 'Cache-Control: no-cache', 'Connection: keep-alive' ) )); $html = curl_exec($ch); if (curl_errno($ch)) { curl_close($ch); return null; } curl_close($ch); if (empty($html)) return null; $data = array(); // 1. 提取标题 if (preg_match('/]*>\s*]*>([^<]+)<\/span>/', $html, $matches)) { $data['title'] = trim(strip_tags(html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8'))); } // 2. 提取封面 if (preg_match('/]*src="([^"]+)"[^>]*id="mainpic"/', $html, $matches)) { $data['image'] = trim($matches[1]); } // 3. 提取描述(内容简介)- 修复:保留HTML标签 $summary = ''; // 豆瓣页面可能有多个intro,第一个通常是内容简介 if (preg_match_all('/]*>([\s\S]*?)<\/div>/', $html, $matches)) { // 尝试获取第一个intro(通常是内容简介) if (isset($matches[1][0])) { $intro = $matches[1][0]; // 检查是否有展开全部链接 if (preg_match('/]*class="[^"]*a_show_full[^"]*"[^>]*>.*?<\/a>/', $intro)) { // 如果有展开全部链接,说明这个intro是被截断的 // 我们需要查找完整的intro(可能在后面的div中) foreach ($matches[1] as $introIndex => $introContent) { // 查找不包含"a_show_full"的完整intro if (!preg_match('/]*class="[^"]*a_show_full[^"]*"[^>]*>.*?<\/a>/', $introContent)) { $intro = $introContent; break; } } } // 移除展开链接,但保留其他HTML标签(特别是

标签) $intro = preg_replace('/]*>.*?<\/a>/', '', $intro); // 清理多余的空白字符但保留HTML标签 $intro = preg_replace('/\s+/', ' ', $intro); $intro = trim($intro); // 解码HTML实体,但保留HTML标签 $intro = html_entity_decode($intro, ENT_QUOTES, 'UTF-8'); // 确保

标签正确闭合 $intro = preg_replace('/

\s*<\/p>/', '', $intro); $intro = preg_replace('/

\s+/', '

', $intro); $intro = preg_replace('/\s+<\/p>/', '

', $intro); $summary = $intro; // 如果获取到的内容很短,可能是作者简介,尝试第二个intro $plainText = strip_tags($intro); if (mb_strlen($plainText, 'UTF-8') < 50 && isset($matches[1][1])) { $intro = $matches[1][1]; $intro = preg_replace('/]*>.*?<\/a>/', '', $intro); $intro = preg_replace('/\s+/', ' ', $intro); $intro = trim($intro); $intro = html_entity_decode($intro, ENT_QUOTES, 'UTF-8'); $summary = $intro; } } } // 如果intro中没有,尝试从meta获取(但meta通常没有HTML标签) if (empty($summary) && preg_match('/暂无简介

'; // 4. 提取作者信息 $authors = array(); if (preg_match('/]*class="author"[^>]*>([^<]+)<\/a>/', $html, $matches)) { foreach ($matches[1] as $author) { $cleanAuthor = trim(html_entity_decode(strip_tags($author), ENT_QUOTES, 'UTF-8')); if ($cleanAuthor && !in_array($cleanAuthor, $authors)) { $authors[] = $cleanAuthor; } } } $data['author'] = !empty($authors) ? $authors : array('未知作者'); // 5. 提取图书详细信息(出版社、出版年、页数等) $infoHtml = ''; if (preg_match('/]*id="info"[^>]*>([\s\S]*?)<\/div>/', $html, $matches)) { $infoHtml = $matches[1]; // 处理换行和span标签 $infoHtml = preg_replace('//', "\n", $infoHtml); $infoHtml = preg_replace('/<\/span>/', "\n", $infoHtml); $infoHtml = strip_tags($infoHtml); // 按行处理 $lines = explode("\n", $infoHtml); foreach ($lines as $line) { $line = trim($line); if (empty($line)) continue; // 使用更灵活的正则匹配各种格式 if (preg_match('/^出版社[::]\s*(.+)$/u', $line, $match)) { $data['publisher'] = trim($match[1]); } elseif (preg_match('/^出版年[::]\s*(.+)$/u', $line, $match)) { $data['pubdate'] = trim($match[1]); } elseif (preg_match('/^页数[::]\s*(.+)$/u', $line, $match)) { $data['pages'] = trim($match[1]); } elseif (preg_match('/^ISBN[::]\s*(.+)$/u', $line, $match)) { $data['isbn'] = trim($match[1]); } elseif (preg_match('/^定价[::]\s*(.+)$/u', $line, $match)) { $data['price'] = trim($match[1]); } // 处理其他可能的格式 elseif (preg_match('/出版社[::]\s*(.+)/u', $line, $match)) { $data['publisher'] = trim($match[1]); } elseif (preg_match('/出版年[::]\s*(.+)/u', $line, $match)) { $data['pubdate'] = trim($match[1]); } } } // 如果上面没提取到,尝试直接在整个HTML中搜索 if (!isset($data['publisher'])) { $publisherPatterns = array( '/出版社[::]\s*<\/span>\s*]*>([^<]+)<\/a>/', '/出版社[::]\s*<\/span>\s*]*>([^<]+)<\/span>/', '/出版社[::]\s*([^<]+)/', '/]*>出版社[::]<\/span>\s*([^<]+)/', '/出版社[::]\s*([^\n<]+)/' ); foreach ($publisherPatterns as $pattern) { if (preg_match($pattern, $html, $matches)) { $data['publisher'] = trim(strip_tags($matches[1])); break; } } } if (!isset($data['pubdate'])) { $pubdatePatterns = array( '/出版年[::]\s*<\/span>\s*]*>([^<]+)<\/span>/', '/出版年[::]\s*([^<]+)/', '/]*>出版年[::]<\/span>\s*([^<]+)/', '/出版年[::]\s*([^\n<]+)/', '/出版日期[::]\s*([^<]+)/' ); foreach ($pubdatePatterns as $pattern) { if (preg_match($pattern, $html, $matches)) { $data['pubdate'] = trim(strip_tags($matches[1])); break; } } } if (!isset($data['pages'])) { $pagesPatterns = array( '/页数[::]\s*<\/span>\s*]*>([^<]+)<\/span>/', '/页数[::]\s*([^<]+)/', '/]*>页数[::]<\/span>\s*([^<]+)/', '/页数[::]\s*([^\n<]+)/' ); foreach ($pagesPatterns as $pattern) { if (preg_match($pattern, $html, $matches)) { $data['pages'] = trim(strip_tags($matches[1])); break; } } } // 设置默认值 if (!isset($data['publisher'])) $data['publisher'] = '未知'; if (!isset($data['pubdate'])) $data['pubdate'] = '未知'; if (!isset($data['pages'])) $data['pages'] = '未知'; if (!isset($data['isbn'])) $data['isbn'] = '未知'; // 6. 提取豆瓣评分 $rating = 0; $ratingCount = 0; // 从评分区域提取 if (preg_match('/]*class="ll rating_num"[^>]*>([^<]+)<\/strong>/', $html, $matches)) { $rating = floatval(trim($matches[1])); } elseif (preg_match('/]*class="[^"]*rating_num[^"]*"[^>]*>([^<]+)<\/strong>/', $html, $matches)) { $rating = floatval(trim($matches[1])); } // 从property属性提取 elseif (preg_match('/property="v:average"[^>]*>([^<]+)<\/strong>/', $html, $matches)) { $rating = floatval(trim($matches[1])); } // 从评分数字提取 elseif (preg_match('/]*>(\d+\.?\d*)<\/span>[^<]*]*>\((\d+)人评价\)/', $html, $matches)) { $rating = floatval($matches[1]); $ratingCount = intval($matches[2]); } $data['rating'] = $rating; // 7. 提取评价人数 if (preg_match('/]*property="v:votes"[^>]*>(\d+)<\/span>/', $html, $matches)) { $data['rating_count'] = intval($matches[1]); } elseif (preg_match('/\((\d+)人评价\)/', $html, $matches)) { $data['rating_count'] = intval($matches[1]); } else { $data['rating_count'] = 0; } // 如果评分人数为0但评分不为0,尝试其他方式 if ($rating > 0 && $data['rating_count'] == 0) { if (preg_match('/]*class="rating_people"[^>]*>(\d+)人评价<\/a>/', $html, $matches)) { $data['rating_count'] = intval($matches[1]); } } // 8. 初始化review和自定义字段 $data['review'] = ''; $data['review_updated'] = 0; // 初始化自定义字段 $data['custom_start_date'] = ''; $data['custom_read_date'] = ''; $data['custom_read_method'] = ''; $data['custom_book_category'] = ''; $data['custom_recommendation'] = 0; // 9. 添加抓取时间 $data['fetched_at'] = time(); return $data; } /** * 渲染编辑器按钮 - 修复:恢复原来的编辑器预览和插入格式 */ public static function renderButton() { echo << #bookinfo-button { padding: 5px!important; background:#fff; cursor: pointer; color: white; border: none; border-radius: 4px; font-size: 16px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; min-width: 26px; min-height: 26px; } #bookinfo-button:hover{background:#E9E9E6} .dark #bookinfo-button{background:rgb(16, 25, 40);} .dark #bookinfo-button:hover{background:#375d85;} /* 豆瓣插件深色模式适配 - 仅添加CSS */ .dark #bookinfo-id, .dark #bookinfo-review, .dark #bookinfo-start-date, .dark #bookinfo-read-date, .dark #bookinfo-read-method, .dark #bookinfo-book-category, .dark #bookinfo-recommendation { background: #101928 !important; border: 1px solid #374151 !important; color: #ffffff !important; } .dark #bookinfo-id::placeholder, .dark #bookinfo-review::placeholder, .dark #bookinfo-start-date::placeholder, .dark #bookinfo-read-date::placeholder, .dark #bookinfo-read-method::placeholder, .dark #bookinfo-book-category::placeholder { color: #9ca3af !important; } .dark #bookinfo-id:focus, .dark #bookinfo-review:focus, .dark #bookinfo-start-date:focus, .dark #bookinfo-read-date:focus, .dark #bookinfo-read-method:focus, .dark #bookinfo-book-category:focus, .dark #bookinfo-recommendation:focus { border-color: #0073aa !important; outline: none !important; } .dark label[for="bookinfo-id"], .dark label[for="bookinfo-review"], .dark label[for="bookinfo-start-date"], .dark label[for="bookinfo-read-date"], .dark label[for="bookinfo-read-method"], .dark label[for="bookinfo-book-category"], .dark label[for="bookinfo-recommendation"], .dark .bookinfo-modal-title { color: #ffffff !important; } .dark .bookinfo-modal-content { background: #1f2937 !important; border: 1px solid #374151 !important; } .dark .bookinfo-modal-header { background: #101928 !important; border-bottom: 1px solid #374151 !important; } .dark .bookinfo-modal-footer { background: #101928 !important; border-top: 1px solid #374151 !important; } .dark .bookinfo-help-text { color: #9ca3af !important; } .dark .bookinfo-preview-box { background: #101928 !important; border: 1px solid #374151 !important; } .dark .bookinfo-preview { color: #d1d5db !important; } .dark .bookinfo-option-group { background: #1f2937 !important; border: 1px solid #374151 !important; } .dark .bookinfo-cancel-btn { background: #374151 !important; color: #d1d5db !important; border: 1px solid #4b5563 !important; } .dark .bookinfo-cancel-btn:hover { background: #4b5563 !important; } .dark .bookinfo-insert-btn { background: #0073aa !important; color: #ffffff !important; border: 1px solid #0056b3 !important; } .dark .bookinfo-insert-btn:hover { background: #0056b3 !important; } /* 弹窗样式适配 */ .dark .bookinfo-modal-overlay { background: rgba(0, 0, 0, 0.7) !important; } /* 选择框选项 */ .dark #bookinfo-recommendation option { background: #1f2937 !important; color: #ffffff !important; } .dark #bookinfo-recommendation option:disabled { color: #9ca3af !important; } /* 信息提示框 */ .dark .bookinfo-info-box { background: #374151 !important; border: 1px solid #4b5563 !important; color: #d1d5db !important; } .dark .bookinfo-info-box strong { color: #ffffff !important; } HTML; } }