Files
MovieInfo/Plugin.php

2527 lines
105 KiB
PHP
Raw Permalink Normal View History

2026-02-23 17:32:57 +08:00
<?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;
}
}