From 246a1f76732dcc0af232ec65c001a84a6cb62dc9 Mon Sep 17 00:00:00 2001 From: levywang Date: Tue, 15 Apr 2025 18:31:29 +0800 Subject: [PATCH] feat: Added popular search recommendation function --- README.md | 2 +- README_CN.md | 2 +- main.py | 10 +- web/config.js | 13 + web/globals.js | 75 ++++ web/index.html | 10 +- web/script.js | 959 ++++++++++++++++++++++++++++--------------------- web/sw.js | 4 +- 8 files changed, 659 insertions(+), 416 deletions(-) create mode 100644 web/config.js create mode 100644 web/globals.js diff --git a/README.md b/README.md index c4096e3..2e31dee 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ python main.py ``` The default API address: `http://127.0.0.1:8000/` -You can configure a reverse proxy and domain, replacing `BASE_URL` in line 38 of `web/script.js`. +You can configure a reverse proxy and domain, replacing `BASE_URL` in line 3 of `web/config.js`. The backend configuration file is located in `data/config.yaml`. Modify it according to your actual needs. diff --git a/README_CN.md b/README_CN.md index c831402..fc63ce7 100644 --- a/README_CN.md +++ b/README_CN.md @@ -56,7 +56,7 @@ python main.py ``` 默认运行的API地址:`http://127.0.0.1:8000/` -可以配置反代和域名,替换 `web/script.js` 38行中的 `BASE_URL` +可以配置反代和域名,替换 `web/config.js` 3行中的 `BASE_URL` 后端运行的配置文件在 `data/config.yaml` 中,请根据实际情况修改 diff --git a/main.py b/main.py index 714f53e..661cc26 100644 --- a/main.py +++ b/main.py @@ -310,11 +310,15 @@ def main(cfg: DictConfig): if not search_terms: return {"status": "succeed", "data": []} - # 统计每个搜索词的出现次数并获取前N名 + # 统计每个搜索词的出现次数并获取指定范围的数据 term_counts = Counter(search_terms) - top_terms = [term for term, _ in term_counts.most_common(top_n)] + most_common_all = term_counts.most_common() # 获取全部排序结果 + start_index = top_n + end_index = start_index + top_n + selected_terms = most_common_all[start_index:end_index] # 切片获取指定范围 + top_terms = [term for term, _ in selected_terms] - logger.info(f"Retrieved top {top_n} popular search terms from last {last_n_lines} lines") + logger.info(f"Retrieved top {top_n*2} popular search terms from last {last_n_lines} lines") return {"status": "succeed", "data": top_terms} except asyncio.TimeoutError: logger.error("Timeout while reading log file") diff --git a/web/config.js b/web/config.js new file mode 100644 index 0000000..1dea2ab --- /dev/null +++ b/web/config.js @@ -0,0 +1,13 @@ +// config.js +const API_CONFIG = { + BASE_URL: '/api/v1', + ENDPOINTS: { + SEARCH: '/avcode', + COLLECTIONS: '/hacg', + VIDEO: '/get_video', + HOT_SEARCHES: '/hot_searches' + } +}; + +// 导出配置 +export default API_CONFIG; diff --git a/web/globals.js b/web/globals.js new file mode 100644 index 0000000..e42a37e --- /dev/null +++ b/web/globals.js @@ -0,0 +1,75 @@ +// globals.js - 包含所有需要在HTML中直接调用的全局函数 + +// 在此处存储全局状态 +let appState = { + translations: null, // 将在初始化后从脚本中设置 + currentLang: 'zh', + SORT_OPTIONS: null, // 将在初始化后从脚本中设置 +}; + +// 注册全局函数 +window.switchTab = function(tabName) { + // 调用主脚本中的函数 + window.dispatchEvent(new CustomEvent('switchTab', { detail: { tabName } })); +}; + +window.searchMagnet = function() { + // 触发搜索事件 + window.dispatchEvent(new CustomEvent('searchMagnet')); +}; + +window.copyToClipboard = function(text) { + // 触发复制事件 + window.dispatchEvent(new CustomEvent('copyToClipboard', { detail: { text } })); +}; + +window.showSortMenu = function(button) { + // 触发排序菜单事件 + window.dispatchEvent(new CustomEvent('showSortMenu', { detail: { button } })); +}; + +// 添加热门搜索词点击处理函数 +window.searchWithTerm = function(term) { + // 触发热门搜索词点击事件 + window.dispatchEvent(new CustomEvent('searchWithTerm', { detail: { term } })); +}; + +// 添加视频页面复制URL按钮点击事件 +window.copyVideoUrl = function() { + const sourceUrlElement = document.getElementById('videoSourceUrl'); + const sourceUrl = sourceUrlElement?.textContent; + if (!sourceUrl) return; + + // 使用全局copyToClipboard函数 + window.copyToClipboard(sourceUrl); + + // 更新按钮状态 + const copyButton = document.getElementById('copySourceUrl'); + if (copyButton) { + copyButton.classList.add('copied'); + const textElement = copyButton.querySelector('.tab-text'); + if (textElement) { + const originalText = textElement.textContent; + textElement.textContent = appState.translations ? + appState.translations[appState.currentLang].copied : + '已复制'; + + setTimeout(() => { + copyButton.classList.remove('copied'); + textElement.textContent = originalText; + }, 2000); + } + } +}; + +// 注册全局事件处理函数以便主脚本可以设置全局状态 +window.setGlobalState = function(key, value) { + appState[key] = value; +}; + +// 为主脚本提供初始化方法 +window.initializeGlobals = function(data) { + if (data.translations) appState.translations = data.translations; + if (data.currentLang) appState.currentLang = data.currentLang; + if (data.SORT_OPTIONS) appState.SORT_OPTIONS = data.SORT_OPTIONS; +}; \ No newline at end of file diff --git a/web/index.html b/web/index.html index 157b339..8024c82 100644 --- a/web/index.html +++ b/web/index.html @@ -7,9 +7,9 @@ - + @@ -175,7 +175,7 @@
- + - + + + +
diff --git a/web/script.js b/web/script.js index aea88e8..af9b602 100644 --- a/web/script.js +++ b/web/script.js @@ -1,3 +1,46 @@ +import API_CONFIG from './config.js'; + +// 在文档加载完成后初始化全局函数和事件监听 +document.addEventListener('DOMContentLoaded', () => { + // 监听switchTab事件 + window.addEventListener('switchTab', (e) => { + const { tabName } = e.detail; + switchTab(tabName); + }); + + // 监听searchMagnet事件 + window.addEventListener('searchMagnet', () => { + searchMagnet(); + }); + + // 监听copyToClipboard事件 + window.addEventListener('copyToClipboard', (e) => { + const { text } = e.detail; + copyToClipboard(text); + }); + + // 监听showSortMenu事件 + window.addEventListener('showSortMenu', (e) => { + const { button } = e.detail; + showSortMenu(button); + }); + + // 监听searchWithTerm事件 + window.addEventListener('searchWithTerm', (e) => { + const { term } = e.detail; + searchWithTerm(term); + }); + + // 初始化全局变量 + if (window.initializeGlobals) { + window.initializeGlobals({ + translations, + currentLang, + SORT_OPTIONS + }); + } +}); + // Tab切换功能 // 添加全局变量 @@ -52,6 +95,8 @@ function switchTab(tabName) { if (!videoPlayer.paused) { videoPlayer.pause(); } + + // 不销毁HLS实例,只是暂停播放,避免重新创建开销 } } @@ -61,16 +106,6 @@ function switchTab(tabName) { } } -// 添加 API 配置 -const API_CONFIG = { - BASE_URL: '/app/v1', - ENDPOINTS: { - SEARCH: '/avcode', - COLLECTIONS: '/hacg', - VIDEO: '/get_video', - HOT_SEARCHES: '/hot_searches' // 添加热门搜索接口 - } -}; // 搜索磁力链接 @@ -154,7 +189,6 @@ async function searchMagnet() { } // 显示搜索结果 - function displaySearchResults(results) { const searchResults = document.getElementById('searchResults'); const coverToggle = document.getElementById('coverToggle'); @@ -175,32 +209,37 @@ function displaySearchResults(results) { return; } - const html = results.map(([magnet, title, size, date]) => { + results.forEach(([magnet, title, size, date]) => { const tags = extractTags(title); const tagsHtml = tags.map(tag => { return `
${getTagLabel(tag.type)}
`; }).join(''); - return ` -
-
-

${title}

-
- ${tagsHtml} -
-

- ${translations[currentLang].size}: ${size} | ${translations[currentLang].date}: ${date} -

- + const resultItem = document.createElement('div'); + resultItem.className = 'magnet-item p-6 rounded-xl'; + resultItem.innerHTML = ` +
+

${title}

+
+ ${tagsHtml}
+

+ ${translations[currentLang].size}: ${size} | ${translations[currentLang].date}: ${date} +

+
`; - }).join(''); - searchResults.innerHTML = html; + // 添加点击事件 + const copyButton = resultItem.querySelector('.copy-button'); + copyButton.addEventListener('click', () => { + copyToClipboard(magnet); + }); + + searchResults.appendChild(resultItem); + }); // 添加这一行,确保结果按照标签数量排序 sortResults('tags-desc'); @@ -459,6 +498,7 @@ function handleMouseEnter() { let hls = null; let autoplayEnabled = localStorage.getItem('autoplay') === 'true'; // 从localStorage读取初始值 let autoNextEnabled = localStorage.getItem('autoNext') === 'true'; // 从localStorage读取初始值 +let isLoadingNextVideo = false; // 添加锁定标志,防止重复请求 // 初始化自动播放设置 function initializeAutoplaySettings() { @@ -516,380 +556,271 @@ function initializeCoverToggle() { // 修改 loadVideo 函数 function loadVideo() { - const videoPlayer = document.getElementById('videoPlayer'); - const videoSourceUrl = document.getElementById('videoSourceUrl'); - const notification = document.getElementById('notification'); - const showCover = document.getElementById('coverToggle').checked; - - // 如果已经有视频URL,则不重新请求 - if (currentVideoUrl) { - if (videoSourceUrl) { - videoSourceUrl.textContent = currentVideoUrl; + try { + const videoPlayer = document.getElementById('videoPlayer'); + const videoSourceUrl = document.getElementById('videoSourceUrl'); + const notification = document.getElementById('notification'); + const showCover = document.getElementById('coverToggle')?.checked || false; + + // 确保视频播放器可操作 + if (videoPlayer) { + videoPlayer.controls = true; } - if (videoPlayer && videoPlayer.src !== currentVideoUrl) { - videoPlayer.src = currentVideoUrl; - // 根据自动播放设置决定是否播放 - if (autoplayEnabled) { - videoPlayer.play().catch(e => console.error('Auto-play failed:', e)); + + // 如果已经有视频URL,则不重新请求 + if (currentVideoUrl) { + if (videoSourceUrl) { + videoSourceUrl.textContent = currentVideoUrl; } - } - return; - } - - // 显示加载中通知 - notification.innerHTML = ` - - - - ${translations[currentLang].loadingVideo} - `; - notification.classList.add('show'); - - // 如果没有视频URL,则请求新的 - fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.VIDEO}`) - .then(response => response.json()) - .then(data => { - if (data && data.url) { - currentVideoUrl = data.url; // 保存视频URL - if (videoSourceUrl) { - videoSourceUrl.textContent = data.url; - } - if (videoPlayer) { - // 设置视频封面(如果开启了封面图显示) - if (showCover && data.img_url) { - videoPlayer.poster = data.img_url; + if (videoPlayer && videoPlayer.src !== currentVideoUrl) { + // 移除之前的事件监听器 + videoPlayer.onended = null; + + // 使用HLS.js播放m3u8视频 + if (currentVideoUrl.includes('.m3u8')) { + if (Hls.isSupported()) { + const hls = new Hls(); + hls.loadSource(currentVideoUrl); + hls.attachMedia(videoPlayer); + hls.on(Hls.Events.MANIFEST_PARSED, function() { + // 根据自动播放设置决定是否播放 + if (autoplayEnabled) { + videoPlayer.play().catch(e => console.error('Auto-play failed:', e)); + } + }); + + // 存储HLS实例以便清理 + videoPlayer.hlsInstance = hls; + } else if (videoPlayer.canPlayType('application/vnd.apple.mpegurl')) { + // 原生支持HLS的浏览器(如Safari) + videoPlayer.src = currentVideoUrl; + if (autoplayEnabled) { + videoPlayer.play().catch(e => console.error('Auto-play failed:', e)); + } } else { - videoPlayer.poster = ''; // 清除封面图 + console.error('当前浏览器不支持HLS视频播放'); + notification.innerHTML = ` + + + + ${translations[currentLang].browserNotSupported || '当前浏览器不支持该视频格式'} + `; + notification.style.background = '#dc2626'; + notification.classList.add('show'); + setTimeout(() => { + notification.classList.remove('show'); + notification.style.background = ''; + }, 3000); } - - videoPlayer.src = data.url; - + } else { + // 非m3u8格式视频使用标准方式播放 + videoPlayer.src = currentVideoUrl; // 根据自动播放设置决定是否播放 if (autoplayEnabled) { videoPlayer.play().catch(e => console.error('Auto-play failed:', e)); } - - // 添加视频结束事件监听 - videoPlayer.onended = () => { - if (autoNextEnabled) { - clearVideoUrl(); // 清除当前URL - loadVideo(); // 加载下一个视频 - } - }; } - // 隐藏加载通知 - notification.classList.remove('show'); + // 添加视频结束事件监听 + videoPlayer.onended = handleVideoEnded; } - }) - .catch(error => { - console.error('加载视频失败:', error); - notification.innerHTML = ` - - - - ${translations[currentLang].videoError} - `; - notification.style.background = '#dc2626'; - setTimeout(() => { - notification.classList.remove('show'); - notification.style.background = ''; - }, 3000); - }); + + // 重置锁定状态 + isLoadingNextVideo = false; + return; + } + + // 设置加载状态为true + isLoadingNextVideo = true; + + // 显示加载中通知 + notification.innerHTML = ` + + + + ${translations[currentLang].loadingVideo} + `; + notification.classList.add('show'); + + // 如果没有视频URL,则请求新的 + fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.VIDEO}`) + .then(response => response.json()) + .then(data => { + if (data && data.url) { + currentVideoUrl = data.url; // 保存视频URL + if (videoSourceUrl) { + videoSourceUrl.textContent = data.url; + } + if (videoPlayer) { + // 移除之前的事件监听器 + videoPlayer.onended = null; + + // 确保控件可用 + videoPlayer.controls = true; + + // 设置视频封面(如果开启了封面图显示) + if (showCover && data.img_url) { + videoPlayer.poster = data.img_url; + } else { + videoPlayer.poster = ''; // 清除封面图 + } + + // 清理之前的HLS实例(如果存在) + if (videoPlayer.hlsInstance) { + videoPlayer.hlsInstance.destroy(); + videoPlayer.hlsInstance = null; + } + + // 使用HLS.js播放m3u8视频 + if (data.url.includes('.m3u8')) { + if (Hls.isSupported()) { + const hls = new Hls({ + // 添加更多的HLS配置,以增强稳定性 + maxBufferLength: 30, + maxMaxBufferLength: 60 + }); + hls.loadSource(data.url); + hls.attachMedia(videoPlayer); + hls.on(Hls.Events.MANIFEST_PARSED, function() { + // 根据自动播放设置决定是否播放 + if (autoplayEnabled) { + videoPlayer.play().catch(e => console.error('Auto-play failed:', e)); + } + }); + + // 存储HLS实例以便清理 + videoPlayer.hlsInstance = hls; + } else if (videoPlayer.canPlayType('application/vnd.apple.mpegurl')) { + // 原生支持HLS的浏览器(如Safari) + videoPlayer.src = data.url; + if (autoplayEnabled) { + videoPlayer.play().catch(e => console.error('Auto-play failed:', e)); + } + } else { + console.error('当前浏览器不支持HLS视频播放'); + notification.innerHTML = ` + + + + ${translations[currentLang].browserNotSupported || '当前浏览器不支持该视频格式'} + `; + notification.style.background = '#dc2626'; + notification.classList.add('show'); + setTimeout(() => { + notification.classList.remove('show'); + notification.style.background = ''; + }, 3000); + } + } else { + // 非m3u8格式视频使用标准方式播放 + videoPlayer.src = data.url; + // 根据自动播放设置决定是否播放 + if (autoplayEnabled) { + videoPlayer.play().catch(e => console.error('Auto-play failed:', e)); + } + } + + // 添加视频结束事件监听 + videoPlayer.onended = handleVideoEnded; + } + + // 隐藏加载通知 + notification.classList.remove('show'); + } + // 重置加载状态 + isLoadingNextVideo = false; + }) + .catch(error => { + console.error('加载视频失败:', error); + notification.innerHTML = ` + + + + ${translations[currentLang].videoError} + `; + notification.style.background = '#dc2626'; + setTimeout(() => { + notification.classList.remove('show'); + notification.style.background = ''; + }, 3000); + + // 重置加载状态 + isLoadingNextVideo = false; + + // 确保播放器可用 + if (videoPlayer) { + videoPlayer.controls = true; + } + }); + } catch (error) { + console.error('loadVideo函数出错:', error); + // 确保播放器可用 + const videoPlayer = document.getElementById('videoPlayer'); + if (videoPlayer) { + videoPlayer.controls = true; + } + // 重置加载状态 + isLoadingNextVideo = false; + } +} + +// 处理视频结束事件 +function handleVideoEnded() { + console.log("视频结束事件触发", autoNextEnabled, isLoadingNextVideo); + + // 确保视频播放器可操作 + const videoPlayer = document.getElementById('videoPlayer'); + if (videoPlayer) { + // 确保控件可用 + videoPlayer.controls = true; + } + + if (autoNextEnabled && !isLoadingNextVideo) { + isLoadingNextVideo = true; + setTimeout(() => { + // 延迟一点时间再加载下一个视频,防止事件冲突 + clearVideoUrl(); + }, 100); + } else { + // 如果不需要自动播放下一个,也要确保重置锁定状态 + isLoadingNextVideo = false; + } +} + +// 添加清除视频URL的函数(可以在需要重新加载视频时调用) +function clearVideoUrl() { + try { + // 确保视频播放器可操作 + const videoPlayer = document.getElementById('videoPlayer'); + if (videoPlayer) { + // 确保控件可用 + videoPlayer.controls = true; + + // 移除视频事件监听器 + videoPlayer.onended = null; + + // 清理HLS实例 + if (videoPlayer.hlsInstance) { + videoPlayer.hlsInstance.destroy(); + videoPlayer.hlsInstance = null; + } + } + + // 清除URL并加载新视频 + currentVideoUrl = ''; + + // 确保loadVideo函数被调用 + setTimeout(() => { + loadVideo(); + }, 200); + } catch (error) { + console.error('清除视频URL出错:', error); + // 发生错误时重置锁定状态 + isLoadingNextVideo = false; + } } // 初始化视频播放器 -document.addEventListener('DOMContentLoaded', () => { - initializeAutoplaySettings(); // 这个函数现在已经包含了自动播放和自动下一个的初始化 - initializeCopyButton(); - initializeCoverToggle(); - - // 修改下一个按钮的事件监听 - const nextVideoButton = document.getElementById('nextVideo'); - if (nextVideoButton) { - nextVideoButton.addEventListener('click', () => { - clearVideoUrl(); // 使用clearVideoUrl函数来处理 - }); - } - - // 初始化模态框关闭功能 - const imageModal = document.getElementById('imageModal'); - const closeModal = document.getElementById('closeModal'); - - if (imageModal && closeModal) { - // 点击关闭按钮关闭模态框 - closeModal.addEventListener('click', () => { - imageModal.classList.remove('active'); - setTimeout(() => { - imageModal.classList.add('hidden'); - }, 300); - }); - - // 点击模态框背景关闭模态框 - imageModal.addEventListener('click', (e) => { - if (e.target === imageModal) { - imageModal.classList.remove('active'); - setTimeout(() => { - imageModal.classList.add('hidden'); - }, 300); - } - }); - - // ESC键关闭模态框 - document.addEventListener('keydown', (e) => { - if (e.key === 'Escape' && !imageModal.classList.contains('hidden')) { - imageModal.classList.remove('active'); - setTimeout(() => { - imageModal.classList.add('hidden'); - }, 300); - } - }); - } - - // 如果当前是搜索标签页,获取热门搜索词 - if (currentTab === 'search') { - fetchHotSearches(); - } -}); - -// 初始化复制按钮功能 -function initializeCopyButton() { - const copyButton = document.getElementById('copySourceUrl'); - const notification = document.getElementById('notification'); - - if (copyButton) { - copyButton.addEventListener('click', async () => { - const sourceUrlElement = document.getElementById('videoSourceUrl'); - const sourceUrl = sourceUrlElement?.textContent; - if (!sourceUrl) return; - - try { - await navigator.clipboard.writeText(sourceUrl); - - // 显示复制成功提示 - if (notification) { - notification.innerHTML = ` - - - - ${translations[currentLang].copied} - `; - notification.style.background = '#10B981'; // 成功绿色 - notification.classList.add('show'); - setTimeout(() => { - notification.classList.remove('show'); - notification.style.background = ''; - }, 2000); - } - - // 更新按钮状态 - copyButton.classList.add('copied'); - const textElement = copyButton.querySelector('.tab-text'); - if (textElement) { - const originalText = textElement.textContent; - textElement.textContent = translations[currentLang].copied; - - setTimeout(() => { - copyButton.classList.remove('copied'); - textElement.textContent = originalText; - }, 2000); - } - } catch (err) { - console.error('复制失败:', err); - if (notification) { - notification.innerHTML = ` - - - - ${translations[currentLang].copyFailed} - `; - notification.style.background = '#dc2626'; - notification.classList.add('show'); - setTimeout(() => { - notification.classList.remove('show'); - notification.style.background = ''; - }, 3000); - } - } - }); - } -} - -// 语言配置 -const translations = { - zh: { - search: 'AV搜索', - collections: '里番合集', - player: '视频播放', - searchPlaceholder: '请输入AV番号...', - searchButton: '搜索', - copyButton: '复制链接', - noResults: '未找到相关结果', - searchError: '搜索出错,请稍后重试', - size: '大小', - date: '日期', - emptySearchWarning: '搜索词为空或有误,请重新输入', - copySuccess: '已复制到剪贴板', - copyError: '复制失败,请手动复制', - loading: '正在搜索中', - pageSize: '每页显示', - items: '条', - total: '共', - currentPage: '当前第', - page: '页', - prevPage: '上一页', - nextPage: '下一页', - goToPage: '跳转到', - sortByDate: '按日期排序', - sortBySize: '按大小排序', - newest: '最新', - oldest: '最早', - largest: '最大', - smallest: '最小', - next: '下一个', - loadingVideo: '正在加载视频...', - videoError: '视频加载失败,请稍后重试', - nsfw: '⚠️ 警告:该内容包含成人内容 (NSFW),请确保您已年满18岁', - autoplay: '自动播放', - sourceUrl: '视频源地址', - copy: '复制', - copied: '已复制', - copyFailed: '复制失败' - }, - en: { - search: 'AV Search', - collections: 'Anime Collection', - player: 'Video Player', - searchPlaceholder: 'Enter AV number...', - searchButton: 'Search', - copyButton: 'Copy Link', - noResults: 'No results found', - searchError: 'Search error, please try again later', - size: 'Size', - date: 'Date', - emptySearchWarning: 'The search term is empty or incorrect, please re-enter', - copySuccess: 'Copied to clipboard', - copyError: 'Copy failed, please copy manually', - loading: 'Searching', - pageSize: 'Show', - items: 'items', - total: 'Total', - currentPage: 'Page', - page: '', - prevPage: 'Previous', - nextPage: 'Next', - goToPage: 'Go to page', - sortByDate: 'Sort by date', - sortBySize: 'Sort by size', - newest: 'Newest', - oldest: 'Oldest', - largest: 'Largest', - smallest: 'Smallest', - next: 'Next', - loadingVideo: 'Loading video...', - videoError: 'Failed to load video, please try again later', - nsfw: '⚠️ Warning: This content contains adult material (NSFW), ensure you are 18+', - autoplay: 'Auto Play', - sourceUrl: 'Video Source URL', - copy: 'Copy', - copied: 'Copied', - copyFailed: 'Copy Failed' - } -}; - -// 语言图标配置 -const LANGUAGES = { - zh: { - icon: ` - - `, - label: '中文' - }, - en: { - icon: ` - - `, - label: 'English' - } -}; - -// 当前语言 -let currentLang = 'zh'; - -// 主题配置 -const THEMES = { - dark: { - icon: ` - - `, - label: { zh: '夜间', en: 'Dark' } - }, - light: { - icon: ` - - `, - label: { zh: '日间', en: 'Light' } - }, - emerald: { - icon: ` - - `, - label: { zh: '翠绿', en: 'Emerald' } - }, - ocean: { - icon: ` - - - - `, - label: { zh: '海蓝', en: 'Ocean' } - }, - amethyst: { - icon: ` - - `, - label: { zh: '紫晶', en: 'Amethyst' } - } -}; - -// 排序配置 -const SORT_OPTIONS = { - 'tags-desc': { - icon: ` - - `, - label: { zh: '标签最多', en: 'Most Tags' } - }, - 'date-desc': { - icon: ` - - `, - label: { zh: '最新日期', en: 'Newest' } - }, - 'date-asc': { - icon: ` - - `, - label: { zh: '最早日期', en: 'Oldest' } - }, - 'size-desc': { - icon: ` - - `, - label: { zh: '文件最大', en: 'Largest' } - }, - 'size-asc': { - icon: ` - - `, - label: { zh: '文件最小', en: 'Smallest' } - } -}; - -// 初始化 document.addEventListener('DOMContentLoaded', () => { // 设置初始主题 const savedTheme = localStorage.getItem('theme') || 'dark'; @@ -901,6 +832,10 @@ document.addEventListener('DOMContentLoaded', () => { // 初始化所有按钮 initializeButtons(); + + // 初始化视频相关功能 + initializeAutoplaySettings(); + initializeCoverToggle(); // 加载合集列表 loadCollections(); @@ -1002,8 +937,199 @@ document.addEventListener('DOMContentLoaded', () => { if (currentTab === 'search') { fetchHotSearches(); } + + // 添加页面卸载事件处理,清理HLS实例 + window.addEventListener('beforeunload', () => { + const videoPlayer = document.getElementById('videoPlayer'); + if (videoPlayer && videoPlayer.hlsInstance) { + videoPlayer.hlsInstance.destroy(); + videoPlayer.hlsInstance = null; + } + }); }); +// 初始化复制按钮功能 +function initializeCopyButton() { + // 现在我们使用全局函数,这个函数不再需要实际功能 + console.log('Using global copyVideoUrl function'); +} + +// 语言配置 +const translations = { + zh: { + search: 'AV搜索', + collections: '里番合集', + player: '视频播放', + searchPlaceholder: '请输入AV番号...', + searchButton: '搜索', + copyButton: '复制链接', + noResults: '未找到相关结果', + searchError: '搜索出错,请稍后重试', + size: '大小', + date: '日期', + emptySearchWarning: '搜索词为空或有误,请重新输入', + copySuccess: '已复制到剪贴板', + copyError: '复制失败,请手动复制', + loading: '正在搜索中', + pageSize: '每页显示', + items: '条', + total: '共', + currentPage: '当前第', + page: '页', + prevPage: '上一页', + nextPage: '下一页', + goToPage: '跳转到', + sortByDate: '按日期排序', + sortBySize: '按大小排序', + newest: '最新', + oldest: '最早', + largest: '最大', + smallest: '最小', + next: '下一个', + loadingVideo: '正在加载视频...', + videoError: '视频加载失败,请稍后重试', + nsfw: '⚠️ 警告:该内容包含成人内容 (NSFW),请确保您已年满18岁', + autoplay: '自动播放', + sourceUrl: '视频源地址', + copy: '复制', + copied: '已复制', + copyFailed: '复制失败', + browserNotSupported: '当前浏览器不支持该视频格式' + }, + en: { + search: 'AV Search', + collections: 'Anime Collection', + player: 'Video Player', + searchPlaceholder: 'Enter AV number...', + searchButton: 'Search', + copyButton: 'Copy Link', + noResults: 'No results found', + searchError: 'Search error, please try again later', + size: 'Size', + date: 'Date', + emptySearchWarning: 'The search term is empty or incorrect, please re-enter', + copySuccess: 'Copied to clipboard', + copyError: 'Copy failed, please copy manually', + loading: 'Searching', + pageSize: 'Show', + items: 'items', + total: 'Total', + currentPage: 'Page', + page: '', + prevPage: 'Previous', + nextPage: 'Next', + goToPage: 'Go to page', + sortByDate: 'Sort by date', + sortBySize: 'Sort by size', + newest: 'Newest', + oldest: 'Oldest', + largest: 'Largest', + smallest: 'Smallest', + next: 'Next', + loadingVideo: 'Loading video...', + videoError: 'Failed to load video, please try again later', + nsfw: '⚠️ Warning: This content contains adult material (NSFW), ensure you are 18+', + autoplay: 'Auto Play', + sourceUrl: 'Video Source URL', + copy: 'Copy', + copied: 'Copied', + copyFailed: 'Copy Failed', + browserNotSupported: 'Current browser does not support this video format' + } +}; + +// 语言图标配置 +const LANGUAGES = { + zh: { + icon: ` + + `, + label: '中文' + }, + en: { + icon: ` + + `, + label: 'English' + } +}; + +// 当前语言 +let currentLang = 'zh'; + +// 主题配置 +const THEMES = { + dark: { + icon: ` + + `, + label: { zh: '夜间', en: 'Dark' } + }, + light: { + icon: ` + + `, + label: { zh: '日间', en: 'Light' } + }, + emerald: { + icon: ` + + `, + label: { zh: '翠绿', en: 'Emerald' } + }, + ocean: { + icon: ` + + + + `, + label: { zh: '海蓝', en: 'Ocean' } + }, + amethyst: { + icon: ` + + `, + label: { zh: '紫晶', en: 'Amethyst' } + } +}; + +// 排序配置 +const SORT_OPTIONS = { + 'tags-desc': { + icon: ` + + `, + label: { zh: '标签最多', en: 'Most Tags' } + }, + 'date-desc': { + icon: ` + + `, + label: { zh: '最新日期', en: 'Newest' } + }, + 'date-asc': { + icon: ` + + `, + label: { zh: '最早日期', en: 'Oldest' } + }, + 'size-desc': { + icon: ` + + `, + label: { zh: '文件最大', en: 'Largest' } + }, + 'size-asc': { + icon: ` + + `, + label: { zh: '文件最小', en: 'Smallest' } + } +}; + // 切换主题功能 function toggleTheme(themeName) { // 移除所有主题类 @@ -1401,15 +1527,24 @@ function displayCollections(collections) { collections.forEach(collection => { const collectionItem = document.createElement('div'); collectionItem.className = 'magnet-item p-6 rounded-xl'; + + const linkText = collection.link; + collectionItem.innerHTML = `
-

${collection.title}

-
`; + + // 添加点击事件 + const copyButton = collectionItem.querySelector('.copy-button'); + copyButton.addEventListener('click', () => { + copyToClipboard(linkText); + }); + collectionList.appendChild(collectionItem); }); } else if (typeof collections === 'object' && collections !== null) { @@ -1417,15 +1552,22 @@ function displayCollections(collections) { Object.entries(collections).forEach(([title, link]) => { const collectionItem = document.createElement('div'); collectionItem.className = 'magnet-item p-6 rounded-xl'; + collectionItem.innerHTML = `

${title}

-
`; + + // 添加点击事件 + const copyButton = collectionItem.querySelector('.copy-button'); + copyButton.addEventListener('click', () => { + copyToClipboard(link); + }); + collectionList.appendChild(collectionItem); }); } else { @@ -1786,14 +1928,6 @@ function initializeModalEvents() { }); } -// 添加清除视频URL的函数(可以在需要重新加载视频时调用) -function clearVideoUrl() { - if (currentVideoUrl) { // 只有在有当前URL时才清除并重新加载 - currentVideoUrl = ''; - loadVideo(); - } -} - // 添加获取热门搜索的函数 async function fetchHotSearches() { try { @@ -1802,12 +1936,23 @@ async function fetchHotSearches() { if (data.status === 'succeed' && Array.isArray(data.data)) { const hotSearchesContainer = document.getElementById('hotSearches'); - hotSearchesContainer.innerHTML = data.data - .map(term => ` - - `).join(''); + + // 清空现有内容 + hotSearchesContainer.innerHTML = ''; + + // 创建并添加热门搜索标签 + data.data.forEach(term => { + const button = document.createElement('button'); + button.className = 'hot-search-tag'; + button.textContent = term; + + // 添加点击事件监听 + button.addEventListener('click', () => { + searchWithTerm(term); + }); + + hotSearchesContainer.appendChild(button); + }); } } catch (error) { console.error('获取热门搜索失败:', error); diff --git a/web/sw.js b/web/sw.js index f2f1557..018b8ec 100644 --- a/web/sw.js +++ b/web/sw.js @@ -3,7 +3,9 @@ const urlsToCache = [ '/', '/index.html', '/style.css', + '/config.js', '/script.js', + '/globals.js', '/manifest.json', '/imgs/favicon.ico', '/imgs/icon-192x192.png', @@ -68,4 +70,4 @@ self.addEventListener('fetch', event => { }); }) ); -}); \ No newline at end of file +});