// 全局变量 let selectedAPIs = JSON.parse(localStorage.getItem('selectedAPIs') || '["tyyszy","dyttzy", "bfzy", "ruyi"]'); // 默认选中资源 let customAPIs = JSON.parse(localStorage.getItem('customAPIs') || '[]'); // 存储自定义API列表 // 添加当前播放的集数索引 let currentEpisodeIndex = 0; // 添加当前视频的所有集数 let currentEpisodes = []; // 添加当前视频的标题 let currentVideoTitle = ''; // 全局变量用于倒序状态 let episodesReversed = false; // 页面初始化 document.addEventListener('DOMContentLoaded', function () { // 初始化API复选框 initAPICheckboxes(); // 初始化自定义API列表 renderCustomAPIsList(); // 初始化显示选中的API数量 updateSelectedApiCount(); // 渲染搜索历史 renderSearchHistory(); // 设置默认API选择(如果是第一次加载) if (!localStorage.getItem('hasInitializedDefaults')) { // 默认选中资源 selectedAPIs = ["tyyszy", "bfzy", "dyttzy", "ruyi"]; localStorage.setItem('selectedAPIs', JSON.stringify(selectedAPIs)); // 默认选中过滤开关 localStorage.setItem('yellowFilterEnabled', 'true'); localStorage.setItem(PLAYER_CONFIG.adFilteringStorage, 'true'); // 默认启用豆瓣功能 localStorage.setItem('doubanEnabled', 'true'); // 标记已初始化默认值 localStorage.setItem('hasInitializedDefaults', 'true'); } // 设置黄色内容过滤器开关初始状态 const yellowFilterToggle = document.getElementById('yellowFilterToggle'); if (yellowFilterToggle) { yellowFilterToggle.checked = localStorage.getItem('yellowFilterEnabled') === 'true'; } // 设置广告过滤开关初始状态 const adFilterToggle = document.getElementById('adFilterToggle'); if (adFilterToggle) { adFilterToggle.checked = localStorage.getItem(PLAYER_CONFIG.adFilteringStorage) !== 'false'; // 默认为true } // 设置事件监听器 setupEventListeners(); // 初始检查成人API选中状态 setTimeout(checkAdultAPIsSelected, 100); }); // 初始化API复选框 function initAPICheckboxes() { const container = document.getElementById('apiCheckboxes'); container.innerHTML = ''; // 添加普通API组标题 const normaldiv = document.createElement('div'); normaldiv.id = 'normaldiv'; normaldiv.className = 'grid grid-cols-2 gap-2'; const normalTitle = document.createElement('div'); normalTitle.className = 'api-group-title'; normalTitle.textContent = '普通资源'; normaldiv.appendChild(normalTitle); // 创建普通API源的复选框 Object.keys(API_SITES).forEach(apiKey => { const api = API_SITES[apiKey]; if (api.adult) return; // 跳过成人内容API,稍后添加 const checked = selectedAPIs.includes(apiKey); const checkbox = document.createElement('div'); checkbox.className = 'flex items-center'; checkbox.innerHTML = ` `; normaldiv.appendChild(checkbox); // 添加事件监听器 checkbox.querySelector('input').addEventListener('change', function () { updateSelectedAPIs(); checkAdultAPIsSelected(); }); }); container.appendChild(normaldiv); // 添加成人API列表 addAdultAPI(); // 初始检查成人内容状态 checkAdultAPIsSelected(); } // 添加成人API列表 function addAdultAPI() { // 仅在隐藏设置为false时添加成人API组 if (!HIDE_BUILTIN_ADULT_APIS && (localStorage.getItem('yellowFilterEnabled') === 'false')) { const container = document.getElementById('apiCheckboxes'); // 添加成人API组标题 const adultdiv = document.createElement('div'); adultdiv.id = 'adultdiv'; adultdiv.className = 'grid grid-cols-2 gap-2'; const adultTitle = document.createElement('div'); adultTitle.className = 'api-group-title adult'; adultTitle.innerHTML = `黄色资源采集站 `; adultdiv.appendChild(adultTitle); // 创建成人API源的复选框 Object.keys(API_SITES).forEach(apiKey => { const api = API_SITES[apiKey]; if (!api.adult) return; // 仅添加成人内容API const checked = selectedAPIs.includes(apiKey); const checkbox = document.createElement('div'); checkbox.className = 'flex items-center'; checkbox.innerHTML = ` `; adultdiv.appendChild(checkbox); // 添加事件监听器 checkbox.querySelector('input').addEventListener('change', function () { updateSelectedAPIs(); checkAdultAPIsSelected(); }); }); container.appendChild(adultdiv); } } // 检查是否有成人API被选中 function checkAdultAPIsSelected() { // 查找所有内置成人API复选框 const adultBuiltinCheckboxes = document.querySelectorAll('#apiCheckboxes .api-adult:checked'); // 查找所有自定义成人API复选框 const customApiCheckboxes = document.querySelectorAll('#customApisList .api-adult:checked'); const hasAdultSelected = adultBuiltinCheckboxes.length > 0 || customApiCheckboxes.length > 0; const yellowFilterToggle = document.getElementById('yellowFilterToggle'); const yellowFilterContainer = yellowFilterToggle.closest('div').parentNode; const filterDescription = yellowFilterContainer.querySelector('p.filter-description'); // 如果选择了成人API,禁用黄色内容过滤器 if (hasAdultSelected) { yellowFilterToggle.checked = false; yellowFilterToggle.disabled = true; localStorage.setItem('yellowFilterEnabled', 'false'); // 添加禁用样式 yellowFilterContainer.classList.add('filter-disabled'); // 修改描述文字 if (filterDescription) { filterDescription.innerHTML = '选中黄色资源站时无法启用此过滤'; } // 移除提示信息(如果存在) const existingTooltip = yellowFilterContainer.querySelector('.filter-tooltip'); if (existingTooltip) { existingTooltip.remove(); } } else { // 启用黄色内容过滤器 yellowFilterToggle.disabled = false; yellowFilterContainer.classList.remove('filter-disabled'); // 恢复原来的描述文字 if (filterDescription) { filterDescription.innerHTML = '过滤"伦理片"等黄色内容'; } // 移除提示信息 const existingTooltip = yellowFilterContainer.querySelector('.filter-tooltip'); if (existingTooltip) { existingTooltip.remove(); } } } // 渲染自定义API列表 function renderCustomAPIsList() { const container = document.getElementById('customApisList'); if (!container) return; if (customAPIs.length === 0) { container.innerHTML = '

未添加自定义API

'; return; } container.innerHTML = ''; customAPIs.forEach((api, index) => { const apiItem = document.createElement('div'); apiItem.className = 'flex items-center justify-between p-1 mb-1 bg-[#222] rounded'; const textColorClass = api.isAdult ? 'text-pink-400' : 'text-white'; const adultTag = api.isAdult ? '(18+)' : ''; // 新增 detail 地址显示 const detailLine = api.detail ? `
detail: ${api.detail}
` : ''; apiItem.innerHTML = `
${adultTag}${api.name}
${api.url}
${detailLine}
`; container.appendChild(apiItem); apiItem.querySelector('input').addEventListener('change', function () { updateSelectedAPIs(); checkAdultAPIsSelected(); }); }); } // 编辑自定义API function editCustomApi(index) { if (index < 0 || index >= customAPIs.length) return; const api = customAPIs[index]; document.getElementById('customApiName').value = api.name; document.getElementById('customApiUrl').value = api.url; document.getElementById('customApiDetail').value = api.detail || ''; const isAdultInput = document.getElementById('customApiIsAdult'); if (isAdultInput) isAdultInput.checked = api.isAdult || false; const form = document.getElementById('addCustomApiForm'); if (form) { form.classList.remove('hidden'); const buttonContainer = form.querySelector('div:last-child'); buttonContainer.innerHTML = ` `; } } // 更新自定义API function updateCustomApi(index) { if (index < 0 || index >= customAPIs.length) return; const nameInput = document.getElementById('customApiName'); const urlInput = document.getElementById('customApiUrl'); const detailInput = document.getElementById('customApiDetail'); const isAdultInput = document.getElementById('customApiIsAdult'); const name = nameInput.value.trim(); let url = urlInput.value.trim(); const detail = detailInput ? detailInput.value.trim() : ''; const isAdult = isAdultInput ? isAdultInput.checked : false; if (!name || !url) { showToast('请输入API名称和链接', 'warning'); return; } if (!/^https?:\/\/.+/.test(url)) { showToast('API链接格式不正确,需以http://或https://开头', 'warning'); return; } if (url.endsWith('/')) url = url.slice(0, -1); // 保存 detail 字段 customAPIs[index] = { name, url, detail, isAdult }; localStorage.setItem('customAPIs', JSON.stringify(customAPIs)); renderCustomAPIsList(); checkAdultAPIsSelected(); restoreAddCustomApiButtons(); nameInput.value = ''; urlInput.value = ''; if (detailInput) detailInput.value = ''; if (isAdultInput) isAdultInput.checked = false; document.getElementById('addCustomApiForm').classList.add('hidden'); showToast('已更新自定义API: ' + name, 'success'); } // 取消编辑自定义API function cancelEditCustomApi() { // 清空表单 document.getElementById('customApiName').value = ''; document.getElementById('customApiUrl').value = ''; document.getElementById('customApiDetail').value = ''; const isAdultInput = document.getElementById('customApiIsAdult'); if (isAdultInput) isAdultInput.checked = false; // 隐藏表单 document.getElementById('addCustomApiForm').classList.add('hidden'); // 恢复添加按钮 restoreAddCustomApiButtons(); } // 恢复自定义API添加按钮 function restoreAddCustomApiButtons() { const form = document.getElementById('addCustomApiForm'); const buttonContainer = form.querySelector('div:last-child'); buttonContainer.innerHTML = ` `; } // 更新选中的API列表 function updateSelectedAPIs() { // 获取所有内置API复选框 const builtInApiCheckboxes = document.querySelectorAll('#apiCheckboxes input:checked'); // 获取选中的内置API const builtInApis = Array.from(builtInApiCheckboxes).map(input => input.dataset.api); // 获取选中的自定义API const customApiCheckboxes = document.querySelectorAll('#customApisList input:checked'); const customApiIndices = Array.from(customApiCheckboxes).map(input => 'custom_' + input.dataset.customIndex); // 合并内置和自定义API selectedAPIs = [...builtInApis, ...customApiIndices]; // 保存到localStorage localStorage.setItem('selectedAPIs', JSON.stringify(selectedAPIs)); // 更新显示选中的API数量 updateSelectedApiCount(); } // 更新选中的API数量显示 function updateSelectedApiCount() { const countEl = document.getElementById('selectedApiCount'); if (countEl) { countEl.textContent = selectedAPIs.length; } } // 全选或取消全选API function selectAllAPIs(selectAll = true, excludeAdult = false) { const checkboxes = document.querySelectorAll('#apiCheckboxes input[type="checkbox"]'); checkboxes.forEach(checkbox => { if (excludeAdult && checkbox.classList.contains('api-adult')) { checkbox.checked = false; } else { checkbox.checked = selectAll; } }); updateSelectedAPIs(); checkAdultAPIsSelected(); } // 显示添加自定义API表单 function showAddCustomApiForm() { const form = document.getElementById('addCustomApiForm'); if (form) { form.classList.remove('hidden'); } } // 取消添加自定义API - 修改函数来重用恢复按钮逻辑 function cancelAddCustomApi() { const form = document.getElementById('addCustomApiForm'); if (form) { form.classList.add('hidden'); document.getElementById('customApiName').value = ''; document.getElementById('customApiUrl').value = ''; document.getElementById('customApiDetail').value = ''; const isAdultInput = document.getElementById('customApiIsAdult'); if (isAdultInput) isAdultInput.checked = false; // 确保按钮是添加按钮 restoreAddCustomApiButtons(); } } // 添加自定义API function addCustomApi() { const nameInput = document.getElementById('customApiName'); const urlInput = document.getElementById('customApiUrl'); const detailInput = document.getElementById('customApiDetail'); const isAdultInput = document.getElementById('customApiIsAdult'); const name = nameInput.value.trim(); let url = urlInput.value.trim(); const detail = detailInput ? detailInput.value.trim() : ''; const isAdult = isAdultInput ? isAdultInput.checked : false; if (!name || !url) { showToast('请输入API名称和链接', 'warning'); return; } if (!/^https?:\/\/.+/.test(url)) { showToast('API链接格式不正确,需以http://或https://开头', 'warning'); return; } if (url.endsWith('/')) { url = url.slice(0, -1); } // 保存 detail 字段 customAPIs.push({ name, url, detail, isAdult }); localStorage.setItem('customAPIs', JSON.stringify(customAPIs)); const newApiIndex = customAPIs.length - 1; selectedAPIs.push('custom_' + newApiIndex); localStorage.setItem('selectedAPIs', JSON.stringify(selectedAPIs)); // 重新渲染自定义API列表 renderCustomAPIsList(); updateSelectedApiCount(); checkAdultAPIsSelected(); nameInput.value = ''; urlInput.value = ''; if (detailInput) detailInput.value = ''; if (isAdultInput) isAdultInput.checked = false; document.getElementById('addCustomApiForm').classList.add('hidden'); showToast('已添加自定义API: ' + name, 'success'); } // 移除自定义API function removeCustomApi(index) { if (index < 0 || index >= customAPIs.length) return; const apiName = customAPIs[index].name; // 从列表中移除API customAPIs.splice(index, 1); localStorage.setItem('customAPIs', JSON.stringify(customAPIs)); // 从选中列表中移除此API const customApiId = 'custom_' + index; selectedAPIs = selectedAPIs.filter(id => id !== customApiId); // 更新大于此索引的自定义API索引 selectedAPIs = selectedAPIs.map(id => { if (id.startsWith('custom_')) { const currentIndex = parseInt(id.replace('custom_', '')); if (currentIndex > index) { return 'custom_' + (currentIndex - 1); } } return id; }); localStorage.setItem('selectedAPIs', JSON.stringify(selectedAPIs)); // 重新渲染自定义API列表 renderCustomAPIsList(); // 更新选中的API数量 updateSelectedApiCount(); // 重新检查成人API选中状态 checkAdultAPIsSelected(); showToast('已移除自定义API: ' + apiName, 'info'); } function toggleSettings(e) { const settingsPanel = document.getElementById('settingsPanel'); if (!settingsPanel) return; if (settingsPanel.classList.contains('show')) { settingsPanel.classList.remove('show'); } else { settingsPanel.classList.add('show'); } if (e) { e.preventDefault(); e.stopPropagation(); } } // 设置事件监听器 function setupEventListeners() { // 回车搜索 document.getElementById('searchInput').addEventListener('keypress', function (e) { if (e.key === 'Enter') { search(); } }); // 点击外部关闭设置面板和历史记录面板 document.addEventListener('click', function (e) { // 关闭设置面板 const settingsPanel = document.querySelector('#settingsPanel.show'); const settingsButton = document.querySelector('#settingsPanel .close-btn'); if (settingsPanel && settingsButton && !settingsPanel.contains(e.target) && !settingsButton.contains(e.target)) { settingsPanel.classList.remove('show'); } // 关闭历史记录面板 const historyPanel = document.querySelector('#historyPanel.show'); const historyButton = document.querySelector('#historyPanel .close-btn'); if (historyPanel && historyButton && !historyPanel.contains(e.target) && !historyButton.contains(e.target)) { historyPanel.classList.remove('show'); } }); // 黄色内容过滤开关事件绑定 const yellowFilterToggle = document.getElementById('yellowFilterToggle'); if (yellowFilterToggle) { yellowFilterToggle.addEventListener('change', function (e) { localStorage.setItem('yellowFilterEnabled', e.target.checked); // 控制黄色内容接口的显示状态 const adultdiv = document.getElementById('adultdiv'); if (adultdiv) { if (e.target.checked === true) { adultdiv.style.display = 'none'; } else if (e.target.checked === false) { adultdiv.style.display = '' } } else { // 添加成人API列表 addAdultAPI(); } }); } // 广告过滤开关事件绑定 const adFilterToggle = document.getElementById('adFilterToggle'); if (adFilterToggle) { adFilterToggle.addEventListener('change', function (e) { localStorage.setItem(PLAYER_CONFIG.adFilteringStorage, e.target.checked); }); } } // 重置搜索区域 function resetSearchArea() { // 清理搜索结果 document.getElementById('results').innerHTML = ''; document.getElementById('searchInput').value = ''; // 恢复搜索区域的样式 document.getElementById('searchArea').classList.add('flex-1'); document.getElementById('searchArea').classList.remove('mb-8'); document.getElementById('resultsArea').classList.add('hidden'); // 确保页脚正确显示,移除相对定位 const footer = document.querySelector('.footer'); if (footer) { footer.style.position = ''; } // 如果有豆瓣功能,检查是否需要显示豆瓣推荐区域 if (typeof updateDoubanVisibility === 'function') { updateDoubanVisibility(); } // 重置URL为主页 try { window.history.pushState( {}, `LibreTV - 免费在线视频搜索与观看平台`, `/` ); // 更新页面标题 document.title = `LibreTV - 免费在线视频搜索与观看平台`; } catch (e) { console.error('更新浏览器历史失败:', e); } } // 获取自定义API信息 function getCustomApiInfo(customApiIndex) { const index = parseInt(customApiIndex); if (isNaN(index) || index < 0 || index >= customAPIs.length) { return null; } return customAPIs[index]; } // 搜索功能 - 修改为支持多选API和多页结果 async function search() { // 强化的密码保护校验 - 防止绕过 try { if (window.ensurePasswordProtection) { window.ensurePasswordProtection(); } else { // 兼容性检查 if (window.isPasswordProtected && window.isPasswordVerified) { if (window.isPasswordProtected() && !window.isPasswordVerified()) { showPasswordModal && showPasswordModal(); return; } } } } catch (error) { console.warn('Password protection check failed:', error.message); return; } const query = document.getElementById('searchInput').value.trim(); if (!query) { showToast('请输入搜索内容', 'info'); return; } if (selectedAPIs.length === 0) { showToast('请至少选择一个API源', 'warning'); return; } showLoading(); try { // 保存搜索历史 saveSearchHistory(query); // 从所有选中的API源搜索 let allResults = []; const searchPromises = selectedAPIs.map(apiId => searchByAPIAndKeyWord(apiId, query) ); // 等待所有搜索请求完成 const resultsArray = await Promise.all(searchPromises); // 合并所有结果 resultsArray.forEach(results => { if (Array.isArray(results) && results.length > 0) { allResults = allResults.concat(results); } }); // 对搜索结果进行排序:按名称优先,名称相同时按接口源排序 allResults.sort((a, b) => { // 首先按照视频名称排序 const nameCompare = (a.vod_name || '').localeCompare(b.vod_name || ''); if (nameCompare !== 0) return nameCompare; // 如果名称相同,则按照来源排序 return (a.source_name || '').localeCompare(b.source_name || ''); }); // 更新搜索结果计数 const searchResultsCount = document.getElementById('searchResultsCount'); if (searchResultsCount) { searchResultsCount.textContent = allResults.length; } // 显示结果区域,调整搜索区域 document.getElementById('searchArea').classList.remove('flex-1'); document.getElementById('searchArea').classList.add('mb-8'); document.getElementById('resultsArea').classList.remove('hidden'); // 隐藏豆瓣推荐区域(如果存在) const doubanArea = document.getElementById('doubanArea'); if (doubanArea) { doubanArea.classList.add('hidden'); } const resultsDiv = document.getElementById('results'); // 如果没有结果 if (!allResults || allResults.length === 0) { resultsDiv.innerHTML = `

没有找到匹配的结果

请尝试其他关键词或更换数据源

`; hideLoading(); return; } // 有搜索结果时,才更新URL try { // 使用URI编码确保特殊字符能够正确显示 const encodedQuery = encodeURIComponent(query); // 使用HTML5 History API更新URL,不刷新页面 window.history.pushState( { search: query }, `搜索: ${query} - LibreTV`, `/s=${encodedQuery}` ); // 更新页面标题 document.title = `搜索: ${query} - LibreTV`; } catch (e) { console.error('更新浏览器历史失败:', e); // 如果更新URL失败,继续执行搜索 } // 处理搜索结果过滤:如果启用了黄色内容过滤,则过滤掉分类含有敏感内容的项目 const yellowFilterEnabled = localStorage.getItem('yellowFilterEnabled') === 'true'; if (yellowFilterEnabled) { const banned = ['伦理片', '福利', '里番动漫', '门事件', '萝莉少女', '制服诱惑', '国产传媒', 'cosplay', '黑丝诱惑', '无码', '日本无码', '有码', '日本有码', 'SWAG', '网红主播', '色情片', '同性片', '福利视频', '福利片']; allResults = allResults.filter(item => { const typeName = item.type_name || ''; return !banned.some(keyword => typeName.includes(keyword)); }); } // 添加XSS保护,使用textContent和属性转义 const safeResults = allResults.map(item => { const safeId = item.vod_id ? item.vod_id.toString().replace(/[^\w-]/g, '') : ''; const safeName = (item.vod_name || '').toString() .replace(//g, '>') .replace(/"/g, '"'); const sourceInfo = item.source_name ? `${item.source_name}` : ''; const sourceCode = item.source_code || ''; // 添加API URL属性,用于详情获取 const apiUrlAttr = item.api_url ? `data-api-url="${item.api_url.replace(/"/g, '"')}"` : ''; // 修改为水平卡片布局,图片在左侧,文本在右侧,并优化样式 const hasCover = item.vod_pic && item.vod_pic.startsWith('http'); return `
${hasCover ? `
${safeName}
` : ''}

${safeName}

${(item.type_name || '').toString().replace(/ ${(item.type_name || '').toString().replace(/` : ''} ${(item.vod_year || '') ? ` ${item.vod_year} ` : ''}

${(item.vod_remarks || '暂无介绍').toString().replace(/

${sourceInfo ? `
${sourceInfo}
` : '
'}
`; }).join(''); resultsDiv.innerHTML = safeResults; } catch (error) { console.error('搜索错误:', error); if (error.name === 'AbortError') { showToast('搜索请求超时,请检查网络连接', 'error'); } else { showToast('搜索请求失败,请稍后重试', 'error'); } } finally { hideLoading(); } } // 切换清空按钮的显示状态 function toggleClearButton() { const searchInput = document.getElementById('searchInput'); const clearButton = document.getElementById('clearSearchInput'); if (searchInput.value !== '') { clearButton.classList.remove('hidden'); } else { clearButton.classList.add('hidden'); } } // 清空搜索框内容 function clearSearchInput() { const searchInput = document.getElementById('searchInput'); searchInput.value = ''; const clearButton = document.getElementById('clearSearchInput'); clearButton.classList.add('hidden'); } // 劫持搜索框的value属性以检测外部修改 function hookInput() { const input = document.getElementById('searchInput'); const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); // 重写 value 属性的 getter 和 setter Object.defineProperty(input, 'value', { get: function () { // 确保读取时返回字符串(即使原始值为 undefined/null) const originalValue = descriptor.get.call(this); return originalValue != null ? String(originalValue) : ''; }, set: function (value) { // 显式将值转换为字符串后写入 const strValue = String(value); descriptor.set.call(this, strValue); this.dispatchEvent(new Event('input', { bubbles: true })); } }); // 初始化输入框值为空字符串(避免初始值为 undefined) input.value = ''; } document.addEventListener('DOMContentLoaded', hookInput); // 显示详情 - 修改为支持自定义API async function showDetails(id, vod_name, sourceCode) { // 密码保护校验 if (window.isPasswordProtected && window.isPasswordVerified) { if (window.isPasswordProtected() && !window.isPasswordVerified()) { showPasswordModal && showPasswordModal(); return; } } if (!id) { showToast('视频ID无效', 'error'); return; } showLoading(); try { // 构建API参数 let apiParams = ''; // 处理自定义API源 if (sourceCode.startsWith('custom_')) { const customIndex = sourceCode.replace('custom_', ''); const customApi = getCustomApiInfo(customIndex); if (!customApi) { showToast('自定义API配置无效', 'error'); hideLoading(); return; } // 传递 detail 字段 if (customApi.detail) { apiParams = '&customApi=' + encodeURIComponent(customApi.url) + '&customDetail=' + encodeURIComponent(customApi.detail) + '&source=custom'; } else { apiParams = '&customApi=' + encodeURIComponent(customApi.url) + '&source=custom'; } } else { // 内置API apiParams = '&source=' + sourceCode; } // Add a timestamp to prevent caching const timestamp = new Date().getTime(); const cacheBuster = `&_t=${timestamp}`; const response = await fetch(`/api/detail?id=${encodeURIComponent(id)}${apiParams}${cacheBuster}`); const data = await response.json(); const modal = document.getElementById('modal'); const modalTitle = document.getElementById('modalTitle'); const modalContent = document.getElementById('modalContent'); // 显示来源信息 const sourceName = data.videoInfo && data.videoInfo.source_name ? ` (${data.videoInfo.source_name})` : ''; // 不对标题进行截断处理,允许完整显示 modalTitle.innerHTML = `${vod_name || '未知视频'}${sourceName}`; currentVideoTitle = vod_name || '未知视频'; if (data.episodes && data.episodes.length > 0) { // 构建详情信息HTML let detailInfoHtml = ''; if (data.videoInfo) { // Prepare description text, strip HTML and trim whitespace const descriptionText = data.videoInfo.desc ? data.videoInfo.desc.replace(/<[^>]+>/g, '').trim() : ''; // Check if there's any actual grid content const hasGridContent = data.videoInfo.type || data.videoInfo.year || data.videoInfo.area || data.videoInfo.director || data.videoInfo.actor || data.videoInfo.remarks; if (hasGridContent || descriptionText) { // Only build if there's something to show detailInfoHtml = ` `; } } currentEpisodes = data.episodes; currentEpisodeIndex = 0; modalContent.innerHTML = ` ${detailInfoHtml}
共 ${data.episodes.length} 集
${renderEpisodes(vod_name, sourceCode, id)}
`; } else { modalContent.innerHTML = `
❌ 未找到播放资源
该视频可能暂时无法播放,请尝试其他视频
`; } modal.classList.remove('hidden'); } catch (error) { console.error('获取详情错误:', error); showToast('获取详情失败,请稍后重试', 'error'); } finally { hideLoading(); } } // 更新播放视频函数,修改为使用/watch路径而不是直接打开player.html function playVideo(url, vod_name, sourceCode, episodeIndex = 0, vodId = '') { // 密码保护校验 if (window.isPasswordProtected && window.isPasswordVerified) { if (window.isPasswordProtected() && !window.isPasswordVerified()) { showPasswordModal && showPasswordModal(); return; } } // 获取当前路径作为返回页面 let currentPath = window.location.href; // 构建播放页面URL,使用watch.html作为中间跳转页 let watchUrl = `watch.html?id=${vodId || ''}&source=${sourceCode || ''}&url=${encodeURIComponent(url)}&index=${episodeIndex}&title=${encodeURIComponent(vod_name || '')}`; // 添加返回URL参数 if (currentPath.includes('index.html') || currentPath.endsWith('/')) { watchUrl += `&back=${encodeURIComponent(currentPath)}`; } // 保存当前状态到localStorage try { localStorage.setItem('currentVideoTitle', vod_name || '未知视频'); localStorage.setItem('currentEpisodes', JSON.stringify(currentEpisodes)); localStorage.setItem('currentEpisodeIndex', episodeIndex); localStorage.setItem('currentSourceCode', sourceCode || ''); localStorage.setItem('lastPlayTime', Date.now()); localStorage.setItem('lastSearchPage', currentPath); localStorage.setItem('lastPageUrl', currentPath); // 确保保存返回页面URL } catch (e) { console.error('保存播放状态失败:', e); } // 在当前标签页中打开播放页面 window.location.href = watchUrl; } // 弹出播放器页面 function showVideoPlayer(url) { // 在打开播放器前,隐藏详情弹窗 const detailModal = document.getElementById('modal'); if (detailModal) { detailModal.classList.add('hidden'); } // 临时隐藏搜索结果和豆瓣区域,防止高度超出播放器而出现滚动条 document.getElementById('resultsArea').classList.add('hidden'); document.getElementById('doubanArea').classList.add('hidden'); // 在框架中打开播放页面 videoPlayerFrame = document.createElement('iframe'); videoPlayerFrame.id = 'VideoPlayerFrame'; videoPlayerFrame.className = 'fixed w-full h-screen z-40'; videoPlayerFrame.src = url; document.body.appendChild(videoPlayerFrame); // 将焦点移入iframe videoPlayerFrame.focus(); } // 关闭播放器页面 function closeVideoPlayer(home = false) { videoPlayerFrame = document.getElementById('VideoPlayerFrame'); if (videoPlayerFrame) { videoPlayerFrame.remove(); // 恢复搜索结果显示 document.getElementById('resultsArea').classList.remove('hidden'); // 关闭播放器时也隐藏详情弹窗 const detailModal = document.getElementById('modal'); if (detailModal) { detailModal.classList.add('hidden'); } // 如果启用豆瓣区域则显示豆瓣区域 if (localStorage.getItem('doubanEnabled') === 'true') { document.getElementById('doubanArea').classList.remove('hidden'); } } if (home) { // 刷新主页 window.location.href = '/' } } // 播放上一集 function playPreviousEpisode(sourceCode) { if (currentEpisodeIndex > 0) { const prevIndex = currentEpisodeIndex - 1; const prevUrl = currentEpisodes[prevIndex]; playVideo(prevUrl, currentVideoTitle, sourceCode, prevIndex); } } // 播放下一集 function playNextEpisode(sourceCode) { if (currentEpisodeIndex < currentEpisodes.length - 1) { const nextIndex = currentEpisodeIndex + 1; const nextUrl = currentEpisodes[nextIndex]; playVideo(nextUrl, currentVideoTitle, sourceCode, nextIndex); } } // 处理播放器加载错误 function handlePlayerError() { hideLoading(); showToast('视频播放加载失败,请尝试其他视频源', 'error'); } // 辅助函数用于渲染剧集按钮(使用当前的排序状态) function renderEpisodes(vodName, sourceCode, vodId) { const episodes = episodesReversed ? [...currentEpisodes].reverse() : currentEpisodes; return episodes.map((episode, index) => { // 根据倒序状态计算真实的剧集索引 const realIndex = episodesReversed ? currentEpisodes.length - 1 - index : index; return ` `; }).join(''); } // 复制视频链接到剪贴板 function copyLinks() { const episodes = episodesReversed ? [...currentEpisodes].reverse() : currentEpisodes; const linkList = episodes.join('\r\n'); navigator.clipboard.writeText(linkList).then(() => { showToast('播放链接已复制', 'success'); }).catch(err => { showToast('复制失败,请检查浏览器权限', 'error'); }); } // 切换排序状态的函数 function toggleEpisodeOrder(sourceCode, vodId) { episodesReversed = !episodesReversed; // 重新渲染剧集区域,使用 currentVideoTitle 作为视频标题 const episodesGrid = document.getElementById('episodesGrid'); if (episodesGrid) { episodesGrid.innerHTML = renderEpisodes(currentVideoTitle, sourceCode, vodId); } // 更新按钮文本和箭头方向 const toggleBtn = document.querySelector(`button[onclick="toggleEpisodeOrder('${sourceCode}', '${vodId}')"]`); if (toggleBtn) { toggleBtn.querySelector('span').textContent = episodesReversed ? '正序排列' : '倒序排列'; const arrowIcon = toggleBtn.querySelector('svg'); if (arrowIcon) { arrowIcon.style.transform = episodesReversed ? 'rotate(180deg)' : 'rotate(0deg)'; } } } // 从URL导入配置 async function importConfigFromUrl() { // 创建模态框元素 let modal = document.getElementById('importUrlModal'); if (modal) { document.body.removeChild(modal); } modal = document.createElement('div'); modal.id = 'importUrlModal'; modal.className = 'fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-40'; modal.innerHTML = `

从URL导入配置

`; document.body.appendChild(modal); // 关闭按钮事件 document.getElementById('closeUrlModal').addEventListener('click', () => { document.body.removeChild(modal); }); // 取消按钮事件 document.getElementById('cancelUrlImport').addEventListener('click', () => { document.body.removeChild(modal); }); // 确认导入按钮事件 document.getElementById('confirmUrlImport').addEventListener('click', async () => { const url = document.getElementById('configUrl').value.trim(); if (!url) { showToast('请输入配置文件URL', 'warning'); return; } // 验证URL格式 try { const urlObj = new URL(url); if (urlObj.protocol !== 'http:' && urlObj.protocol !== 'https:') { showToast('URL必须以http://或https://开头', 'warning'); return; } } catch (e) { showToast('URL格式不正确', 'warning'); return; } showLoading('正在从URL导入配置...'); try { // 获取配置文件 - 直接请求URL const response = await fetch(url, { mode: 'cors', headers: { 'Accept': 'application/json' } }); if (!response.ok) throw '获取配置文件失败'; // 验证响应内容类型 const contentType = response.headers.get('content-type'); if (!contentType || !contentType.includes('application/json')) { throw '响应不是有效的JSON格式'; } const config = await response.json(); if (config.name !== 'LibreTV-Settings') throw '配置文件格式不正确'; // 验证哈希 const dataHash = await sha256(JSON.stringify(config.data)); if (dataHash !== config.hash) throw '配置文件哈希值不匹配'; // 导入配置 for (let item in config.data) { localStorage.setItem(item, config.data[item]); } showToast('配置文件导入成功,3 秒后自动刷新本页面。', 'success'); setTimeout(() => { window.location.reload(); }, 3000); } catch (error) { const message = typeof error === 'string' ? error : '导入配置失败'; showToast(`从URL导入配置出错 (${message})`, 'error'); } finally { hideLoading(); document.body.removeChild(modal); } }); // 点击模态框外部关闭 modal.addEventListener('click', (e) => { if (e.target === modal) { document.body.removeChild(modal); } }); } // 配置文件导入功能 async function importConfig() { showImportBox(async (file) => { try { // 检查文件类型 if (!(file.type === 'application/json' || file.name.endsWith('.json'))) throw '文件类型不正确'; // 检查文件大小 if (file.size > 1024 * 1024 * 10) throw new Error('文件大小超过 10MB'); // 读取文件内容 const content = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = () => reject('文件读取失败'); reader.readAsText(file); }); // 解析并验证配置 const config = JSON.parse(content); if (config.name !== 'LibreTV-Settings') throw '配置文件格式不正确'; // 验证哈希 const dataHash = await sha256(JSON.stringify(config.data)); if (dataHash !== config.hash) throw '配置文件哈希值不匹配'; // 导入配置 for (let item in config.data) { localStorage.setItem(item, config.data[item]); } showToast('配置文件导入成功,3 秒后自动刷新本页面。', 'success'); setTimeout(() => { window.location.reload(); }, 3000); } catch (error) { const message = typeof error === 'string' ? error : '配置文件格式错误'; showToast(`配置文件读取出错 (${message})`, 'error'); } }); } // 配置文件导出功能 async function exportConfig() { // 存储配置数据 const config = {}; const items = {}; const settingsToExport = [ 'selectedAPIs', 'customAPIs', 'yellowFilterEnabled', 'adFilteringEnabled', 'doubanEnabled', 'hasInitializedDefaults' ]; // 导出设置项 settingsToExport.forEach(key => { const value = localStorage.getItem(key); if (value !== null) { items[key] = value; } }); // 导出历史记录 const viewingHistory = localStorage.getItem('viewingHistory'); if (viewingHistory) { items['viewingHistory'] = viewingHistory; } const searchHistory = localStorage.getItem(SEARCH_HISTORY_KEY); if (searchHistory) { items[SEARCH_HISTORY_KEY] = searchHistory; } const times = Date.now().toString(); config['name'] = 'LibreTV-Settings'; // 配置文件名,用于校验 config['time'] = times; // 配置文件生成时间 config['cfgVer'] = '1.0.0'; // 配置文件版本 config['data'] = items; // 配置文件数据 config['hash'] = await sha256(JSON.stringify(config['data'])); // 计算数据的哈希值,用于校验 // 将配置数据保存为 JSON 文件 saveStringAsFile(JSON.stringify(config), 'LibreTV-Settings_' + times + '.json'); } // 将字符串保存为文件 function saveStringAsFile(content, fileName) { // 创建Blob对象并指定类型 const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); // 生成临时URL const url = window.URL.createObjectURL(blob); // 创建标签并触发下载 const a = document.createElement('a'); a.href = url; a.download = fileName; document.body.appendChild(a); a.click(); // 清理临时对象 document.body.removeChild(a); window.URL.revokeObjectURL(url); } // 移除Node.js的require语句,因为这是在浏览器环境中运行的