Files
BookInfo/Plugin.php
2026-02-23 17:13:52 +08:00

1861 lines
91 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 豆瓣图书
*
* @package BookInfo
* @author 石头厝
* @version 3.8.0
* @link http://www.shitoucuo.com
*/
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
class BookInfo_Plugin implements Typecho_Plugin_Interface
{
/**
* 激活插件
*/
public static function activate()
{
Typecho_Plugin::factory('Widget_Abstract_Contents')->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('&#91;', '&#93;'), 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 '<div class="book-error" style="padding:10px; background:var(--book-error-bg); color:var(--book-error-text); border:1px solid var(--book-error-border); border-radius:4px; margin:10px 0;">
获取图书信息失败ID' . htmlspecialchars($bookId) . '
</div>';
}
$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 = '<div class="book-rating" style="margin:0 0 10px 0; font-size:14px; color:var(--book-text-secondary);">
<span class="book-label" style="font-weight:600; color:var(--book-text-primary); display:inline-block; width:70px;">豆瓣评分:</span>
<span class="book-rating-value" style="color:var(--book-rating-color); font-weight:600; font-size:16px;">' . number_format($rating, 1) . '</span>';
if ($ratingCount > 0) {
$ratingHtml .= ' <span class="book-rating-count" style="color:var(--book-text-tertiary); font-size:13px;">(' . number_format($ratingCount) . '人评价)</span>';
}
$ratingHtml .= '</div>';
}
// 生成推荐指数星星
$recommendationHtml = '';
if ($recommendation > 0) {
$recommendationHtml = '<div class="book-recommendation" style="margin:0 0 10px 0; font-size:14px; color:var(--book-text-secondary);">
<span class="book-label" style="font-weight:600; color:var(--book-text-primary); display:inline-block; width:70px;">推荐指数:</span>';
for ($i = 1; $i <= 5; $i++) {
if ($i <= $recommendation) {
$recommendationHtml .= '<span style="color:var(--book-star-color); font-size:16px; margin-right:2px;">★</span>';
} else {
$recommendationHtml .= '<span style="color:var(--book-star-empty-color); font-size:16px; margin-right:2px;">★</span>';
}
}
$recommendationHtml .= '</div>';
}
// 生成自定义信息HTML
$customInfoHtml = '';
$hasCustomInfo = false;
// 生成右侧栏的自定义信息(无标题,样式与中间栏一致)
if ($startDate || $readDate || $readMethod || $bookCategory) {
$hasCustomInfo = true;
if ($startDate) {
$customInfoHtml .= '<div class="book-custom-item" style="margin:0 0 10px 0; font-size:14px; color:var(--book-text-secondary);">
<span class="book-label" style="font-weight:600; color:var(--book-text-primary); display:inline-block; width:70px;">开始阅读:</span>
<span class="book-custom-value" style="color:var(--book-text-primary);">' . $startDate . '</span>
</div>';
}
if ($readDate) {
$customInfoHtml .= '<div class="book-custom-item" style="margin:0 0 10px 0; font-size:14px; color:var(--book-text-secondary);">
<span class="book-label" style="font-weight:600; color:var(--book-text-primary); display:inline-block; width:70px;">结束阅读:</span>
<span class="book-custom-value" style="color:var(--book-text-primary);">' . $readDate . '</span>
</div>';
}
if ($readMethod) {
$customInfoHtml .= '<div class="book-custom-item" style="margin:0 0 10px 0; font-size:14px; color:var(--book-text-secondary);">
<span class="book-label" style="font-weight:600; color:var(--book-text-primary); display:inline-block; width:70px;">阅读方法:</span>
<span class="book-custom-value" style="color:var(--book-text-primary);">' . $readMethod . '</span>
</div>';
}
if ($bookCategory) {
$customInfoHtml .= '<div class="book-custom-item" style="margin:0 0 10px 0; font-size:14px; color:var(--book-text-secondary);">
<span class="book-label" style="font-weight:600; color:var(--book-text-primary); display:inline-block; width:70px;">图书分类:</span>
<span class="book-custom-value" style="color:var(--book-text-primary);">' . $bookCategory . '</span>
</div>';
}
}
// 生成右侧栏的完整HTML
$rightColumnHtml = '';
if ($recommendationHtml) {
$rightColumnHtml .= $recommendationHtml;
}
if ($customInfoHtml) {
$rightColumnHtml .= $customInfoHtml;
} else {
// 如果没有自定义信息,显示占位符
$rightColumnHtml .= '<div class="book-custom-item" style="margin:0 0 10px 0; font-size:14px; color:var(--book-text-secondary);">
<span class="book-label" style="font-weight:600; color:var(--book-text-primary); display:inline-block; width:70px;">阅读记录:</span>
<span class="book-custom-value" style="color:var(--book-text-tertiary);">暂无记录</span>
</div>';
}
// 生成简介部分HTML包含展开/收起功能
$summaryHtml = '<div class="book-summary-container">';
if ($isSummaryLong) {
// 长简介:显示短版本 + 展开按钮
$summaryHtml .= '
<div class="book-summary-short" style="display:block;">
<div class="book-summary-content" style="font-size:14px; line-height:1.8; color:var(--book-text-primary); text-align:justify; font-family:\"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif;">
' . nl2br($summaryShort) . '
<a href="javascript:void(0);" class="book-summary-expand"
onclick="toggleBookSummary(this)"
style="color:' . htmlspecialchars($expandColor) . '; text-decoration:none; font-weight:600; cursor:pointer; margin-left:5px; padding:2px 8px; background:var(--book-button-bg); border-radius:4px; border:1px solid var(--book-border); font-size:13px; transition:all 0.2s ease;"
onmouseover="this.style.backgroundColor=\'var(--book-button-hover-bg)\'; this.style.borderColor=\'' . htmlspecialchars($expandColor) . '\';"
onmouseout="this.style.backgroundColor=\'var(--book-button-bg)\'; this.style.borderColor=\'var(--book-border)\';">'
. htmlspecialchars($expandText) . ' ↓
</a>
</div>
</div>
<div class="book-summary-full" style="display:none;">
<div class="book-summary-content" style="font-size:14px; line-height:1.8; color:var(--book-text-primary); text-align:justify; font-family:\"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif;">
' . $summary . '
<a href="javascript:void(0);" class="book-summary-collapse"
onclick="toggleBookSummary(this)"
style="color:' . htmlspecialchars($expandColor) . '; text-decoration:none; font-weight:600; cursor:pointer; margin-left:5px; padding:2px 8px; background:var(--book-button-bg); border-radius:4px; border:1px solid var(--book-border); font-size:13px; transition:all 0.2s ease;"
onmouseover="this.style.backgroundColor=\'var(--book-button-hover-bg)\'; this.style.borderColor=\'' . htmlspecialchars($expandColor) . '\';"
onmouseout="this.style.backgroundColor=\'var(--book-button-bg)\'; this.style.borderColor=\'var(--book-border)\';">'
. htmlspecialchars($collapseText) . ' ↑
</a>
</div>
</div>';
} else {
// 短简介:直接显示完整内容
$summaryHtml .= '
<div class="book-summary-content" style="font-size:14px; line-height:1.8; color:var(--book-text-primary); text-align:justify; font-family:\"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif;">
' . $summary . '
</div>';
}
$summaryHtml .= '</div>';
// 生成短评HTML如果有短评- 只显示纯粹的短评
$reviewHtml = '';
if (!empty($review)) {
$reviewHtml = '<div class="book-review-section" style="margin-top:15px;">
<div class="book-section-title" style="margin:0 0 8px 0; font-size:14px; color:var(--book-text-secondary); font-weight:600; padding-bottom:8px; border-bottom:1px solid var(--book-border);">💭 我的短评</div>
<div class="book-review-content" style="font-size:14px; line-height:1.8; color:var(--book-text-primary); text-align:justify; background:var(--book-review-bg); padding:15px; border-radius:8px; border-left:3px solid ' . htmlspecialchars($expandColor) . '; font-family:\"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif;">
' . nl2br($review) . '
</div>
</div>';
}
$html = <<<HTML
<!-- 深色模式CSS变量 -->
<style>
.dark .markdown-body h2{margin-bottom:0px!important;padding-bottom:0px!important;}
:root {
/* 浅色主题变量 */
--book-bg-primary: #ffffff;
--book-bg-secondary: #f9fafb;
--book-bg-tertiary: #fafafa;
--book-bg-header: linear-gradient(135deg, #f9fafb 0%, #f1f3f5 100%);
--book-bg-review: #f9f9f9;
--book-text-primary: #1a1a1a;
--book-text-secondary: #555;
--book-text-tertiary: #666;
--book-border: #eaeaea;
--book-border-light: #f0f0f0;
--book-rating-color: #ffac2d;
--book-star-color: #ff6b35;
--book-star-empty-color: #ddd;
--book-button-bg: #fff;
--book-button-hover-bg: #e9ecef;
--book-error-bg: #f8d7da;
--book-error-text: #721c24;
--book-error-border: #f5c6cb;
--book-shadow: 0 4px 12px rgba(0,0,0,0.08);
--book-shadow-light: 0 2px 8px rgba(0,0,0,0.05);
--book-shadow-hover: 0 6px 16px rgba(0,0,0,0.12);
--book-index-bg: linear-gradient(135deg, #0073aa, #0056b3);
}
@media (prefers-color-scheme: dark) {
:root {
/* 深色主题变量 */
--book-bg-primary: #1e1e1e;
--book-bg-secondary: #2d2d2d;
--book-bg-tertiary: #252525;
--book-bg-header: linear-gradient(135deg, #2d2d2d 0%, #252525 100%);
--book-bg-review: #2a2a2a;
--book-text-primary: #e0e0e0;
--book-text-secondary: #b0b0b0;
--book-text-tertiary: #888;
--book-border: #444;
--book-border-light: #333;
--book-rating-color: #ffac2d;
--book-star-color: #ff8c42;
--book-star-empty-color: #555;
--book-button-bg: #3a3a3a;
--book-button-hover-bg: #4a4a4a;
--book-error-bg: #5c2a2a;
--book-error-text: #ffb8b8;
--book-error-border: #7a3a3a;
--book-shadow: 0 4px 12px rgba(0,0,0,0.3);
--book-shadow-light: 0 2px 8px rgba(0,0,0,0.2);
--book-shadow-hover: 0 6px 16px rgba(0,0,0,0.4);
--book-index-bg: linear-gradient(135deg, #0073aa, #0056b3);
}
/* 保持链接颜色在深色模式下的可读性 */
.book-card a {
color: #66b3ff;
}
.book-card a:hover {
color: #99ccff;
}
/* 深色模式下的卡片样式 */
.book-card {
background: var(--book-bg-primary);
color: var(--book-text-primary);
}
}
</style>
<div class="book-card-h">
<div class="book-card" style="background:var(--book-bg-primary); margin-top:10px;border-radius:12px; box-shadow:var(--book-shadow); overflow:hidden; border:1px solid var(--book-border);">
<!-- 上栏:标题 -->
<div class="book-header" style="padding:20px 25px; background:var(--book-bg-header); border-bottom:1px solid var(--book-border-light);">
<h2 style="margin:0; font-size:22px; line-height:1.3; color:var(--book-text-primary); font-weight:700; text-align:center;">
<a href="https://book.douban.com/subject/{$bookId}/" target="_blank"
style="color:#0073aa; text-decoration:none; transition:color 0.2s ease;"
onmouseover="this.style.color='#0056b3';"
onmouseout="this.style.color='#0073aa';">
《{$title}》
</a>
</h2>
</div>
<!-- 中栏:三列布局 -->
<div class="book-main" style="padding:25px; border-bottom:1px solid var(--book-border-light); background:var(--book-bg-primary);">
<div style="display:grid; grid-template-columns:120px 1fr 1fr; gap:25px; align-items:stretch;">
<!-- 左侧:图书封面 - 固定高度容器 -->
<div style="display:flex; align-items:center; justify-content:center;">
<a href="https://book.douban.com/subject/{$bookId}/" target="_blank" style="display:block; text-decoration:none; width:100%;">
<div style="position:relative; width:100%;">
<div style="width:100%; height:0; padding-bottom:140%; position:relative; overflow:hidden; border-radius:8px; border:3px solid var(--book-bg-primary); box-shadow:0 4px 12px rgba(0,0,0,0.12); background:var(--book-bg-tertiary);">
<img src="{$coverSrc}" alt="{$title}"
style="position:absolute; top:0; left:0; width:100%; height:100%; object-fit:cover; transition:transform 0.3s ease;"
onerror="this.src='{$defaultCover}';"
onmouseover="this.style.transform='scale(1.05)';"
onmouseout="this.style.transform='scale(1)';">
</div>
<div style="position:absolute; top:-5px; right:-5px; background:#0073aa; color:white; width:24px; height:24px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:12px; font-weight:bold; box-shadow:0 2px 4px rgba(0,115,170,0.3);">📚</div>
</div>
</a>
</div>
<!-- 中间:豆瓣图书信息 -->
<div style="padding-right:15px; border-right:1px solid var(--book-border);">
<div style="margin:0 0 10px 0; font-size:14px; color:var(--book-text-secondary);">
<span style="font-weight:600; color:var(--book-text-primary); display:inline-block; width:70px;">作者:</span>
<span style="color:#0073aa;">{$author}</span>
</div>
<div style="margin:0 0 10px 0; font-size:14px; color:var(--book-text-secondary);">
<span style="font-weight:600; color:var(--book-text-primary); display:inline-block; width:70px;">出版社:</span>
<span style="color:var(--book-text-primary);">{$publisher}</span>
</div>
<div style="margin:0 0 10px 0; font-size:14px; color:var(--book-text-secondary);">
<span style="font-weight:600; color:var(--book-text-primary); display:inline-block; width:70px;">出版年:</span>
<span style="color:var(--book-text-primary);">{$pubdate}</span>
</div>
<div style="margin:0 0 10px 0; font-size:14px; color:var(--book-text-secondary);">
<span style="font-weight:600; color:var(--book-text-primary); display:inline-block; width:70px;">页数:</span>
<span style="color:var(--book-text-primary);">{$pages}</span>
</div>
{$ratingHtml}
</div>
<!-- 右侧:自定义信息 -->
<div style="padding-left:15px;">
{$rightColumnHtml}
</div>
</div>
</div>
<!-- 下栏:内容简介和短评 -->
<div style="padding:20px 25px 25px 25px;">
<!-- 内容简介 -->
<div style="margin-bottom:20px;">
<div style="margin:0 0 12px 0; font-size:16px; color:var(--book-text-primary); font-weight:600; display:flex; align-items:center; gap:8px; padding-bottom:8px; border-bottom:1px solid var(--book-border);">
<span style="background:#28a745; color:white; width:28px; height:28px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:14px;">📖</span>
<span>内容简介</span>
</div>
<div style="background:var(--book-bg-tertiary); padding:20px; border-radius:8px; border:1px solid var(--book-border); box-shadow:0 1px 3px rgba(0,0,0,0.05);">
{$summaryHtml}
</div>
</div>
<!-- 短评 -->
{$reviewHtml}
</div>
</div>
</div>
HTML;
// 添加JavaScript切换函数
$html .= '
<script>
function toggleBookSummary(element) {
var container = element.closest(".book-summary-container");
var shortDiv = container.querySelector(".book-summary-short");
var fullDiv = container.querySelector(".book-summary-full");
if (shortDiv && fullDiv) {
if (shortDiv.style.display === "none") {
// 收起
shortDiv.style.display = "block";
fullDiv.style.display = "none";
} else {
// 展开
shortDiv.style.display = "none";
fullDiv.style.display = "block";
}
}
}
</script>';
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 = '<div class="book-pagination" style="margin:30px 0; text-align:center;">';
$html .= '<div style="display:inline-flex; align-items:center; gap:8px; background:var(--book-bg-primary); padding:12px 20px; border-radius:8px; box-shadow:var(--book-shadow-light); border:1px solid var(--book-border);">';
// 首页
if ($currentPage > 1) {
$html .= '<a href="' . self::getPageUrl(1, $baseUrl) . '" style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--book-button-bg); color:#0073aa; text-decoration:none; font-size:14px; font-weight:600; transition:all 0.2s ease;"
onmouseover="this.style.background=\'#0073aa\'; this.style.color=\'white\';"
onmouseout="this.style.background=\'var(--book-button-bg)\'; this.style.color=\'#0073aa\';">«</a>';
} else {
$html .= '<span style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--book-button-bg); color:var(--book-text-tertiary); font-size:14px; font-weight:600;">«</span>';
}
// 上一页
if ($currentPage > 1) {
$html .= '<a href="' . self::getPageUrl($currentPage - 1, $baseUrl) . '" style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--book-button-bg); color:#0073aa; text-decoration:none; font-size:14px; font-weight:600; transition:all 0.2s ease;"
onmouseover="this.style.background=\'#0073aa\'; this.style.color=\'white\';"
onmouseout="this.style.background=\'var(--book-button-bg)\'; this.style.color=\'#0073aa\';"></a>';
} else {
$html .= '<span style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--book-button-bg); color:var(--book-text-tertiary); font-size:14px; font-weight:600;"></span>';
}
// 页码显示
$startPage = max(1, $currentPage - 2);
$endPage = min($totalPages, $currentPage + 2);
for ($i = $startPage; $i <= $endPage; $i++) {
if ($i == $currentPage) {
$html .= '<span style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:#0073aa; color:white; font-size:14px; font-weight:600;">' . $i . '</span>';
} else {
$html .= '<a href="' . self::getPageUrl($i, $baseUrl) . '" style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--book-button-bg); color:#0073aa; text-decoration:none; font-size:14px; font-weight:600; transition:all 0.2s ease;"
onmouseover="this.style.background=\'#e6f3ff\'; this.style.color=\'#0073aa\';"
onmouseout="this.style.background=\'var(--book-button-bg)\'; this.style.color=\'#0073aa\';">' . $i . '</a>';
}
}
// 下一页
if ($currentPage < $totalPages) {
$html .= '<a href="' . self::getPageUrl($currentPage + 1, $baseUrl) . '" style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--book-button-bg); color:#0073aa; text-decoration:none; font-size:14px; font-weight:600; transition:all 0.2s ease;"
onmouseover="this.style.background=\'#0073aa\'; this.style.color=\'white\';"
onmouseout="this.style.background=\'var(--book-button-bg)\'; this.style.color=\'#0073aa\';"></a>';
} else {
$html .= '<span style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--book-button-bg); color:var(--book-text-tertiary); font-size:14px; font-weight:600;"></span>';
}
// 末页
if ($currentPage < $totalPages) {
$html .= '<a href="' . self::getPageUrl($totalPages, $baseUrl) . '" style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--book-button-bg); color:#0073aa; text-decoration:none; font-size:14px; font-weight:600; transition:all 0.2s ease;"
onmouseover="this.style.background=\'#0073aa\'; this.style.color=\'white\';"
onmouseout="this.style.background=\'var(--book-button-bg)\'; this.style.color=\'#0073aa\';">»</a>';
} else {
$html .= '<span style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--book-button-bg); color:var(--book-text-tertiary); font-size:14px; font-weight:600;">»</span>';
}
$html .= '</div>';
// 删除了"共几页"的显示
$html .= '</div>';
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 '<div style="padding:20px; text-align:center; color:var(--book-text-tertiary); background:var(--book-bg-tertiary); border-radius:8px; border:1px solid var(--book-border);">
<p>暂无图书数据</p>
<p style="font-size:13px; color:var(--book-text-tertiary);">请先在文章中使用[book:ID]短代码添加图书</p>
</div>';
}
$html = '<div class="all-books-list" style="margin:20px 0;">';
// 标题模块
$html .= '<div style="margin-bottom:20px; padding-bottom:10px; border-bottom:2px solid #0073aa;text-align:center;">';
$html .= '<h2 style="margin:0; font-size:24px; color:var(--book-text-primary);">我的全部已读图书</h2>';
$html .= '<p style="margin:5px 0 0 0;font-size:14px;color:var(--book-text-secondary);">已读' . $total . '本图书本数据2025.12.08开始统计</p>';
$html .= '</div>';
// 页码信息
$html .= '<div style="margin-bottom:20px; padding:15px; background:var(--book-bg-primary); border-radius:8px; border:1px solid var(--book-border); box-shadow:var(--book-shadow-light);">';
$html .= '<div style="display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:10px;">';
$html .= '<div style="font-size:14px; color:var(--book-text-secondary);">';
$html .= '<span style="font-weight:600;">显示:</span>';
$html .= '<span style="color:#0073aa; font-weight:600;">' . $startIndex . '-' . $endIndex . '</span> / ';
$html .= '<span style="color:var(--book-text-primary); font-weight:600;">' . $total . '</span>';
$html .= '</div>';
$html .= '<div style="font-size:14px; color:var(--book-text-secondary);">';
$html .= '<span style="font-weight:600;">当前:</span>';
$html .= '<span style="color:#e67e22; font-weight:600;">第 ' . $currentPage . ' 页</span> / ';
$html .= '<span style="color:var(--book-text-primary); font-weight:600;">共 ' . $totalPages . ' 页</span>';
$html .= '</div>';
$html .= '</div>';
$html .= '</div>';
// 计算倒序序号(最大的序号在最前面)
$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 .= '<div class="book-list-item" style="margin-bottom:25px; padding:20px; background:var(--book-bg-primary); border-radius:8px; border:1px solid var(--book-border); box-shadow:var(--book-shadow-light); transition:all 0.3s ease;"
onmouseover="this.style.boxShadow=\'var(--book-shadow-hover)\'; this.style.transform=\'translateY(-2px)\';"
onmouseout="this.style.boxShadow=\'var(--book-shadow-light)\'; this.style.transform=\'translateY(0)\';">';
$html .= '<div style="display:flex; align-items:flex-start; gap:20px;">';
// 左侧:封面图片带序号
$html .= '<div style="flex-shrink:0; width:90px; position:relative;">';
$html .= '<div style="position:relative;">';
$html .= '<a href="https://book.douban.com/subject/' . $bookId . '/" target="_blank" style="display:block; text-decoration:none;">';
$html .= '<div style="width:100%; height:0; padding-bottom:140%; position:relative; overflow:hidden; border-radius:8px; border:3px solid var(--book-bg-primary); box-shadow:0 4px 12px rgba(0,0,0,0.12); background:var(--book-bg-tertiary);">';
$html .= '<img src="' . $coverSrc . '" alt="' . $title . '"
style="position:absolute; top:0; left:0; width:100%; height:100%; object-fit:cover; transition:transform 0.3s ease;"
onerror="this.src=\'' . $defaultCover . '\';"
onmouseover="this.style.transform=\'scale(1.05)\';"
onmouseout="this.style.transform=\'scale(1)\';">';
$html .= '</div>';
// 序号显示在封面右上角
$html .= '<div style="position:absolute; top:-8px; right:-8px; width:28px; height:28px; background:' . $indexColor . '; color:white; border-radius:50%; display:flex; align-items:center; justify-content:center; font-weight:bold; font-size:14px; box-shadow:0 2px 4px rgba(0,0,0,0.3); z-index:10;">' . $currentIndex . '</div>';
$html .= '</a>';
$html .= '</div>';
$html .= '</div>';
// 右侧:图书信息
$html .= '<div style="flex-grow:1;">';
// 第一行:书名(带豆瓣链接)/分类/日期范围/阅读方法
$html .= '<div style="margin-bottom:10px; font-size:16px; line-height:1.5;">';
$html .= '<a href="https://book.douban.com/subject/' . $bookId . '/" target="_blank"
style="color:#0073aa; text-decoration:none; font-weight:bold; font-size:18px;"
onmouseover="this.style.textDecoration=\'underline\';"
onmouseout="this.style.textDecoration=\'none\';">《' . $title . '》</a>';
if ($categoryDisplay || $dateRange || $methodDisplay) {
$html .= '<span style="color:var(--book-text-tertiary); font-size:14px; margin-left:10px;">/</span>';
$html .= '<span style="color:#0073aa; font-size:14px; margin-left:5px;">' . $categoryDisplay . '</span>';
if ($dateRange) {
$html .= '<span style="color:var(--book-text-tertiary); font-size:14px; margin-left:10px;">/</span>';
$html .= '<span style="color:#e67e22; font-size:14px; margin-left:5px;">' . $dateRange . '</span>';
}
if ($methodDisplay) {
$html .= '<span style="color:var(--book-text-tertiary); font-size:14px; margin-left:10px;">/</span>';
$html .= '<span style="color:#27ae60; font-size:14px; margin-left:5px;">' . $methodDisplay . '</span>';
}
}
$html .= '</div>';
// 第二行:作者/出版社/出版年/页数/豆瓣评分
$html .= '<div style="margin-bottom:12px; font-size:14px; color:var(--book-text-secondary); line-height:1.6;">';
$html .= '<span style="color:var(--book-text-primary);"><strong>作者:</strong>' . $author . '</span>';
$html .= '<span style="color:var(--book-text-tertiary); margin:0 10px;">|</span>';
$html .= '<span style="color:var(--book-text-primary);"><strong>出版社:</strong>' . $publisher . '</span>';
$html .= '<span style="color:var(--book-text-tertiary); margin:0 10px;">|</span>';
$html .= '<span style="color:var(--book-text-primary);"><strong>出版年:</strong>' . $pubdate . '</span>';
$html .= '<span style="color:var(--book-text-tertiary); margin:0 10px;">|</span>';
$html .= '<span style="color:var(--book-text-primary);"><strong>页数:</strong>' . $pages . '页</span>';
$html .= '<span style="color:var(--book-text-tertiary); margin:0 10px;">|</span>';
// 豆瓣评分显示如果是0分则显示"暂无评分",否则显示具体分数
if ($rating > 0) {
$html .= '<span style="color:var(--book-rating-color); font-weight:bold;"><strong>豆瓣评分:</strong>' . $ratingDisplay . '</span>';
} else {
$html .= '<span style="color:var(--book-text-tertiary);"><strong>豆瓣评分:</strong>' . $ratingDisplay . '</span>';
}
$html .= '</div>';
// 第三行:短评
if ($review) {
$html .= '<div style="margin-top:12px; padding-top:12px; border-top:1px dashed var(--book-border);">';
$html .= '<div style="font-size:13px; color:var(--book-text-secondary); font-weight:600; margin-bottom:5px;">📝 短评:</div>';
$html .= '<div style="font-size:14px; line-height:1.6; color:var(--book-text-primary); background:var(--book-review-bg); padding:12px; border-radius:6px; border-left:3px solid #3498db;">';
$html .= nl2br($review);
$html .= '</div>';
$html .= '</div>';
}
$html .= '</div>';
$html .= '</div>';
$html .= '</div>';
$currentIndex--;
}
// 分页导航
if ($totalPages > 1) {
$html .= self::generatePagination($currentPage, $totalPages);
}
$html .= '</div>';
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('/<meta\s+property="og:title"\s+content="([^"]+)"/i', $html, $matches)) {
$data['title'] = trim(strip_tags(html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8')));
} elseif (preg_match('/<h1[^>]*>\s*<span[^>]*>([^<]+)<\/span>/', $html, $matches)) {
$data['title'] = trim(strip_tags(html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8')));
}
// 2. 提取封面
if (preg_match('/<meta\s+property="og:image"\s+content="([^"]+)"/i', $html, $matches)) {
$data['image'] = trim($matches[1]);
} elseif (preg_match('/<img[^>]*src="([^"]+)"[^>]*id="mainpic"/', $html, $matches)) {
$data['image'] = trim($matches[1]);
}
// 3. 提取描述(内容简介)- 修复保留HTML标签
$summary = '';
// 豆瓣页面可能有多个intro第一个通常是内容简介
if (preg_match_all('/<div\s+class="intro"[^>]*>([\s\S]*?)<\/div>/', $html, $matches)) {
// 尝试获取第一个intro通常是内容简介
if (isset($matches[1][0])) {
$intro = $matches[1][0];
// 检查是否有展开全部链接
if (preg_match('/<a[^>]*class="[^"]*a_show_full[^"]*"[^>]*>.*?<\/a>/', $intro)) {
// 如果有展开全部链接说明这个intro是被截断的
// 我们需要查找完整的intro可能在后面的div中
foreach ($matches[1] as $introIndex => $introContent) {
// 查找不包含"a_show_full"的完整intro
if (!preg_match('/<a[^>]*class="[^"]*a_show_full[^"]*"[^>]*>.*?<\/a>/', $introContent)) {
$intro = $introContent;
break;
}
}
}
// 移除展开链接但保留其他HTML标签特别是<p>标签)
$intro = preg_replace('/<a[^>]*>.*?<\/a>/', '', $intro);
// 清理多余的空白字符但保留HTML标签
$intro = preg_replace('/\s+/', ' ', $intro);
$intro = trim($intro);
// 解码HTML实体但保留HTML标签
$intro = html_entity_decode($intro, ENT_QUOTES, 'UTF-8');
// 确保<p>标签正确闭合
$intro = preg_replace('/<p>\s*<\/p>/', '', $intro);
$intro = preg_replace('/<p>\s+/', '<p>', $intro);
$intro = preg_replace('/\s+<\/p>/', '</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[^>]*>.*?<\/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('/<meta\s+property="og:description"\s+content="([^"]+)"/i', $html, $matches)) {
$summary = trim(html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8'));
}
$data['summary'] = $summary ?: '<p>暂无简介</p>';
// 4. 提取作者信息
$authors = array();
if (preg_match('/<meta\s+property="book:author"\s+content="([^"]+)"/i', $html, $matches)) {
$authors[] = trim(html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8'));
}
// 从作者链接提取
if (preg_match_all('/<a[^>]*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('/<div[^>]*id="info"[^>]*>([\s\S]*?)<\/div>/', $html, $matches)) {
$infoHtml = $matches[1];
// 处理换行和span标签
$infoHtml = preg_replace('/<br\s*\/?>/', "\n", $infoHtml);
$infoHtml = preg_replace('/<\/span>/', "</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[^>]*>([^<]+)<\/a>/',
'/出版社[:]\s*<\/span>\s*<span[^>]*>([^<]+)<\/span>/',
'/出版社[:]\s*([^<]+)<br\s*\/?>/',
'/<span[^>]*>出版社[:]<\/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[^>]*>([^<]+)<\/span>/',
'/出版年[:]\s*([^<]+)<br\s*\/?>/',
'/<span[^>]*>出版年[:]<\/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[^>]*>([^<]+)<\/span>/',
'/页数[:]\s*([^<]+)<br\s*\/?>/',
'/<span[^>]*>页数[:]<\/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('/<strong[^>]*class="ll rating_num"[^>]*>([^<]+)<\/strong>/', $html, $matches)) {
$rating = floatval(trim($matches[1]));
}
elseif (preg_match('/<strong[^>]*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('/<span[^>]*>(\d+\.?\d*)<\/span>[^<]*<span[^>]*>\((\d+)人评价\)/', $html, $matches)) {
$rating = floatval($matches[1]);
$ratingCount = intval($matches[2]);
}
$data['rating'] = $rating;
// 7. 提取评价人数
if (preg_match('/<span[^>]*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('/<a[^>]*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 <<<HTML
<style>
#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;
}
</style>
<script>
(function() {
function initBookButton() {
var toolbar = null;
var selectors = ['.wmd-button-row', '.typecho-post-option', '.submit', '#custom-field'];
for (var i = 0; i < selectors.length; i++) {
var el = document.querySelector(selectors[i]);
if (el) { toolbar = el; break; }
}
if (!toolbar) {
setTimeout(initBookButton, 500);
return;
}
if (document.getElementById('bookinfo-button')) return;
var button = document.createElement('button');
button.type = 'button';
button.id = 'bookinfo-button';
button.className = 'btn btn-s';
button.innerHTML = '📚';
button.style.cssText = 'padding:6px 12px;';
toolbar.appendChild(button);
button.addEventListener('click', function() {
showBookDialog();
});
}
function showBookDialog() {
// 创建遮罩层
var overlay = document.createElement('div');
overlay.style.cssText = 'position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.5); z-index:9999;';
// 创建对话框
var modal = document.createElement('div');
modal.style.cssText = 'position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); background:white; border:1px solid #ddd; border-radius:8px; box-shadow:0 4px 20px rgba(0,0,0,0.15); z-index:10000; width:500px; max-height:90vh; overflow-y:auto; padding:20px;';
modal.innerHTML =
'<!--<h3 style="margin-top:0; color:#000;margin-bottom:15px;">插入豆瓣图书</h3>-->' +
'<div style="margin-bottom:10px;margin-top:-5px;">' +
'<label style="display:block; margin-bottom:5px;color:#000; font-weight:600;">📚 豆瓣图书ID</label>' +
'<input type="text" id="bookinfo-id" placeholder="例如1007305" style="width:100%; padding:8px; border:1px solid #ddd; border-radius:4px; font-size:14px; box-sizing:border-box;">' +
'<!--<div style="font-size:12px; color:#666; margin-top:5px;">在豆瓣图书页面URL中找到的纯数字ID</div>-->' +
'</div>' +
'<div style="margin-bottom:15px;">' +
'<label style="display:block;color:#000; margin-bottom:5px; font-weight:600;">💭 我的短评/读后感</label>' +
'<textarea id="bookinfo-review" placeholder="输入读后感或简短评论..." style="width:100%; padding:8px; border:1px solid #ddd; border-radius:4px; font-size:14px; box-sizing:border-box; min-height:80px; resize:vertical;color:#666;background-Color:#fff;"></textarea>' +
'</div>' +
'<div style="margin-bottom:15px; padding:15px; background:#f8f9fa; border-radius:6px; border:1px solid #eaeaea;">' +
'<h4 style="margin-top:0; margin-bottom:12px; font-size:15px; color:#333;">📖 我的阅读记录(可选)</h4>' +
'<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">' +
'<div>' +
'<label style="display:block; margin-bottom:5px; font-size:13px; color:#555;">开始阅读</label>' +
'<input type="text" id="bookinfo-start-date" placeholder="例如2024-03-15" style="width:100%; padding:6px; border:1px solid #ddd; border-radius:4px; font-size:13px; box-sizing:border-box;">' +
'</div>' +
'<div>' +
'<label style="display:block; margin-bottom:5px; font-size:13px; color:#555;">结束阅读</label>' +
'<input type="text" id="bookinfo-read-date" placeholder="例如2024-03-20" style="width:100%; padding:6px; border:1px solid #ddd; border-radius:4px; font-size:13px; box-sizing:border-box;">' +
'</div>' +
'</div>' +
'<div style="margin-top:12px;">' +
'<label style="display:block; margin-bottom:5px; font-size:13px;color:#666; ">推荐指数</label>' +
'<select id="bookinfo-recommendation" style="width:100%; color:#666;padding:6px; border:1px solid #ddd; border-radius:4px; font-size:13px; box-sizing:border-box;background-Color:#fff;">' +
'<option value="0">请选择推荐指数</option>' +
'<option value="1">★ 1星 - 不推荐</option>' +
'<option value="2">★★ 2星 - 一般</option>' +
'<option value="3">★★★ 3星 - 还行</option>' +
'<option value="4">★★★★ 4星 - 推荐</option>' +
'<option value="5">★★★★★ 5星 - 极力推荐</option>' +
'</select>' +
'</div>' +
'<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;margin:10px 0px;">' +
'<div>' +
'<label style="display:block; margin-bottom:5px; font-size:13px; color:#555;">阅读方法</label>' +
'<input type="text" id="bookinfo-read-method" placeholder="例如:速读" style="width:100%; padding:6px; border:1px solid #ddd; border-radius:4px; font-size:13px; box-sizing:border-box;">' +
'</div>' +
'<div>' +
'<label style="display:block; margin-bottom:5px; font-size:13px; color:#555;">图书分类</label>' +
'<input type="text" id="bookinfo-book-category" placeholder="例如:小说" style="width:100%; padding:6px; border:1px solid #ddd; border-radius:4px; font-size:13px; box-sizing:border-box;">' +
'</div>' +
'</div>' +
'<div style="margin-bottom:15px; background:#e8f4fd; padding:12px; border-radius:6px; font-size:13px; color:#666; border:1px solid #d1e7ff;">' +
'<div style="font-weight:600; color:#000; margin-bottom:5px;">📝 生成格式预览:</div>' +
'<div style="font-family:monospace; background:#fff; padding:8px; margin-top:5px; border-radius:4px; border:1px solid #cfe2ff; max-height:80px; overflow-y:auto;">' +
'<div id="bookinfo-preview" style="color:#333; word-break:break-all; font-size:13px; line-height:1.4;">[book:ID:短评]</div>' +
'</div>' +
'<div style="margin-top:8px; color:#666;">说明自定义信息将自动保存到JSON缓存中</div>' +
'</div>' +
'<div style="text-align:right;">' +
'<button type="button" id="bookinfo-cancel" style="padding:8px 16px; margin-right:10px; background:#f5f5f5; border:1px solid #ddd; border-radius:4px; cursor:pointer; font-size:14px;">取消</button>' +
'<button type="button" id="bookinfo-insert" style="padding:8px 20px; background:#0073aa; color:white; border:none; border-radius:4px; cursor:pointer; font-size:14px; font-weight:600;">插入</button>' +
'</div>';
// 添加到页面
document.body.appendChild(overlay);
document.body.appendChild(modal);
// 获取DOM元素
var bookIdInput = document.getElementById('bookinfo-id');
var reviewInput = document.getElementById('bookinfo-review');
var startDateInput = document.getElementById('bookinfo-start-date');
var readDateInput = document.getElementById('bookinfo-read-date');
var readMethodInput = document.getElementById('bookinfo-read-method');
var bookCategoryInput = document.getElementById('bookinfo-book-category');
var recommendationSelect = document.getElementById('bookinfo-recommendation');
var previewDiv = document.getElementById('bookinfo-preview');
var insertBtn = document.getElementById('bookinfo-insert');
var cancelBtn = document.getElementById('bookinfo-cancel');
// 更新预览 - 恢复原来的预览显示
function updatePreview() {
var bookId = bookIdInput.value.trim();
var review = reviewInput.value.trim();
var startDate = startDateInput.value.trim();
var readDate = readDateInput.value.trim();
var readMethod = readMethodInput.value.trim();
var bookCategory = bookCategoryInput.value.trim();
var recommendation = parseInt(recommendationSelect.value) || 0;
var preview = '[book:' + (bookId || 'ID');
// 构建自定义数据对象
var customData = {};
if (startDate) customData.startDate = startDate;
if (readDate) customData.readDate = readDate;
if (readMethod) customData.readMethod = readMethod;
if (bookCategory) customData.bookCategory = bookCategory;
if (recommendation > 0) customData.recommendation = recommendation;
// 显示预览(中文显示)
var displayText = '';
if (review) {
displayText = review;
}
// 如果有自定义数据,添加到显示文本
if (Object.keys(customData).length > 0) {
var customText = '(自定义:';
var customParts = [];
if (startDate) customParts.push('开始阅读:' + startDate);
if (readDate) customParts.push('结束阅读:' + readDate);
if (readMethod) customParts.push('阅读方法:' + readMethod);
if (bookCategory) customParts.push('图书分类:' + bookCategory);
if (recommendation > 0) customParts.push('推荐指数:' + '★'.repeat(recommendation));
customText += customParts.join('') + '';
if (displayText) {
displayText += customText;
} else {
displayText = customText;
}
}
// 如果有内容,添加到预览
if (displayText) {
// 不对预览中的方括号进行编码,直接显示中文
preview += ':' + displayText;
}
preview += ']';
previewDiv.innerHTML = preview;
}
// 绑定输入事件
bookIdInput.addEventListener('input', updatePreview);
reviewInput.addEventListener('input', updatePreview);
startDateInput.addEventListener('input', updatePreview);
readDateInput.addEventListener('input', updatePreview);
readMethodInput.addEventListener('input', updatePreview);
bookCategoryInput.addEventListener('input', updatePreview);
recommendationSelect.addEventListener('change', updatePreview);
// 聚焦输入框
setTimeout(function() {
bookIdInput.focus();
}, 100);
// 插入按钮点击
insertBtn.addEventListener('click', function() {
var bookId = bookIdInput.value.trim();
var review = reviewInput.value.trim();
var startDate = startDateInput.value.trim();
var readDate = readDateInput.value.trim();
var readMethod = readMethodInput.value.trim();
var bookCategory = bookCategoryInput.value.trim();
var recommendation = parseInt(recommendationSelect.value) || 0;
if (bookId && /^\d+$/.test(bookId)) {
// 构建自定义数据对象
var customData = {};
if (startDate) customData.startDate = startDate;
if (readDate) customData.readDate = readDate;
if (readMethod) customData.readMethod = readMethod;
if (bookCategory) customData.bookCategory = bookCategory;
if (recommendation > 0) customData.recommendation = recommendation;
// 构建短代码
var shortcode = '[book:' + bookId;
// 如果有短评或自定义数据
if (review || Object.keys(customData).length > 0) {
var content = '';
// 处理短评部分 - 需要对短评中的方括号进行编码
if (review) {
// 对短评中的方括号进行编码
var encodedReview = review.replace(/[\[\]]/g, function(match) {
if (match === '[') return '&#91;';
if (match === ']') return '&#93;';
return match;
});
content = encodedReview;
}
// 如果有自定义数据,添加到内容后面
if (Object.keys(customData).length > 0) {
// 将自定义数据转为JSON并编码
var customJson = encodeURIComponent(JSON.stringify(customData));
if (content) {
content += '|CUSTOM:' + customJson;
} else {
content = '|CUSTOM:' + customJson;
}
}
shortcode += ':' + content;
}
shortcode += ']';
// 在编辑器中显示中文
var displayShortcode = '[book:' + bookId;
if (review || Object.keys(customData).length > 0) {
var displayContent = review;
if (Object.keys(customData).length > 0) {
var displayCustomText = '(自定义:';
var customParts = [];
if (startDate) customParts.push('开始阅读:' + startDate);
if (readDate) customParts.push('结束阅读:' + readDate);
if (readMethod) customParts.push('阅读方法:' + readMethod);
if (bookCategory) customParts.push('图书分类:' + bookCategory);
if (recommendation > 0) customParts.push('推荐指数:' + '★'.repeat(recommendation));
displayCustomText += customParts.join('') + '';
if (displayContent) {
displayContent += displayCustomText;
} else {
displayContent = displayCustomText;
}
}
displayShortcode += ':' + displayContent;
}
displayShortcode += ']';
insertBookShortcode(displayShortcode);
closeDialog();
} else if (bookId) {
alert('请输入有效的图书ID纯数字');
} else {
alert('请输入豆瓣图书ID');
}
});
// 取消按钮点击
cancelBtn.addEventListener('click', closeDialog);
// 点击遮罩层关闭
overlay.addEventListener('click', closeDialog);
// 键盘事件
bookIdInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
insertBtn.click();
} else if (e.key === 'Escape') {
closeDialog();
}
});
reviewInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && e.ctrlKey) {
e.preventDefault();
insertBtn.click();
} else if (e.key === 'Escape') {
closeDialog();
}
});
// 关闭对话框
function closeDialog() {
if (overlay.parentNode) document.body.removeChild(overlay);
if (modal.parentNode) document.body.removeChild(modal);
}
// 插入短代码到编辑器 - 显示中文而不是编码
function insertBookShortcode(shortcode) {
var textarea = document.getElementById('text');
if (!textarea) return;
var start = textarea.selectionStart;
var end = textarea.selectionEnd;
var text = textarea.value;
// 直接插入中文短代码到编辑器
textarea.value = text.substring(0, start) + shortcode + text.substring(end);
textarea.selectionStart = textarea.selectionEnd = start + shortcode.length;
textarea.focus();
// 触发输入事件
var event = new Event('input', { bubbles: true });
textarea.dispatchEvent(event);
}
}
// 初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initBookButton);
} else {
initBookButton();
}
})();
</script>
HTML;
}
}