TV / js /douban.js
samlax12's picture
Upload 30 files
7eff83b verified
// 豆瓣热门电影电视剧推荐功能
// 豆瓣标签列表
const doubanTags = ['热门', '最新', '经典', '豆瓣高分','古装剧', '冷门佳片', '科幻' ,'喜剧', '综艺', '欧美', '电视剧', '韩国', '日本', '动漫','动画片'];
let doubanCurrentTag = localStorage.getItem('doubanCurrentTag') || '热门';
let doubanPageStart = 0;
const doubanPageSize = 16; // 一次显示的项目数量
// 初始化豆瓣功能
function initDouban() {
// 设置豆瓣开关的初始状态
const doubanToggle = document.getElementById('doubanToggle');
if (doubanToggle) {
const isEnabled = localStorage.getItem('doubanEnabled') === 'true';
doubanToggle.checked = isEnabled;
// 设置开关外观
const toggleBg = doubanToggle.nextElementSibling;
const toggleDot = toggleBg.nextElementSibling;
if (isEnabled) {
toggleBg.classList.add('bg-pink-600');
toggleDot.classList.add('translate-x-6');
}
// 添加事件监听
doubanToggle.addEventListener('change', function(e) {
const isChecked = e.target.checked;
localStorage.setItem('doubanEnabled', isChecked);
// 更新开关外观
if (isChecked) {
toggleBg.classList.add('bg-pink-600');
toggleDot.classList.add('translate-x-6');
} else {
toggleBg.classList.remove('bg-pink-600');
toggleDot.classList.remove('translate-x-6');
}
// 更新显示状态
updateDoubanVisibility();
});
// 初始更新显示状态
updateDoubanVisibility();
}
// 渲染豆瓣标签
renderDoubanTags();
// 换一批按钮事件监听
setupDoubanRefreshBtn();
// 初始加载热门内容
if (localStorage.getItem('doubanEnabled') === 'true') {
renderRecommend(doubanCurrentTag, doubanPageSize, doubanPageStart);
}
}
// 根据设置更新豆瓣区域的显示状态
function updateDoubanVisibility() {
const doubanArea = document.getElementById('doubanArea');
if (!doubanArea) return;
const isEnabled = localStorage.getItem('doubanEnabled') === 'true';
const isSearching = document.getElementById('resultsArea') &&
!document.getElementById('resultsArea').classList.contains('hidden');
// 只有在启用且没有搜索结果显示时才显示豆瓣区域
if (isEnabled && !isSearching) {
doubanArea.classList.remove('hidden');
// 如果豆瓣结果为空,重新加载
if (document.getElementById('douban-results').children.length === 0) {
renderRecommend(doubanCurrentTag, doubanPageSize, doubanPageStart);
}
} else {
doubanArea.classList.add('hidden');
}
}
// 只填充搜索框,不执行搜索,让用户自主决定搜索时机
function fillSearchInput(title) {
if (!title) return;
// 安全处理标题,防止XSS
const safeTitle = title
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
const input = document.getElementById('searchInput');
if (input) {
input.value = safeTitle;
// 聚焦搜索框,便于用户立即使用键盘操作
input.focus();
// 显示一个提示,告知用户点击搜索按钮进行搜索
showToast('已填充搜索内容,点击搜索按钮开始搜索', 'info');
}
}
// 填充搜索框并执行搜索
function fillAndSearch(title) {
if (!title) return;
// 安全处理标题,防止XSS
const safeTitle = title
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
const input = document.getElementById('searchInput');
if (input) {
input.value = safeTitle;
search(); // 使用已有的search函数执行搜索
}
}
// 填充搜索框,确保豆瓣资源API被选中,然后执行搜索
function fillAndSearchWithDouban(title) {
if (!title) return;
// 安全处理标题,防止XSS
const safeTitle = title
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
// 确保豆瓣资源API被选中
if (typeof selectedAPIs !== 'undefined' && !selectedAPIs.includes('dbzy')) {
// 在设置中勾选豆瓣资源API复选框
const doubanCheckbox = document.querySelector('input[id="api_dbzy"]');
if (doubanCheckbox) {
doubanCheckbox.checked = true;
// 触发updateSelectedAPIs函数以更新状态
if (typeof updateSelectedAPIs === 'function') {
updateSelectedAPIs();
} else {
// 如果函数不可用,则手动添加到selectedAPIs
selectedAPIs.push('dbzy');
localStorage.setItem('selectedAPIs', JSON.stringify(selectedAPIs));
// 更新选中API计数(如果有这个元素)
const countEl = document.getElementById('selectedAPICount');
if (countEl) {
countEl.textContent = selectedAPIs.length;
}
}
showToast('已自动选择豆瓣资源API', 'info');
}
}
// 填充搜索框并执行搜索
const input = document.getElementById('searchInput');
if (input) {
input.value = safeTitle;
search(); // 使用已有的search函数执行搜索
}
}
// 渲染豆瓣标签选择器
function renderDoubanTags() {
const tagContainer = document.getElementById('douban-tags');
if (!tagContainer) return;
tagContainer.innerHTML = '';
doubanTags.forEach(tag => {
const btn = document.createElement('button');
// 更新标签样式:统一高度,添加过渡效果,改进颜色对比度
btn.className = 'py-1.5 px-3.5 rounded text-sm font-medium transition-all duration-300 ' +
(tag === doubanCurrentTag ?
'bg-pink-600 text-white shadow-md' :
'bg-[#1a1a1a] text-gray-300 hover:bg-pink-700 hover:text-white');
btn.textContent = tag;
btn.onclick = function() {
if (doubanCurrentTag !== tag) {
doubanCurrentTag = tag;
localStorage.setItem('doubanCurrentTag', tag);
doubanPageStart = 0;
renderRecommend(doubanCurrentTag, doubanPageSize, doubanPageStart);
renderDoubanTags();
}
};
tagContainer.appendChild(btn);
});
}
// 设置换一批按钮事件
function setupDoubanRefreshBtn() {
// 修复ID,使用正确的ID douban-refresh 而不是 douban-refresh-btn
const btn = document.getElementById('douban-refresh');
if (!btn) return;
btn.onclick = function() {
doubanPageStart += doubanPageSize;
if (doubanPageStart > 9 * doubanPageSize) {
doubanPageStart = 0;
}
renderRecommend(doubanCurrentTag, doubanPageSize, doubanPageStart);
};
}
// 渲染热门推荐内容
function renderRecommend(tag, pageLimit, pageStart) {
const container = document.getElementById("douban-results");
if (!container) return;
// 显示加载状态
container.innerHTML = `
<div class="col-span-full text-center py-10">
<div class="w-6 h-6 border-2 border-pink-500 border-t-transparent rounded-full animate-spin mr-2 inline-block"></div>
<span class="text-pink-500">加载中...</span>
</div>
`;
const target = `https://movie.douban.com/j/search_subjects?type=movie&tag=${tag}&sort=recommend&page_limit=${pageLimit}&page_start=${pageStart}`;
// 添加超时控制
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
// 设置请求选项,包括信号和头部
const fetchOptions = {
signal: controller.signal,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
'Referer': 'https://movie.douban.com/',
'Accept': 'application/json, text/plain, */*',
}
};
// 尝试直接访问(豆瓣API可能允许部分CORS请求)
fetch(PROXY_URL + encodeURIComponent(target), fetchOptions)
.then(response => {
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
renderDoubanCards(data, container);
})
.catch(err => {
console.error("豆瓣 API 请求失败(直接代理):", err);
// 失败后尝试备用方法:作为备选
const fallbackUrl = `https://api.allorigins.win/get?url=${encodeURIComponent(target)}`;
fetch(fallbackUrl)
.then(response => {
if (!response.ok) throw new Error(`备用API请求失败! 状态: ${response.status}`);
return response.json();
})
.then(data => {
// 解析原始内容
if (data && data.contents) {
const doubanData = JSON.parse(data.contents);
renderDoubanCards(doubanData, container);
} else {
throw new Error("无法获取有效数据");
}
})
.catch(fallbackErr => {
console.error("豆瓣 API 备用请求也失败:", fallbackErr);
container.innerHTML = `
<div class="col-span-full text-center py-8">
<div class="text-red-400">❌ 获取豆瓣数据失败,请稍后重试</div>
<div class="text-gray-500 text-sm mt-2">提示:使用VPN可能有助于解决此问题</div>
</div>
`;
});
});
}
// 抽取渲染豆瓣卡片的逻辑到单独函数
function renderDoubanCards(data, container) {
// 创建文档片段以提高性能
const fragment = document.createDocumentFragment();
// 如果没有数据
if (!data.subjects || data.subjects.length === 0) {
const emptyEl = document.createElement("div");
emptyEl.className = "col-span-full text-center py-8";
emptyEl.innerHTML = `
<div class="text-pink-500">❌ 暂无数据,请尝试其他分类或刷新</div>
`;
fragment.appendChild(emptyEl);
} else {
// 循环创建每个影视卡片
data.subjects.forEach(item => {
const card = document.createElement("div");
card.className = "bg-[#111] hover:bg-[#222] transition-all duration-300 rounded-lg overflow-hidden flex flex-col transform hover:scale-105 shadow-md hover:shadow-lg";
// 生成卡片内容,确保安全显示(防止XSS)
const safeTitle = item.title
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
const safeRate = (item.rate || "暂无")
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
// 处理图片URL
// 1. 直接使用豆瓣图片URL (添加no-referrer属性)
const originalCoverUrl = item.cover;
// 2. 也准备代理URL作为备选
const proxiedCoverUrl = PROXY_URL + encodeURIComponent(originalCoverUrl);
// 为不同设备优化卡片布局
card.innerHTML = `
<div class="relative w-full aspect-[2/3] overflow-hidden cursor-pointer" onclick="fillAndSearchWithDouban('${safeTitle}')">
<img src="${originalCoverUrl}" alt="${safeTitle}"
class="w-full h-full object-cover transition-transform duration-500 hover:scale-110"
onerror="this.onerror=null; this.src='${proxiedCoverUrl}'; this.classList.add('object-contain');"
loading="lazy" referrerpolicy="no-referrer">
<div class="absolute inset-0 bg-gradient-to-t from-black to-transparent opacity-60"></div>
<div class="absolute bottom-2 left-2 bg-black/70 text-white text-xs px-2 py-1 rounded-sm">
<span class="text-yellow-400">★</span> ${safeRate}
</div>
<div class="absolute bottom-2 right-2 bg-black/70 text-white text-xs px-2 py-1 rounded-sm hover:bg-[#333] transition-colors">
<a href="${item.url}" target="_blank" rel="noopener noreferrer" title="在豆瓣查看">
🔗
</a>
</div>
</div>
<div class="p-2 text-center bg-[#111]">
<button onclick="fillAndSearchWithDouban('${safeTitle}')"
class="text-sm font-medium text-white truncate w-full hover:text-pink-400 transition"
title="${safeTitle}">
${safeTitle}
</button>
</div>
`;
fragment.appendChild(card);
});
}
// 清空并添加所有新元素
container.innerHTML = "";
container.appendChild(fragment);
}
// 重置到首页
function resetToHome() {
resetSearchArea();
updateDoubanVisibility();
}
// 加载豆瓣首页内容
document.addEventListener('DOMContentLoaded', initDouban);