Files
MovieInfo/Plugin.php
2026-02-23 17:32:57 +08:00

2527 lines
105 KiB
PHP
Raw Permalink 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
/**
* TMDB电影
*
* @package MovieInfo
* @author 石头厝
* @version 1.0.0
* @link https://www.shitoucuo.com/
*/
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
class MovieInfo_Plugin implements Typecho_Plugin_Interface
{
/**
* 激活插件
*/
public static function activate()
{
Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array('MovieInfo_Plugin', 'parse');
Typecho_Plugin::factory('Widget_Abstract_Contents')->excerptEx = array('MovieInfo_Plugin', 'parse');
Typecho_Plugin::factory('admin/write-post.php')->bottom = array('MovieInfo_Plugin', 'renderButton');
Typecho_Plugin::factory('admin/write-page.php')->bottom = array('MovieInfo_Plugin', 'renderButton');
$cacheDir = dirname(__FILE__) . '/cache/';
if (!file_exists($cacheDir)) mkdir($cacheDir, 0755, true);
return 'TMDB电影插件激活成功';
}
/**
* 禁用插件
*/
public static function deactivate()
{
return '插件已禁用';
}
/**
* 配置面板
*/
public static function config(Typecho_Widget_Helper_Form $form)
{
// TMDB API 配置
$tmdbApiKey = new Typecho_Widget_Helper_Form_Element_Text('tmdbApiKey', NULL, '',
'TMDB API Key', '在TMDB网站申请的API密钥');
$form->addInput($tmdbApiKey);
$language = new Typecho_Widget_Helper_Form_Element_Select('language',
array(
'zh-CN' => '中文',
'en-US' => '英文',
'ja-JP' => '日文',
'ko-KR' => '韩文',
'fr-FR' => '法文'
), 'zh-CN', '显示语言', '电影信息的显示语言');
$form->addInput($language);
$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=', '图片代理', '用于加载TMDB图片');
$form->addInput($imageProxy);
$defaultPoster = new Typecho_Widget_Helper_Form_Element_Text('defaultPoster', NULL,
'https://via.placeholder.com/300x450/CCCCCC/666666?text=No+Poster',
'默认海报', '当无法获取海报时显示的图片');
$form->addInput($defaultPoster);
$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 MovieInfo_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_movies]标记,则显示所有电影
if ($widget instanceof Widget_Archive && $widget->is('page')) {
$pattern = '/\[all_movies(?::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;
$allMoviesHtml = self::renderAllMovies($page);
$content = str_replace($match, $allMoviesHtml, $content);
}
}
}
// 如果是单篇文章,解析电影短代码
if ($widget instanceof Widget_Archive && $widget->is('single')) {
// 匹配 [movie:数字] 或 [movie:数字:短评] 格式
$pattern = '/\[movie:(\d+)(?::([^\]]+))?\]/i';
if (preg_match_all($pattern, $content, $matches)) {
foreach ($matches[0] as $key => $match) {
$movieId = $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)) {
$decodedJson = urldecode($customJson);
$customData = json_decode($decodedJson, true);
if (!is_array($customData)) {
$customData = array();
}
}
} else {
// 检查旧格式的自定义数据
if (preg_match('/^(自定义:(.*)$/u', $reviewWithCustom, $customMatch)) {
$review = '';
$customData = self::parseLegacyCustomData($customMatch[1]);
} else {
// 检查中文括号格式
if (preg_match('/^(.*?)(自定义:(.*)$/u', $reviewWithCustom, $customMatch)) {
$review = trim($customMatch[1]);
$customData = self::parseLegacyCustomData($customMatch[2]);
} else {
$review = $reviewWithCustom;
}
}
}
// 解码短评
if (!empty($review)) {
$review = str_replace(array('&#91;', '&#93;'), array('[', ']'), $review);
}
}
// 获取电影数据
$movieHtml = self::renderMovie($movieId, $review, $customData);
$content = str_replace($match, $movieHtml, $content);
}
}
}
return $content;
}
/**
* 解析旧格式的自定义数据
*/
private static function parseLegacyCustomData($customText)
{
$customData = array();
$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['watchDate'] = trim($value);
break;
case '观看方式':
$customData['watchMethod'] = trim($value);
break;
case '电影分类':
$customData['movieCategory'] = trim($value);
break;
case '推荐指数':
$starCount = substr_count($value, '★');
$customData['recommendation'] = $starCount;
break;
case '我的评分':
$customData['myRating'] = trim($value);
break;
}
}
}
return $customData;
}
/**
* 渲染单部电影信息
*/
private static function renderMovie($movieId, $review = '', $customData = array())
{
// 获取电影数据
$movieData = self::getMovieData($movieId, $review, $customData);
if (!$movieData || empty($movieData['title'])) {
return '<div style="padding:10px; background:#f8d7da; color:#721c24; border:1px solid #f5c6cb; border-radius:4px; margin:10px 0;">
获取电影信息失败ID' . htmlspecialchars($movieId) . '
</div>';
}
$options = Typecho_Widget::widget('Widget_Options')->plugin('MovieInfo');
$imageProxy = isset($options->imageProxy) ? $options->imageProxy : 'https://images.weserv.nl/?url=';
$defaultPoster = isset($options->defaultPoster) ? $options->defaultPoster :
'https://via.placeholder.com/300x450/CCCCCC/666666?text=No+Poster';
$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($movieData['title']);
$originalTitle = isset($movieData['original_title']) ? htmlspecialchars($movieData['original_title']) : '';
$releaseYear = isset($movieData['release_year']) ? htmlspecialchars($movieData['release_year']) : '未知';
$director = isset($movieData['director']) ? htmlspecialchars($movieData['director']) : '未知';
$genres = isset($movieData['genres']) ? $movieData['genres'] : array('未知');
$runtime = isset($movieData['runtime']) ? htmlspecialchars($movieData['runtime']) . '分钟' : '未知';
$summary = isset($movieData['overview']) ? $movieData['overview'] : '';
$rating = isset($movieData['vote_average']) ? floatval($movieData['vote_average']) : 0;
$ratingCount = isset($movieData['vote_count']) ? intval($movieData['vote_count']) : 0;
$productionCountries = isset($movieData['production_countries']) ? $movieData['production_countries'] : array('未知');
$review = htmlspecialchars($review);
// 自定义字段
$startDate = isset($customData['startDate']) ? htmlspecialchars($customData['startDate']) : '';
$watchDate = isset($customData['watchDate']) ? htmlspecialchars($customData['watchDate']) : '';
$watchMethod = isset($customData['watchMethod']) ? htmlspecialchars($customData['watchMethod']) : '';
$movieCategory = isset($customData['movieCategory']) ? htmlspecialchars($customData['movieCategory']) : '';
$recommendation = isset($customData['recommendation']) ? intval($customData['recommendation']) : 0;
$myRating = isset($customData['myRating']) ? htmlspecialchars($customData['myRating']) : '';
// 如果customData中没有尝试从movieData中获取
if (empty($startDate) && isset($movieData['custom_start_date']) && !empty($movieData['custom_start_date'])) {
$startDate = htmlspecialchars($movieData['custom_start_date']);
}
if (empty($watchDate) && isset($movieData['custom_watch_date']) && !empty($movieData['custom_watch_date'])) {
$watchDate = htmlspecialchars($movieData['custom_watch_date']);
}
if (empty($watchMethod) && isset($movieData['custom_watch_method']) && !empty($movieData['custom_watch_method'])) {
$watchMethod = htmlspecialchars($movieData['custom_watch_method']);
}
if (empty($movieCategory) && isset($movieData['custom_movie_category']) && !empty($movieData['custom_movie_category'])) {
$movieCategory = htmlspecialchars($movieData['custom_movie_category']);
}
if ($recommendation == 0 && isset($movieData['custom_recommendation'])) {
$recommendation = intval($movieData['custom_recommendation']);
}
if (empty($myRating) && isset($movieData['custom_my_rating'])) {
$myRating = htmlspecialchars($movieData['custom_my_rating']);
}
// 检查简介长度
$isSummaryLong = false;
$summaryShort = $summary;
$plainSummary = strip_tags($summary);
if (mb_strlen($plainSummary, 'UTF-8') > $summaryLength) {
$isSummaryLong = true;
$plainShort = mb_substr($plainSummary, 0, $summaryLength, 'UTF-8');
$summaryShort = $plainShort . '...';
}
// 海报URL
$posterPath = isset($movieData['poster_path']) ? $movieData['poster_path'] : '';
if ($posterPath) {
$posterUrl = 'https://image.tmdb.org/t/p/w300' . $posterPath;
$posterSrc = $imageProxy . urlencode($posterUrl);
} else {
$posterSrc = $defaultPoster;
}
// 类型显示 - 确保不换行
$genresHtml = '';
if (is_array($genres)) {
$genresHtml = implode(' / ', $genres);
}
// 地区显示
$countriesHtml = '';
if (is_array($productionCountries)) {
$countriesHtml = implode(' / ', $productionCountries);
}
// TMDB评分显示 - 移除括号和评价人数
$ratingHtml = '';
if ($rating > 0) {
$ratingHtml = '<div class="movie-rating" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">评分:</span>
<span style="color:var(--movie-rating-color); font-weight:600; font-size:16px;">' . number_format($rating, 1) . '</span>
</div>';
}
// 我的评分显示
$myRatingHtml = '';
if (!empty($myRating)) {
$myRatingHtml = '<div class="movie-my-rating" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">我的评分:</span>
<span style="color:var(--movie-my-rating-color); font-weight:600; font-size:16px;">' . $myRating . '</span>
</div>';
}
// 推荐指数星星
$recommendationHtml = '';
if ($recommendation > 0) {
$recommendationHtml = '<div class="movie-recommendation" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">推荐指数:</span>';
for ($i = 1; $i <= 5; $i++) {
if ($i <= $recommendation) {
$recommendationHtml .= '<span style="color:var(--movie-star-color); font-size:16px; margin-right:2px;">★</span>';
} else {
$recommendationHtml .= '<span style="color:var(--movie-star-empty-color); font-size:16px; margin-right:2px;">★</span>';
}
}
$recommendationHtml .= '</div>';
}
// 自定义信息 - 按图书插件逻辑处理开始和结束观看
$customInfoHtml = '';
$hasCustomInfo = false;
// 判断开始和结束观看日期是否相同
$hasStartDate = !empty($startDate);
$hasWatchDate = !empty($watchDate);
$datesEqual = $hasStartDate && $hasWatchDate && $startDate === $watchDate;
if ($hasStartDate || $hasWatchDate || $watchMethod || $movieCategory) {
$hasCustomInfo = true;
if ($datesEqual) {
// 开始和结束观看日期相同,显示"观看日期"
$customInfoHtml .= '<div class="movie-watch-date" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">观看日期:</span>
<span style="color:var(--movie-text-primary);">' . $startDate . '</span>
</div>';
} else {
// 日期不同或只有一个日期,分开显示
if ($startDate) {
$customInfoHtml .= '<div class="movie-start-date" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">开始观看:</span>
<span style="color:var(--movie-text-primary);">' . $startDate . '</span>
</div>';
}
if ($watchDate) {
$customInfoHtml .= '<div class="movie-end-date" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">结束观看:</span>
<span style="color:var(--movie-text-primary);">' . $watchDate . '</span>
</div>';
}
}
if ($watchMethod) {
$customInfoHtml .= '<div class="movie-watch-method" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">观看方式:</span>
<span style="color:var(--movie-text-primary);">' . $watchMethod . '</span>
</div>';
}
if ($movieCategory) {
$customInfoHtml .= '<div class="movie-category" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">电影分类:</span>
<span style="color:var(--movie-text-primary);">' . $movieCategory . '</span>
</div>';
}
}
// 右侧栏完整HTML
$rightColumnHtml = '';
if ($myRatingHtml) {
$rightColumnHtml .= $myRatingHtml;
}
if ($recommendationHtml) {
$rightColumnHtml .= $recommendationHtml;
}
if ($customInfoHtml) {
$rightColumnHtml .= $customInfoHtml;
} else {
$rightColumnHtml .= '<div class="movie-no-record" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">观看记录:</span>
<span style="color:var(--movie-text-tertiary);">暂无记录</span>
</div>';
}
// 简介部分HTML
$summaryHtml = '<div class="movie-summary-container">';
if ($isSummaryLong) {
$summaryHtml .= '
<div class="movie-summary-short" style="display:block;">
<div style="font-size:14px; line-height:1.8; color:var(--movie-text-primary); text-align:justify; font-family:\"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif;">
' . nl2br($summaryShort) . '
<a href="javascript:void(0);" class="movie-summary-expand"
onclick="toggleMovieSummary(this)"
style="color:' . htmlspecialchars($expandColor) . '; text-decoration:none; font-weight:600; cursor:pointer; margin-left:5px; padding:2px 8px; background:var(--movie-button-bg); border-radius:4px; border:1px solid var(--movie-border-color); font-size:13px; transition:all 0.2s ease;"
onmouseover="this.style.backgroundColor=\'var(--movie-button-hover-bg)\'; this.style.borderColor=\'' . htmlspecialchars($expandColor) . '\';"
onmouseout="this.style.backgroundColor=\'var(--movie-button-bg)\'; this.style.borderColor=\'var(--movie-border-color)\';">'
. htmlspecialchars($expandText) . ' ↓
</a>
</div>
</div>
<div class="movie-summary-full" style="display:none;">
<div style="font-size:14px; line-height:1.8; color:var(--movie-text-primary); text-align:justify; font-family:\"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif;">
' . $summary . '
<a href="javascript:void(0);" class="movie-summary-collapse"
onclick="toggleMovieSummary(this)"
style="color:' . htmlspecialchars($expandColor) . '; text-decoration:none; font-weight:600; cursor:pointer; margin-left:5px; padding:2px 8px; background:var(--movie-button-bg); border-radius:4px; border:1px solid var(--movie-border-color); font-size:13px; transition:all 0.2s ease;"
onmouseover="this.style.backgroundColor=\'var(--movie-button-hover-bg)\'; this.style.borderColor=\'' . htmlspecialchars($expandColor) . '\';"
onmouseout="this.style.backgroundColor=\'var(--movie-button-bg)\'; this.style.borderColor=\'var(--movie-border-color)\';">'
. htmlspecialchars($collapseText) . ' ↑
</a>
</div>
</div>';
} else {
$summaryHtml .= '
<div style="font-size:14px; line-height:1.8; color:var(--movie-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="movie-review-container" style="margin-top:15px;">
<div class="movie-review-title" style="margin:0 0 8px 0; font-size:14px; color:var(--movie-text-primary); font-weight:600; padding-bottom:8px; border-bottom:1px solid var(--movie-border-color);">💭 我的影评</div>
<div class="movie-review-content" style="font-size:14px; line-height:1.8; color:var(--movie-text-primary); text-align:justify; background:var(--movie-review-bg); padding:15px; border-radius:8px; border-left:3px solid var(--movie-primary-color); font-family:\"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif;">
' . nl2br($review) . '
</div>
</div>';
}
// 原始标题显示 - 仅作为鼠标悬停提示
$titleTooltip = '';
if ($originalTitle && $originalTitle != $title) {
$titleTooltip = ' title="原名:' . $originalTitle . '"';
}
$html = <<<HTML
<style>
:root {
--movie-bg-primary: #ffffff;
--movie-bg-secondary: #f9fafb;
--movie-bg-tertiary: #fafafa;
--movie-bg-card: #ffffff;
--movie-text-primary: #1a1a1a;
--movie-text-secondary: #666666;
--movie-text-tertiary: #999999;
--movie-primary-color: #0073aa;
--movie-primary-hover: #0056b3;
--movie-accent-color: #01d277;
--movie-accent-hover: #01b36b;
--movie-border-color: #e8e8e8;
--movie-border-light: #f0f0f0;
--movie-shadow-color: rgba(0,0,0,0.08);
--movie-rating-color: #ffac2d;
--movie-my-rating-color: #ff6b35;
--movie-star-color: #ff6b35;
--movie-star-empty-color: #dddddd;
--movie-button-bg: #f8f9fa;
--movie-button-hover-bg: #e9ecef;
--movie-review-bg: #f9f9f9;
}
@media (prefers-color-scheme: dark) {
:root {
--movie-bg-primary: #1a1a1a;
--movie-bg-secondary: #2d2d2d;
--movie-bg-tertiary: #2a2a2a;
--movie-bg-card: #2d2d2d;
--movie-text-primary: #e0e0e0;
--movie-text-secondary: #aaaaaa;
--movie-text-tertiary: #888888;
--movie-primary-color: #4dabf7;
--movie-primary-hover: #74c0fc;
--movie-accent-color: #20c997;
--movie-accent-hover: #12b886;
--movie-border-color: #404040;
--movie-border-light: #333333;
--movie-shadow-color: rgba(0,0,0,0.2);
--movie-rating-color: #ffc107;
--movie-my-rating-color: #ff922b;
--movie-star-color: #ff922b;
--movie-star-empty-color: #555555;
--movie-button-bg: #3a3a3a;
--movie-button-hover-bg: #4a4a4a;
--movie-review-bg: #3a3a3a;
}
}
.dark .markdown-body h2{border-bottom:0px!important;margin-top:0px!important;padding-bottom:0px!important;margin-bottom:0px!important;}
.movie-card-content {
background: var(--movie-bg-card);
border-radius: 12px;
box-shadow: 0 4px 12px var(--movie-shadow-color);
overflow: hidden;
border: 1px solid var(--movie-border-color);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.movie-card-content:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px var(--movie-shadow-color);
}
.movie-header {
padding: 20px 25px;
background: linear-gradient(135deg, var(--movie-bg-secondary) 0%, var(--movie-bg-tertiary) 100%);
border-bottom: 1px solid var(--movie-border-light);
text-align: center;
}
.movie-title {
margin: 0;
font-size: 22px;
line-height: 1.3;
color: var(--movie-text-primary);
font-weight: 700;
}
.movie-title-link {
color: var(--movie-primary-color);
text-decoration: none;
transition: color 0.2s ease;
}
.movie-title-link:hover {
color: var(--movie-primary-hover);
text-decoration: underline;
}
.movie-content {
padding: 25px;
border-bottom: 1px solid var(--movie-border-light);
background: var(--movie-bg-card);
}
.movie-grid {
display: grid;
grid-template-columns: 120px 1fr 1fr;
gap: 25px;
align-items: stretch;
}
@media (max-width: 768px) {
.movie-grid {
grid-template-columns: 1fr;
gap: 20px;
}
.movie-grid > div:nth-child(2) {
border-right: none;
padding-right: 0;
}
.movie-grid > div:nth-child(3) {
padding-left: 0;
}
}
.movie-poster-container {
display: flex;
align-items: center;
justify-content: center;
}
.movie-poster-link {
display: block;
text-decoration: none;
width: 100%;
}
.movie-poster-wrapper {
position: relative;
width: 100%;
}
.movie-poster-frame {
width: 100%;
height: 0;
padding-bottom: 150%;
position: relative;
overflow: hidden;
border-radius: 8px;
border: 3px solid var(--movie-bg-card);
box-shadow: 0 4px 12px rgba(0,0,0,0.12);
background: var(--movie-bg-tertiary);
}
.movie-poster {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.movie-poster:hover {
transform: scale(1.05);
}
.movie-poster-badge {
position: absolute;
top: -5px;
right: -5px;
background: var(--movie-accent-color);
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(1,210,119,0.3);
}
.movie-info-left {
padding-right: 15px;
border-right: 1px solid var(--movie-border-color);
}
.movie-info-item {
margin: 0 0 10px 0;
font-size: 14px;
color: var(--movie-text-secondary);
line-height: 1.5;
}
.movie-info-label {
font-weight: 600;
color: var(--movie-text-primary);
display: inline-block;
width: 70px;
flex-shrink: 0;
}
.movie-info-value {
color: var(--movie-text-primary);
}
.movie-genres {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline;
}
.movie-info-right {
padding-left: 15px;
}
.movie-footer {
padding: 20px 25px 25px 25px;
}
.movie-summary-section {
margin-bottom: 20px;
}
.movie-summary-header {
margin: 0 0 12px 0;
font-size: 16px;
color: var(--movie-text-primary);
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
padding-bottom: 8px;
border-bottom: 1px solid var(--movie-border-color);
}
.movie-summary-icon {
background: var(--movie-accent-color);
color: white;
width: 28px;
height: 28px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
}
.movie-summary-content {
background: var(--movie-bg-tertiary);
padding: 20px;
border-radius: 8px;
border: 1px solid var(--movie-border-color);
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
</style>
<div class="movie-card">
<div class="movie-card-content">
<!-- 上栏:标题 -->
<div class="movie-header">
<h2 class="movie-title">
<a href="https://www.themoviedb.org/movie/{$movieId}" target="_blank"
class="movie-title-link" {$titleTooltip}>
《{$title}》
</a>
</h2>
</div>
<!-- 中栏:三列布局 -->
<div class="movie-content">
<div class="movie-grid">
<!-- 左侧:电影海报 -->
<div class="movie-poster-container">
<a href="https://www.themoviedb.org/movie/{$movieId}" target="_blank" class="movie-poster-link">
<div class="movie-poster-wrapper">
<div class="movie-poster-frame">
<img src="{$posterSrc}" alt="{$title}" class="movie-poster"
onerror="this.src='{$defaultPoster}';">
</div>
<div class="movie-poster-badge">🎬</div>
</div>
</a>
</div>
<!-- 中间:电影信息 - 修复:按新顺序显示 -->
<div class="movie-info-left">
<!-- 导演 -->
<div class="movie-info-item">
<span class="movie-info-label">导演:</span>
<span class="movie-info-value" style="color:var(--movie-primary-color);">{$director}</span>
</div>
<!-- 类型 - 修复:确保在一行显示 -->
<div class="movie-info-item">
<span class="movie-info-label">类型:</span>
<span class="movie-info-value movie-genres" title="{$genresHtml}">{$genresHtml}</span>
</div>
<!-- 年份 -->
<div class="movie-info-item">
<span class="movie-info-label">年份:</span>
<span class="movie-info-value">{$releaseYear}</span>
</div>
<!-- 时长 -->
<div class="movie-info-item">
<span class="movie-info-label">时长:</span>
<span class="movie-info-value">{$runtime}</span>
</div>
<!-- 地区 -->
<div class="movie-info-item">
<span class="movie-info-label">地区:</span>
<span class="movie-info-value">{$countriesHtml}</span>
</div>
<!-- 评分 -->
{$ratingHtml}
</div>
<!-- 右侧:自定义信息 -->
<div class="movie-info-right">
{$rightColumnHtml}
</div>
</div>
</div>
<!-- 下栏:剧情简介和影评 -->
<div class="movie-footer">
<!-- 剧情简介 -->
<div class="movie-summary-section">
<div class="movie-summary-header">
<span class="movie-summary-icon">📽️</span>
<span>剧情简介</span>
</div>
<div class="movie-summary-content">
{$summaryHtml}
</div>
</div>
<!-- 影评 -->
{$reviewHtml}
</div>
</div>
</div>
HTML;
// 添加JavaScript切换函数
$html .= '
<script>
function toggleMovieSummary(element) {
var container = element.closest(".movie-summary-container");
var shortDiv = container.querySelector(".movie-summary-short");
var fullDiv = container.querySelector(".movie-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 getAllMoviesData($page = 1, $pageSize = 10)
{
$cacheDir = dirname(__FILE__) . '/cache/';
$allMovies = 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'])) {
$movieId = pathinfo($file, PATHINFO_FILENAME);
$data['movieId'] = $movieId;
if (file_exists($filePath)) {
$data['added_time'] = filemtime($filePath);
} else {
$data['added_time'] = isset($data['fetched_at']) ? $data['fetched_at'] : time();
}
$allMovies[] = $data;
}
}
}
}
}
// 按added_time倒序排序
usort($allMovies, function($a, $b) {
return $b['added_time'] - $a['added_time'];
});
$total = count($allMovies);
$totalPages = ceil($total / $pageSize);
// 限制页码范围
$page = max(1, min($page, $totalPages));
// 分页处理
$startIndex = ($page - 1) * $pageSize;
$paginatedMovies = array_slice($allMovies, $startIndex, $pageSize);
return array(
'movies' => $paginatedMovies,
'total' => $total,
'page' => $page,
'pageSize' => $pageSize,
'totalPages' => $totalPages,
'startIndex' => $startIndex + 1,
'endIndex' => min($startIndex + $pageSize, $total)
);
}
/**
* 渲染所有电影列表(简单文本格式)- 支持分页
*/
public static function renderAllMovies($page = 1)
{
// 从GET参数获取页码
if (isset($_GET['page']) && is_numeric($_GET['page'])) {
$page = intval($_GET['page']);
}
$options = Typecho_Widget::widget('Widget_Options')->plugin('MovieInfo');
$pageSize = isset($options->pageSize) ? intval($options->pageSize) : 10;
$imageProxy = isset($options->imageProxy) ? $options->imageProxy : 'https://images.weserv.nl/?url=';
$defaultPoster = isset($options->defaultPoster) ? $options->defaultPoster :
'https://via.placeholder.com/300x450/CCCCCC/666666?text=No+Poster';
// 获取分页数据
$paginationData = self::getAllMoviesData($page, $pageSize);
$allMovies = $paginationData['movies'];
$total = $paginationData['total'];
$currentPage = $paginationData['page'];
$totalPages = $paginationData['totalPages'];
$startIndex = $paginationData['startIndex'];
$endIndex = $paginationData['endIndex'];
if (empty($allMovies)) {
return '<div style="padding:20px; text-align:center; color:var(--movie-text-secondary); background:var(--movie-bg-tertiary); border-radius:8px; border:1px solid var(--movie-border-color);">
<p>暂无电影数据</p>
<p style="font-size:13px; color:var(--movie-text-tertiary);">请先在文章中使用[movie:ID]短代码添加电影</p>
</div>';
}
$html = '<style>
.movies-list-container {
margin: 20px 0;
font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
}
.movies-header {
margin-bottom: 20px;
padding: 20px;
background: var(--movie-bg-card);
border-radius: 8px;
border: 1px solid var(--movie-border-color);
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
text-align: center;
border-left: 4px solid var(--movie-accent-color);
}
.movies-title {
margin: 0;
font-size: 24px;
color: var(--movie-text-primary);
font-weight: 600;
}
.movies-subtitle {
margin: 8px 0 0 0;
font-size: 14px;
color: var(--movie-text-secondary);
line-height: 1.5;
}
.movies-pagination-info {
margin-bottom: 25px;
padding: 18px;
background: var(--movie-bg-card);
border-radius: 8px;
border: 1px solid var(--movie-border-color);
box-shadow: 0 2px 6px rgba(0,0,0,0.03);
}
.movies-info-row {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
.movies-info-item {
font-size: 14px;
color: var(--movie-text-secondary);
}
.movies-info-label {
font-weight: 600;
color: var(--movie-text-primary);
}
.movies-count {
color: var(--movie-accent-color);
font-weight: 600;
}
.movies-total {
color: var(--movie-text-primary);
font-weight: 600;
}
.movies-current-page {
color: var(--movie-my-rating-color);
font-weight: 600;
}
.movies-total-pages {
color: var(--movie-text-primary);
font-weight: 600;
}
.movie-item-card {
margin-bottom: 25px;
padding: 0;
background: var(--movie-bg-card);
border-radius: 10px;
border: 1px solid var(--movie-border-color);
box-shadow: 0 3px 10px rgba(0,0,0,0.06);
transition: all 0.3s ease;
overflow: hidden;
}
.movie-item-card:hover {
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
transform: translateY(-3px);
border-color: var(--movie-accent-color);
}
.movie-item-content {
display: flex;
padding: 20px;
gap: 20px;
}
@media (max-width: 768px) {
.movie-item-content {
flex-direction: column;
gap: 15px;
}
.movie-poster-container {
width: 120px;
margin: 0 auto 10px;
}
}
.movie-poster-container {
position: relative;
width: 100px;
flex-shrink: 0;
}
.movie-poster-wrapper {
position: relative;
width: 100%;
height: 0;
padding-bottom: 150%;
border-radius: 6px;
overflow: hidden;
background: var(--movie-bg-tertiary);
border: 1px solid var(--movie-border-color);
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
}
.movie-poster {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.movie-poster:hover {
transform: scale(1.05);
}
.movie-index-on-poster {
position: absolute;
top: -8px;
right: -8px;
width: 32px;
height: 32px;
background: linear-gradient(135deg, var(--index-color));
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 16px;
box-shadow: 0 3px 8px rgba(0,0,0,0.2);
border: 2px solid var(--movie-bg-card);
z-index: 10;
}
.movie-details {
flex-grow: 1;
}
.movie-title-line {
margin-bottom: 8px;
font-size: 16px;
line-height: 1.5;
}
.movie-title-link {
color: var(--movie-accent-color);
text-decoration: none;
font-weight: bold;
font-size: 18px;
transition: color 0.2s ease;
}
.movie-title-link:hover {
color: var(--movie-accent-hover);
text-decoration: underline;
}
.movie-separator {
color: var(--movie-text-tertiary);
font-size: 14px;
margin: 0 8px;
}
.movie-category {
color: var(--movie-accent-color);
font-size: 14px;
margin-left: 5px;
font-weight: 500;
background: rgba(1, 210, 119, 0.1);
padding: 2px 8px;
border-radius: 4px;
border: 1px solid rgba(1, 210, 119, 0.2);
}
.movie-date {
color: var(--movie-my-rating-color);
font-size: 14px;
margin-left: 5px;
font-weight: 500;
}
.movie-method {
color: #27ae60;
font-size: 14px;
margin-left: 5px;
font-weight: 500;
background: rgba(39, 174, 96, 0.1);
padding: 2px 8px;
border-radius: 4px;
border: 1px solid rgba(39, 174, 96, 0.2);
}
.movie-info-line {
margin-bottom: 10px;
font-size: 14px;
color: var(--movie-text-secondary);
line-height: 1.5;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
}
.movie-director {
color: var(--movie-text-primary);
font-weight: 500;
}
.movie-director-label {
font-weight: 600;
color: var(--movie-text-primary);
}
.movie-genres {
color: var(--movie-text-tertiary);
white-space: normal;
display: inline;
font-weight: 500;
}
.movie-genre-separator {
margin: 0 4px;
color: var(--movie-text-tertiary);
font-weight: 300;
}
.movie-year {
color: var(--movie-text-tertiary);
font-weight: 500;
}
.movie-runtime {
color: var(--movie-text-tertiary);
font-weight: 500;
}
.movie-rating-value {
color: var(--movie-rating-color);
font-weight: bold;
background: rgba(255, 172, 45, 0.1);
padding: 2px 6px;
border-radius: 4px;
border: 1px solid rgba(255, 172, 45, 0.2);
}
.movie-no-rating {
color: var(--movie-text-tertiary);
font-weight: 500;
}
.movie-my-rating-value {
color: var(--movie-my-rating-color);
font-weight: bold;
background: rgba(255, 107, 53, 0.1);
padding: 2px 6px;
border-radius: 4px;
border: 1px solid rgba(255, 107, 53, 0.2);
}
.movie-review-section {
margin-top: 12px;
padding-top: 12px;
border-top: 1px dashed var(--movie-border-color);
}
.movie-review-title {
font-size: 13px;
color: var(--movie-text-primary);
font-weight: 600;
margin-bottom: 5px;
display: flex;
align-items: center;
gap: 6px;
}
.movie-review-content {
font-size: 14px;
line-height: 1.6;
color: var(--movie-text-primary);
background: var(--movie-review-bg);
padding: 12px;
border-radius: 6px;
border-left: 3px solid var(--movie-accent-color);
}
</style>';
$html .= '<div class="movies-list-container">';
// 标题模块 - 调整深色模式
$html .= '<div class="movies-header">
<h2 class="movies-title">🎬 我的全部已看电影</h2>
<p class="movies-subtitle">已记录 <span style="color:var(--movie-accent-color);font-weight:600;">' . $total . '</span> 部电影,继续探索更多精彩影片</p>
</div>';
// 页码信息 - 调整深色模式
$html .= '<div class="movies-pagination-info">
<div class="movies-info-row">
<div class="movies-info-item">
<span class="movies-info-label">显示范围:</span>
<span class="movies-count">' . $startIndex . '-' . $endIndex . '</span>
<span class="movies-info-label"> / 总计:</span>
<span class="movies-total">' . $total . '</span>
</div>
<div class="movies-info-item">
<span class="movies-info-label">当前页码:</span>
<span class="movies-current-page">第 ' . $currentPage . ' 页</span>
<span class="movies-info-label"> / 总页数:</span>
<span class="movies-total-pages">' . $totalPages . ' 页</span>
</div>
</div>
</div>';
// 计算倒序序号
$totalCount = $total;
$currentIndex = $totalCount - (($currentPage - 1) * $pageSize);
foreach ($allMovies as $movie) {
$movieId = $movie['movieId'];
$title = isset($movie['title']) ? htmlspecialchars($movie['title']) : '未知电影';
$originalTitle = isset($movie['original_title']) ? htmlspecialchars($movie['original_title']) : '';
// 海报URL
$posterPath = isset($movie['poster_path']) ? $movie['poster_path'] : '';
if ($posterPath) {
$posterUrl = 'https://image.tmdb.org/t/p/w200' . $posterPath;
$posterSrc = $imageProxy . urlencode($posterUrl);
} else {
$posterSrc = $defaultPoster;
}
// 自定义字段
$startDate = isset($movie['custom_start_date']) ? htmlspecialchars($movie['custom_start_date']) : '';
$watchDate = isset($movie['custom_watch_date']) ? htmlspecialchars($movie['custom_watch_date']) : '';
$watchMethod = isset($movie['custom_watch_method']) ? htmlspecialchars($movie['custom_watch_method']) : '';
$movieCategory = isset($movie['custom_movie_category']) ? htmlspecialchars($movie['custom_movie_category']) : '';
$myRating = isset($movie['custom_my_rating']) ? htmlspecialchars($movie['custom_my_rating']) : '';
// 电影信息
$releaseYear = isset($movie['release_year']) ? htmlspecialchars($movie['release_year']) : '未知';
$director = isset($movie['director']) ? htmlspecialchars($movie['director']) : '未知';
// 类型 - 使用·分隔符
$genres = '未知';
if (isset($movie['genres']) && is_array($movie['genres'])) {
$genres = implode('<span class="movie-genre-separator">·</span>', $movie['genres']);
}
// 时长
$runtime = isset($movie['runtime']) ? htmlspecialchars($movie['runtime']) . '分钟' : '未知';
// 评分
$rating = isset($movie['vote_average']) ? floatval($movie['vote_average']) : 0;
$ratingDisplay = $rating > 0 ? number_format($rating, 1) . '分' : '暂无评分';
// 短评
$review = isset($movie['review']) ? htmlspecialchars($movie['review']) : '';
// 原始标题作为工具提示
$titleTooltip = '';
if ($originalTitle && $originalTitle != $title) {
$titleTooltip = ' title="原名:' . $originalTitle . '"';
}
// 获取序号颜色
$indexColors = [
'#01d277, #01b36b', // TMDB绿色
'#0073aa, #0056b3', // 蓝色
'#e67e22, #d35400', // 橙色
'#9b59b6, #8e44ad', // 紫色
'#e74c3c, #c0392b', // 红色
];
$indexColor = $indexColors[($currentIndex - 1) % count($indexColors)];
$html .= '<div class="movie-item-card">
<div class="movie-item-content">
<!-- 左侧:电影海报 -->
<div class="movie-poster-container">
<a href="https://www.themoviedb.org/movie/' . $movieId . '" target="_blank" style="text-decoration:none;">
<div class="movie-poster-wrapper">
<img src="' . $posterSrc . '" alt="' . $title . '" class="movie-poster"
onerror="this.src=\'' . $defaultPoster . '\';">
<!-- 序号显示在封面上 -->
<div class="movie-index-on-poster" style="--index-color:' . $indexColor . ';">' . $currentIndex . '</div>
</div>
</a>
</div>
<!-- 右侧:电影详情 -->
<div class="movie-details">
<!-- 第一行:电影名/分类/观看日期/观看方式 -->
<div class="movie-title-line">
<a href="https://www.themoviedb.org/movie/' . $movieId . '" target="_blank"
class="movie-title-link"' . $titleTooltip . '>《' . $title . '》</a>';
if ($movieCategory) {
$html .= '<span class="movie-separator">/</span><span class="movie-category">' . $movieCategory . '</span>';
}
// 处理观看日期显示
if ($startDate && $watchDate) {
if ($startDate === $watchDate) {
$html .= '<span class="movie-separator">/</span><span class="movie-date">' . $startDate . '</span>';
} else {
$html .= '<span class="movie-separator">/</span><span class="movie-date">' . $startDate . '-' . $watchDate . '</span>';
}
} elseif ($startDate) {
$html .= '<span class="movie-separator">/</span><span class="movie-date">' . $startDate . '-至今</span>';
} elseif ($watchDate) {
$html .= '<span class="movie-separator">/</span><span class="movie-date">未知-' . $watchDate . '</span>';
}
if ($watchMethod) {
$html .= '<span class="movie-separator">/</span><span class="movie-method">' . $watchMethod . '</span>';
}
$html .= '</div>';
// 第二行:导演/类型/年份/时长/评分 - 修改:导演前面加"导演:"
$html .= '<div class="movie-info-line">';
$html .= '<span class="movie-director-label">导演:</span><span class="movie-director">' . $director . '</span>';
$html .= '<span class="movie-separator">/</span>';
// 类型显示 - 使用·分隔符
$html .= '<span class="movie-genres">' . $genres . '</span>';
$html .= '<span class="movie-separator">/</span>';
$html .= '<span class="movie-year">' . $releaseYear . '年</span>';
$html .= '<span class="movie-separator">/</span>';
$html .= '<span class="movie-runtime">' . $runtime . '</span>';
$html .= '<span class="movie-separator">/</span>';
if ($rating > 0) {
$html .= '<span class="movie-rating-value">评分:' . $ratingDisplay . '</span>';
} else {
$html .= '<span class="movie-no-rating">' . $ratingDisplay . '</span>';
}
if ($myRating) {
$html .= '<span class="movie-separator">/</span><span class="movie-my-rating-value">我的评分:' . $myRating . '</span>';
}
$html .= '</div>';
// 第三行:短评
if ($review) {
$html .= '<div class="movie-review-section">
<div class="movie-review-title">📝 影评</div>
<div class="movie-review-content">' . nl2br($review) . '</div>
</div>';
}
$html .= '</div>
</div>
</div>';
$currentIndex--;
}
// 分页导航
if ($totalPages > 1) {
$html .= self::generatePagination($currentPage, $totalPages);
}
$html .= '</div>';
return $html;
}
/**
* 生成分页HTML
*/
private static function generatePagination($currentPage, $totalPages, $baseUrl = '')
{
if ($totalPages <= 1) {
return '';
}
$html = '<div class="movie-pagination" style="margin:30px 0; text-align:center;">
<div style="display:inline-flex; align-items:center; gap:8px; background:var(--movie-bg-card); padding:12px 20px; border-radius:8px; border:1px solid var(--movie-border-color); box-shadow:0 2px 8px rgba(0,0,0,0.05);">';
// 首页
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(--movie-button-bg); color:var(--movie-primary-color); text-decoration:none; font-size:14px; font-weight:600; transition:all 0.2s ease;"
onmouseover="this.style.background=\'var(--movie-primary-color)\'; this.style.color=\'white\';"
onmouseout="this.style.background=\'var(--movie-button-bg)\'; this.style.color=\'var(--movie-primary-color)\';">«</a>';
} else {
$html .= '<span style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--movie-button-bg); color:var(--movie-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(--movie-button-bg); color:var(--movie-primary-color); text-decoration:none; font-size:14px; font-weight:600; transition:all 0.2s ease;"
onmouseover="this.style.background=\'var(--movie-primary-color)\'; this.style.color=\'white\';"
onmouseout="this.style.background=\'var(--movie-button-bg)\'; this.style.color=\'var(--movie-primary-color)\';"></a>';
} else {
$html .= '<span style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--movie-button-bg); color:var(--movie-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:var(--movie-primary-color); 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(--movie-button-bg); color:var(--movie-primary-color); text-decoration:none; font-size:14px; font-weight:600; transition:all 0.2s ease;"
onmouseover="this.style.background=\'var(--movie-accent-color)\'; this.style.color=\'white\';"
onmouseout="this.style.background=\'var(--movie-button-bg)\'; this.style.color=\'var(--movie-primary-color)\';">' . $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(--movie-button-bg); color:var(--movie-primary-color); text-decoration:none; font-size:14px; font-weight:600; transition:all 0.2s ease;"
onmouseover="this.style.background=\'var(--movie-primary-color)\'; this.style.color=\'white\';"
onmouseout="this.style.background=\'var(--movie-button-bg)\'; this.style.color=\'var(--movie-primary-color)\';"></a>';
} else {
$html .= '<span style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--movie-button-bg); color:var(--movie-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(--movie-button-bg); color:var(--movie-primary-color); text-decoration:none; font-size:14px; font-weight:600; transition:all 0.2s ease;"
onmouseover="this.style.background=\'var(--movie-primary-color)\'; this.style.color=\'white\';"
onmouseout="this.style.background=\'var(--movie-button-bg)\'; this.style.color=\'var(--movie-primary-color)\';">»</a>';
} else {
$html .= '<span style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--movie-button-bg); color:var(--movie-text-tertiary); font-size:14px; font-weight:600;">»</span>';
}
$html .= '</div></div>';
return $html;
}
/**
* 获取页面URL
*/
private static function getPageUrl($page, $baseUrl = '')
{
if (empty($baseUrl)) {
$currentUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
$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;
}
/**
* 获取序号颜色
*/
private static function getIndexColor($index)
{
$colors = [
'#01d277, #01b36b', // TMDB绿色
'#0073aa, #0056b3', // 蓝色
'#e67e22, #d35400', // 橙色
'#9b59b6, #8e44ad', // 紫色
'#e74c3c, #c0392b', // 红色
];
return $colors[($index - 1) % count($colors)];
}
/**
* 获取电影数据(包含影评和自定义信息)
*/
private static function getMovieData($movieId, $review = '', $customData = array())
{
if (!is_numeric($movieId)) return null;
$cacheFile = dirname(__FILE__) . '/cache/' . $movieId . '.json';
$options = Typecho_Widget::widget('Widget_Options')->plugin('MovieInfo');
$cacheEnable = isset($options->cacheEnable) ? $options->cacheEnable : '1';
$cacheTime = isset($options->cacheTime) ? intval($options->cacheTime) : 7;
$apiKey = isset($options->tmdbApiKey) ? $options->tmdbApiKey : '';
$language = isset($options->language) ? $options->language : 'zh-CN';
if (empty($apiKey)) {
return array(
'title' => '请配置TMDB API Key',
'original_title' => 'Please configure TMDB API Key',
'error' => 'API_KEY_MISSING'
);
}
$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);
}
}
}
// 如果缓存不存在或已过期从TMDB API获取
if (!$data || empty($data['title'])) {
$data = self::fetchFromTMDB($movieId, $apiKey, $language);
$needUpdate = true;
}
// 更新影评
if (!empty($review) && (!isset($data['review']) || $data['review'] !== $review)) {
$data['review'] = $review;
$data['review_updated'] = time();
$needUpdate = true;
}
// 更新自定义数据
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['watchDate']) && (!isset($data['custom_watch_date']) || $data['custom_watch_date'] !== $customData['watchDate'])) {
$data['custom_watch_date'] = $customData['watchDate'];
$needUpdate = true;
}
if (isset($customData['watchMethod']) && (!isset($data['custom_watch_method']) || $data['custom_watch_method'] !== $customData['watchMethod'])) {
$data['custom_watch_method'] = $customData['watchMethod'];
$needUpdate = true;
}
if (isset($customData['movieCategory']) && (!isset($data['custom_movie_category']) || $data['custom_movie_category'] !== $customData['movieCategory'])) {
$data['custom_movie_category'] = $customData['movieCategory'];
$needUpdate = true;
}
if (isset($customData['recommendation']) && (!isset($data['custom_recommendation']) || $data['custom_recommendation'] != $customData['recommendation'])) {
$data['custom_recommendation'] = intval($customData['recommendation']);
$needUpdate = true;
}
if (isset($customData['myRating']) && (!isset($data['custom_my_rating']) || $data['custom_my_rating'] !== $customData['myRating'])) {
$data['custom_my_rating'] = $customData['myRating'];
$needUpdate = true;
}
}
// 确保自定义字段存在
$customFields = array(
'custom_start_date' => '',
'custom_watch_date' => '',
'custom_watch_method' => '',
'custom_movie_category' => '',
'custom_recommendation' => 0,
'custom_my_rating' => ''
);
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;
}
/**
* 从TMDB API获取数据
*/
private static function fetchFromTMDB($movieId, $apiKey, $language)
{
// TMDB API URL
$url = "https://api.themoviedb.org/3/movie/{$movieId}?api_key={$apiKey}&language={$language}&append_to_response=credits";
$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_HTTPHEADER => array(
'Accept: application/json',
'Cache-Control: no-cache'
)
));
$response = curl_exec($ch);
if (curl_errno($ch)) {
curl_close($ch);
return null;
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode != 200 || empty($response)) {
return null;
}
$movieData = json_decode($response, true);
if (!$movieData) {
return null;
}
$data = array();
// 1. 基本信息
$data['title'] = isset($movieData['title']) ? trim($movieData['title']) : '未知电影';
$data['original_title'] = isset($movieData['original_title']) ? trim($movieData['original_title']) : '';
// 2. 海报
$data['poster_path'] = isset($movieData['poster_path']) ? $movieData['poster_path'] : '';
// 3. 简介
$data['overview'] = isset($movieData['overview']) ? trim($movieData['overview']) : '暂无简介';
// 4. 上映年份
if (isset($movieData['release_date']) && !empty($movieData['release_date'])) {
$releaseDate = $movieData['release_date'];
$data['release_year'] = substr($releaseDate, 0, 4);
} else {
$data['release_year'] = '未知';
}
// 5. 导演
$director = '未知';
if (isset($movieData['credits']['crew'])) {
foreach ($movieData['credits']['crew'] as $crew) {
if (isset($crew['job']) && $crew['job'] === 'Director') {
$director = $crew['name'];
break;
}
}
}
$data['director'] = $director;
// 6. 类型
$genres = array();
if (isset($movieData['genres'])) {
foreach ($movieData['genres'] as $genre) {
if (isset($genre['name'])) {
$genres[] = $genre['name'];
}
}
}
$data['genres'] = !empty($genres) ? $genres : array('未知');
// 7. 时长
$data['runtime'] = isset($movieData['runtime']) ? $movieData['runtime'] : 0;
// 8. 评分
$data['vote_average'] = isset($movieData['vote_average']) ? floatval($movieData['vote_average']) : 0;
$data['vote_count'] = isset($movieData['vote_count']) ? intval($movieData['vote_count']) : 0;
// 9. 生产国家
$countries = array();
if (isset($movieData['production_countries'])) {
foreach ($movieData['production_countries'] as $country) {
if (isset($country['name'])) {
$countries[] = $country['name'];
}
}
}
$data['production_countries'] = !empty($countries) ? $countries : array('未知');
// 10. 初始化影评和自定义字段
$data['review'] = '';
$data['review_updated'] = 0;
// 初始化自定义字段
$data['custom_start_date'] = '';
$data['custom_watch_date'] = '';
$data['custom_watch_method'] = '';
$data['custom_movie_category'] = '';
$data['custom_recommendation'] = 0;
$data['custom_my_rating'] = '';
// 11. 添加抓取时间
$data['fetched_at'] = time();
return $data;
}
/**
* 渲染编辑器按钮 - 增加关键词联想搜索
*/
public static function renderButton()
{
// 获取插件配置
$options = Typecho_Widget::widget('Widget_Options')->plugin('MovieInfo');
$apiKey = isset($options->tmdbApiKey) ? $options->tmdbApiKey : '';
$language = isset($options->language) ? $options->language : 'zh-CN';
// 生成nonce或token用于验证请求
$nonce = md5(uniqid());
echo <<<HTML
<style>
#movieinfo-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;
}
#movieinfo-button:hover { background: #E9E9E6 }
.dark #movieinfo-button { background: rgb(16, 25, 40); }
.dark #movieinfo-button:hover { background: #375d85; }
/* 搜索建议下拉框样式 */
.movie-search-suggestions {
position: absolute;
background: white;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
max-height: 300px;
overflow-y: auto;
z-index: 10001;
width: 300px;
margin-top: 2px;
}
.movie-search-suggestion-item {
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid #f0f0f0;
font-size: 14px;
color: #333;
display: flex;
align-items: center;
gap: 10px;
}
.movie-search-suggestion-item:hover {
background: #f5f5f5;
}
.movie-search-suggestion-item.selected {
background: #0073aa;
color: white;
}
.movie-search-suggestion-poster {
width: 30px;
height: 45px;
object-fit: cover;
border-radius: 3px;
flex-shrink: 0;
}
.movie-search-suggestion-info {
flex-grow: 1;
overflow: hidden;
}
.movie-search-suggestion-title {
font-weight: 600;
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.movie-search-suggestion-year {
font-size: 12px;
color: #666;
}
.movie-search-suggestion-item:hover .movie-search-suggestion-year {
color: #ccc;
}
.movie-search-suggestion-item.selected .movie-search-suggestion-year {
color: #ccc;
}
.movie-search-loading {
padding: 10px;
text-align: center;
color: #666;
font-size: 14px;
}
.movie-search-no-results {
padding: 10px;
text-align: center;
color: #999;
font-size: 14px;
}
.movie-search-input-container {
position: relative;
margin-bottom: 10px;
}
.movie-search-results {
position: relative;
}
.movie-id-section {
margin-top: 15px;
margin-bottom: 10px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.dark .movie-search-suggestions {
background: #2d2d2d;
border-color: #404040;
color: #e0e0e0;
}
.dark .movie-search-suggestion-item {
color: #e0e0e0;
border-bottom-color: #404040;
}
.dark .movie-search-suggestion-item:hover {
background: #3a3a3a;
}
.dark .movie-search-suggestion-item.selected {
background: #0073aa;
}
.dark .movie-search-suggestion-year {
color: #aaa;
}
.dark .movie-search-loading,
.dark .movie-search-no-results {
color: #aaa;
}
</style>
<script>
(function() {
function initMovieButton() {
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(initMovieButton, 500);
return;
}
if (document.getElementById('movieinfo-button')) return;
var button = document.createElement('button');
button.type = 'button';
button.id = 'movieinfo-button';
button.className = 'btn btn-s';
button.innerHTML = '🎬';
button.style.cssText = 'padding:6px 12px;';
toolbar.appendChild(button);
button.addEventListener('click', function() {
showMovieDialog();
});
}
// TMDB搜索函数
function searchMovies(keyword, callback) {
if (!keyword || keyword.length < 2) {
callback([]);
return;
}
// 使用插件配置的API Key和语言
var apiKey = '{$apiKey}';
var language = '{$language}';
if (!apiKey) {
alert('请先在插件设置中配置TMDB API Key');
return;
}
// 发送AJAX请求
var xhr = new XMLHttpRequest();
var url = 'https://api.themoviedb.org/3/search/movie?api_key=' + encodeURIComponent(apiKey) +
'&language=' + encodeURIComponent(language) +
'&query=' + encodeURIComponent(keyword) +
'&page=1&include_adult=false';
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
try {
var response = JSON.parse(xhr.responseText);
if (response.results && response.results.length > 0) {
callback(response.results.slice(0, 10)); // 只返回前10个结果
} else {
callback([]);
}
} catch (e) {
console.error('解析JSON失败:', e);
callback([]);
}
} else {
console.error('搜索失败:', xhr.status);
callback([]);
}
}
};
xhr.onerror = function() {
callback([]);
};
xhr.send();
}
// 显示搜索建议
function showSearchSuggestions(keyword, suggestions, container, inputField) {
// 移除现有的建议框
var existing = document.getElementById('movie-search-suggestions');
if (existing) {
existing.remove();
}
if (!suggestions || suggestions.length === 0) {
return;
}
// 创建建议框
var suggestionsDiv = document.createElement('div');
suggestionsDiv.id = 'movie-search-suggestions';
suggestionsDiv.className = 'movie-search-suggestions';
suggestions.forEach(function(movie, index) {
var item = document.createElement('div');
item.className = 'movie-search-suggestion-item';
item.dataset.movieId = movie.id;
item.dataset.movieTitle = movie.title;
item.dataset.movieYear = movie.release_date ? movie.release_date.substring(0, 4) : '未知';
// 海报URL
var posterUrl = movie.poster_path ?
'https://image.tmdb.org/t/p/w92' + movie.poster_path : '';
item.innerHTML =
(posterUrl ?
'<img src="' + posterUrl + '" class="movie-search-suggestion-poster" alt="' + movie.title + '" onerror="this.style.display=\'none\'">' :
'<div class="movie-search-suggestion-poster" style="background:#f0f0f0; display:flex; align-items:center; justify-content:center; color:#999; font-size:12px;">无</div>') +
'<div class="movie-search-suggestion-info">' +
'<div class="movie-search-suggestion-title">' + movie.title + '</div>' +
'<div class="movie-search-suggestion-year">' +
(movie.release_date ? movie.release_date.substring(0, 4) : '未知') +
(movie.vote_average ? ' · ⭐ ' + movie.vote_average.toFixed(1) : '') +
'</div>' +
'</div>';
// 点击选择
item.addEventListener('click', function() {
var movieId = this.dataset.movieId;
var movieTitle = this.dataset.movieTitle;
// 设置ID输入框的值
inputField.value = movieId;
// 在输入框下方显示选择提示
var selectionInfo = document.getElementById('movie-search-selection-info');
if (!selectionInfo) {
selectionInfo = document.createElement('div');
selectionInfo.id = 'movie-search-selection-info';
selectionInfo.style.cssText = 'margin-top:5px; font-size:12px; color:#0073aa;';
container.parentNode.insertBefore(selectionInfo, container.nextSibling);
}
selectionInfo.innerHTML = '已选择: <strong>' + movieTitle + '</strong> (ID: ' + movieId + ')';
// 移除建议框
suggestionsDiv.remove();
// 触发输入事件以更新预览
var event = new Event('input', { bubbles: true });
inputField.dispatchEvent(event);
// 聚焦到影评输入框
var reviewInput = document.getElementById('movieinfo-review');
if (reviewInput) {
setTimeout(function() {
reviewInput.focus();
}, 100);
}
});
// 鼠标悬停效果
item.addEventListener('mouseenter', function() {
this.classList.add('selected');
});
item.addEventListener('mouseleave', function() {
this.classList.remove('selected');
});
suggestionsDiv.appendChild(item);
});
// 添加到页面
container.appendChild(suggestionsDiv);
// 点击页面其他地方关闭建议框
setTimeout(function() {
document.addEventListener('click', function closeSuggestions(e) {
if (!container.contains(e.target) && !suggestionsDiv.contains(e.target)) {
suggestionsDiv.remove();
document.removeEventListener('click', closeSuggestions);
}
});
}, 10);
}
function showMovieDialog() {
// 创建遮罩层
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;">插入TMDB电影</h3>-->' +
'<div class="movie-search-input-container" style="margin-bottom:10px;margin-top:-5px;">' +
'<label style="display:block; margin-bottom:5px;color:#000; font-weight:600;">🔍 搜索电影</label>' +
'<input type="text" id="movieinfo-search" placeholder="输入电影名关键词搜索至少2个字符..." 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;">输入电影名称从下拉菜单中选择后自动填写ID</div>' +
'<div class="movie-search-results" style="position:relative;"></div>' +
'</div>' +
'<div class="movie-id-section">' +
'<label style="display:block; margin-bottom:5px;color:#000; font-weight:600;">🎬 TMDB电影ID</label>' +
'<input type="text" id="movieinfo-id" placeholder="自动从搜索结果填充也可手动输入ID" 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;">从搜索结果中选择或手动输入TMDB电影ID数字</div>' +
'</div>' +
'<div style="margin-bottom:15px;">' +
'<label style="display:block;color:#000; margin-bottom:5px; font-weight:600;">💭 我的影评</label>' +
'<textarea id="movieinfo-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="movieinfo-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="movieinfo-watch-date" placeholder="例如2024-03-15同一天或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="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="movieinfo-watch-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="movieinfo-movie-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-top:12px;">' +
'<label style="display:block; margin-bottom:5px; font-size:13px;color:#666; ">我的评分</label>' +
'<input type="text" id="movieinfo-my-rating" placeholder="例如8.5/10" style="width:100%; color:#666;padding:6px; border:1px solid #ddd; border-radius:4px; font-size:13px; box-sizing:border-box;background-Color:#fff;">' +
'</div>' +
'<div style="margin-top:12px;">' +
'<label style="display:block; margin-bottom:5px; font-size:13px;color:#666; ">推荐指数</label>' +
'<select id="movieinfo-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>' +
'<div style="margin-top:15px;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="movieinfo-preview" style="color:#333; word-break:break-all; font-size:13px; line-height:1.4;">[movie:ID:影评]</div>' +
'</div>' +
'<div style="margin-top:8px; color:#666;">说明自定义信息将自动保存到JSON缓存中</div>' +
'</div>' +
'<div style="text-align:right;">' +
'<button type="button" id="movieinfo-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="movieinfo-insert" style="padding:8px 20px; background:#01d277; 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 searchInput = document.getElementById('movieinfo-search');
var resultsContainer = document.querySelector('.movie-search-results');
var movieIdInput = document.getElementById('movieinfo-id');
var reviewInput = document.getElementById('movieinfo-review');
var startDateInput = document.getElementById('movieinfo-start-date');
var watchDateInput = document.getElementById('movieinfo-watch-date');
var watchMethodInput = document.getElementById('movieinfo-watch-method');
var movieCategoryInput = document.getElementById('movieinfo-movie-category');
var recommendationSelect = document.getElementById('movieinfo-recommendation');
var myRatingInput = document.getElementById('movieinfo-my-rating');
var previewDiv = document.getElementById('movieinfo-preview');
var insertBtn = document.getElementById('movieinfo-insert');
var cancelBtn = document.getElementById('movieinfo-cancel');
// 搜索输入防抖处理
var searchTimeout;
searchInput.addEventListener('input', function() {
clearTimeout(searchTimeout);
var keyword = this.value.trim();
if (keyword.length < 2) {
// 移除现有的建议框
var existing = document.getElementById('movie-search-suggestions');
if (existing) existing.remove();
return;
}
// 显示加载中
var existing = document.getElementById('movie-search-suggestions');
if (existing) existing.remove();
var loadingDiv = document.createElement('div');
loadingDiv.id = 'movie-search-suggestions';
loadingDiv.className = 'movie-search-suggestions';
loadingDiv.innerHTML = '<div class="movie-search-loading">搜索中...</div>';
resultsContainer.appendChild(loadingDiv);
searchTimeout = setTimeout(function() {
searchMovies(keyword, function(suggestions) {
// 移除加载中
var loading = document.getElementById('movie-search-suggestions');
if (loading) loading.remove();
if (suggestions.length === 0) {
var noResultsDiv = document.createElement('div');
noResultsDiv.id = 'movie-search-suggestions';
noResultsDiv.className = 'movie-search-suggestions';
noResultsDiv.innerHTML = '<div class="movie-search-no-results">未找到相关电影,请尝试其他关键词</div>';
resultsContainer.appendChild(noResultsDiv);
} else {
showSearchSuggestions(keyword, suggestions, resultsContainer, movieIdInput);
}
});
}, 300); // 300ms防抖延迟
});
// 搜索输入框键盘导航
var selectedIndex = -1;
searchInput.addEventListener('keydown', function(e) {
var suggestionsDiv = document.getElementById('movie-search-suggestions');
if (!suggestionsDiv) return;
var items = suggestionsDiv.querySelectorAll('.movie-search-suggestion-item');
if (items.length === 0) return;
if (e.key === 'ArrowDown') {
e.preventDefault();
selectedIndex = (selectedIndex + 1) % items.length;
updateSelection(items);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
updateSelection(items);
} else if (e.key === 'Enter') {
e.preventDefault();
if (selectedIndex >= 0 && selectedIndex < items.length) {
items[selectedIndex].click();
}
}
});
function updateSelection(items) {
items.forEach(function(item, index) {
if (index === selectedIndex) {
item.classList.add('selected');
item.scrollIntoView({ block: 'nearest' });
} else {
item.classList.remove('selected');
}
});
}
// 更新预览
function updatePreview() {
var movieId = movieIdInput.value.trim();
var review = reviewInput.value.trim();
var startDate = startDateInput.value.trim();
var watchDate = watchDateInput.value.trim();
var watchMethod = watchMethodInput.value.trim();
var movieCategory = movieCategoryInput.value.trim();
var recommendation = parseInt(recommendationSelect.value) || 0;
var myRating = myRatingInput.value.trim();
var preview = '[movie:' + (movieId || 'ID');
// 构建自定义数据对象
var customData = {};
if (startDate) customData.startDate = startDate;
if (watchDate) customData.watchDate = watchDate;
if (watchMethod) customData.watchMethod = watchMethod;
if (movieCategory) customData.movieCategory = movieCategory;
if (recommendation > 0) customData.recommendation = recommendation;
if (myRating) customData.myRating = myRating;
// 显示预览(中文显示)
var displayText = '';
if (review) {
displayText = review;
}
// 如果有自定义数据,添加到显示文本
if (Object.keys(customData).length > 0) {
var customText = '(自定义:';
var customParts = [];
if (startDate) customParts.push('开始观看:' + startDate);
if (watchDate) customParts.push('结束观看:' + watchDate);
if (watchMethod) customParts.push('观看方式:' + watchMethod);
if (movieCategory) customParts.push('电影分类:' + movieCategory);
if (myRating) customParts.push('我的评分:' + myRating);
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;
}
// 绑定输入事件
movieIdInput.addEventListener('input', updatePreview);
reviewInput.addEventListener('input', updatePreview);
startDateInput.addEventListener('input', updatePreview);
watchDateInput.addEventListener('input', updatePreview);
watchMethodInput.addEventListener('input', updatePreview);
movieCategoryInput.addEventListener('input', updatePreview);
recommendationSelect.addEventListener('change', updatePreview);
myRatingInput.addEventListener('input', updatePreview);
// 聚焦搜索框
setTimeout(function() {
searchInput.focus();
}, 100);
// 插入按钮点击
insertBtn.addEventListener('click', function() {
var movieId = movieIdInput.value.trim();
var review = reviewInput.value.trim();
var startDate = startDateInput.value.trim();
var watchDate = watchDateInput.value.trim();
var watchMethod = watchMethodInput.value.trim();
var movieCategory = movieCategoryInput.value.trim();
var recommendation = parseInt(recommendationSelect.value) || 0;
var myRating = myRatingInput.value.trim();
if (movieId && /^\d+$/.test(movieId)) {
// 构建自定义数据对象
var customData = {};
if (startDate) customData.startDate = startDate;
if (watchDate) customData.watchDate = watchDate;
if (watchMethod) customData.watchMethod = watchMethod;
if (movieCategory) customData.movieCategory = movieCategory;
if (recommendation > 0) customData.recommendation = recommendation;
if (myRating) customData.myRating = myRating;
// 构建短代码
var shortcode = '[movie:' + movieId;
// 如果有影评或自定义数据
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 = '[movie:' + movieId;
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 (watchDate) customParts.push('结束观看:' + watchDate);
if (watchMethod) customParts.push('观看方式:' + watchMethod);
if (movieCategory) customParts.push('电影分类:' + movieCategory);
if (myRating) customParts.push('我的评分:' + myRating);
if (recommendation > 0) customParts.push('推荐指数:' + '★'.repeat(recommendation));
displayCustomText += customParts.join('') + '';
if (displayContent) {
displayContent += displayCustomText;
} else {
displayContent = displayCustomText;
}
}
displayShortcode += ':' + displayContent;
}
displayShortcode += ']';
insertMovieShortcode(displayShortcode);
closeDialog();
} else if (movieId) {
alert('请输入有效的电影ID纯数字');
} else {
alert('请输入TMDB电影ID');
}
});
// 取消按钮点击
cancelBtn.addEventListener('click', closeDialog);
// 点击遮罩层关闭
overlay.addEventListener('click', closeDialog);
// ID输入框键盘事件
movieIdInput.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 insertMovieShortcode(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', initMovieButton);
} else {
initMovieButton();
}
})();
</script>
HTML;
}
}