From f1d42939784195cea31a50454870e589f7d1e783 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Wed, 21 Jan 2026 11:44:52 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BC=B9=E5=B9=95?= =?UTF-8?q?=E4=B8=8A=E9=99=90=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/play/page.tsx | 71 ++++++++++++++++++++++++++++- src/components/DanmakuPanel.tsx | 1 + src/components/UserMenu.tsx | 81 +++++++++++++++++++++++++++++++++ src/lib/danmaku/types.ts | 2 + 4 files changed, 154 insertions(+), 1 deletion(-) diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx index 4f2635e..eac5e03 100644 --- a/src/app/play/page.tsx +++ b/src/app/play/page.tsx @@ -367,6 +367,7 @@ function PlayPageClient() { >([]); const [danmakuLoading, setDanmakuLoading] = useState(false); const [danmakuCount, setDanmakuCount] = useState(0); + const [danmakuOriginalCount, setDanmakuOriginalCount] = useState(0); const danmakuPluginRef = useRef(null); const danmakuSettingsRef = useRef(danmakuSettings); @@ -705,6 +706,22 @@ function PlayPageClient() { } } + // 应用弹幕数量限制 + const maxCount = typeof window !== 'undefined' ? parseInt(localStorage.getItem('danmakuMaxCount') || '0', 10) : 0; + if (maxCount > 0 && danmakuData.length > maxCount) { + const originalCount = danmakuData.length; + const step = danmakuData.length / maxCount; + const limitedData = []; + for (let i = 0; i < maxCount; i++) { + limitedData.push(danmakuData[Math.floor(i * step)]); + } + danmakuData = limitedData; + setDanmakuOriginalCount(originalCount); + console.log(`弹幕数量限制: 原始 ${originalCount} 条,限制到 ${danmakuData.length} 条`); + } else { + setDanmakuOriginalCount(0); + } + // 加载弹幕到插件 const currentSettings = danmakuSettingsRef.current; danmakuPluginRef.current.config({ @@ -3602,6 +3619,22 @@ function PlayPageClient() { } } + // 应用弹幕数量限制 + const maxCount = typeof window !== 'undefined' ? parseInt(localStorage.getItem('danmakuMaxCount') || '0', 10) : 0; + if (maxCount > 0 && danmakuData.length > maxCount) { + const originalCount = danmakuData.length; + const step = danmakuData.length / maxCount; + const limitedData = []; + for (let i = 0; i < maxCount; i++) { + limitedData.push(danmakuData[Math.floor(i * step)]); + } + danmakuData = limitedData; + setDanmakuOriginalCount(originalCount); + console.log(`弹幕数量限制: 原始 ${originalCount} 条,限制到 ${danmakuData.length} 条`); + } else { + setDanmakuOriginalCount(0); + } + // 加载弹幕到插件,同时应用当前的弹幕设置 const currentSettings = danmakuSettingsRef.current; danmakuPluginRef.current.config({ @@ -3634,6 +3667,7 @@ function PlayPageClient() { episodeTitle: metadata.episodeTitle || '', searchKeyword: metadata.searchKeyword, danmakuCount: danmakuData.length, + danmakuOriginalCount: danmakuOriginalCount > 0 ? danmakuOriginalCount : undefined, }); } @@ -3801,6 +3835,22 @@ function PlayPageClient() { }); } + // 应用弹幕数量限制 + const maxCount = typeof window !== 'undefined' ? parseInt(localStorage.getItem('danmakuMaxCount') || '0', 10) : 0; + if (maxCount > 0 && danmakuData.length > maxCount) { + const originalCount = danmakuData.length; + const step = danmakuData.length / maxCount; + const limitedData = []; + for (let i = 0; i < maxCount; i++) { + limitedData.push(danmakuData[Math.floor(i * step)]); + } + danmakuData = limitedData; + setDanmakuOriginalCount(originalCount); + console.log(`弹幕数量限制: 原始 ${originalCount} 条,限制到 ${danmakuData.length} 条`); + } else { + setDanmakuOriginalCount(0); + } + // 加载弹幕到播放器(使用 reset 方法清空,不触发显示/隐藏事件) if (danmakuPluginRef.current) { danmakuPluginRef.current.reset(); @@ -4050,6 +4100,22 @@ function PlayPageClient() { } } + // 应用弹幕数量限制 + const maxCount = typeof window !== 'undefined' ? parseInt(localStorage.getItem('danmakuMaxCount') || '0', 10) : 0; + if (maxCount > 0 && danmakuData.length > maxCount) { + const originalCount = danmakuData.length; + const step = danmakuData.length / maxCount; + const limitedData = []; + for (let i = 0; i < maxCount; i++) { + limitedData.push(danmakuData[Math.floor(i * step)]); + } + danmakuData = limitedData; + setDanmakuOriginalCount(originalCount); + console.log(`弹幕数量限制: 原始 ${originalCount} 条,限制到 ${danmakuData.length} 条`); + } else { + setDanmakuOriginalCount(0); + } + // 加载弹幕到插件 const currentSettings = danmakuSettingsRef.current; danmakuPluginRef.current.config({ @@ -7107,7 +7173,10 @@ function PlayPageClient() { /> - 已加载 {danmakuCount} 条弹幕 + {danmakuOriginalCount > 0 + ? `已加载 ${danmakuCount} 条弹幕(原始 ${danmakuOriginalCount} 条)` + : `已加载 ${danmakuCount} 条弹幕` + } ) : ( diff --git a/src/components/DanmakuPanel.tsx b/src/components/DanmakuPanel.tsx index 66304d0..b2c332b 100644 --- a/src/components/DanmakuPanel.tsx +++ b/src/components/DanmakuPanel.tsx @@ -241,6 +241,7 @@ export default function DanmakuPanel({ {currentSelection.danmakuCount !== undefined && (

弹幕数量: {currentSelection.danmakuCount} + {currentSelection.danmakuOriginalCount && ` (原始 ${currentSelection.danmakuOriginalCount} 条)`}

)} diff --git a/src/components/UserMenu.tsx b/src/components/UserMenu.tsx index 799df77..0d8266d 100644 --- a/src/components/UserMenu.tsx +++ b/src/components/UserMenu.tsx @@ -114,6 +114,7 @@ export const UserMenu: React.FC = () => { const [nextEpisodePreCache, setNextEpisodePreCache] = useState(true); const [nextEpisodeDanmakuPreload, setNextEpisodeDanmakuPreload] = useState(true); const [disableAutoLoadDanmaku, setDisableAutoLoadDanmaku] = useState(false); + const [danmakuMaxCount, setDanmakuMaxCount] = useState(0); const [searchTraditionalToSimplified, setSearchTraditionalToSimplified] = useState(false); // 邮件通知设置 @@ -408,6 +409,19 @@ export const UserMenu: React.FC = () => { setDisableAutoLoadDanmaku(savedDisableAutoLoadDanmaku === 'true'); } + const savedDanmakuMaxCount = localStorage.getItem('danmakuMaxCount'); + if (savedDanmakuMaxCount !== null) { + const value = parseInt(savedDanmakuMaxCount, 10); + // 兼容旧版本的0-4映射值 + if (value <= 4) { + const mappedValue = value === 0 ? 0 : value === 1 ? 1000 : value === 2 ? 3000 : value === 3 ? 5000 : 10000; + setDanmakuMaxCount(mappedValue); + localStorage.setItem('danmakuMaxCount', String(mappedValue)); + } else { + setDanmakuMaxCount(value); + } + } + // 加载首页模块配置 const savedHomeModules = localStorage.getItem('homeModules'); if (savedHomeModules !== null) { @@ -771,6 +785,13 @@ export const UserMenu: React.FC = () => { } }; + const handleDanmakuMaxCountChange = (value: number) => { + setDanmakuMaxCount(value); + if (typeof window !== 'undefined') { + localStorage.setItem('danmakuMaxCount', String(value)); + } + }; + const handleSearchTraditionalToSimplifiedToggle = (value: boolean) => { setSearchTraditionalToSimplified(value); if (typeof window !== 'undefined') { @@ -890,6 +911,7 @@ export const UserMenu: React.FC = () => { localStorage.setItem('nextEpisodePreCache', 'true'); localStorage.setItem('nextEpisodeDanmakuPreload', 'true'); localStorage.setItem('disableAutoLoadDanmaku', 'false'); + localStorage.setItem('danmakuMaxCount', '0'); localStorage.setItem('homeModules', JSON.stringify(defaultHomeModules)); localStorage.setItem('searchTraditionalToSimplified', 'false'); window.dispatchEvent(new CustomEvent('homeModulesUpdated')); @@ -1878,6 +1900,65 @@ export const UserMenu: React.FC = () => { + {/* 弹幕加载上限 */} +
+
+ + 弹幕加载上限 + + + {danmakuMaxCount === 0 ? '无上限' : `${danmakuMaxCount} 条`} + +
+
+ handleDanmakuMaxCountChange(parseInt(e.target.value))} + className='flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700' + style={{ + background: `linear-gradient(to right, #10b981 0%, #10b981 ${(danmakuMaxCount / 10000) * 100}%, #e5e7eb ${(danmakuMaxCount / 10000) * 100}%, #e5e7eb 100%)` + }} + /> +
+
+ + + + +
+

+ 限制加载的弹幕数量,减少性能消耗 +

+
+ {/* 清除弹幕缓存 */}
diff --git a/src/lib/danmaku/types.ts b/src/lib/danmaku/types.ts index d032a53..642c2b7 100644 --- a/src/lib/danmaku/types.ts +++ b/src/lib/danmaku/types.ts @@ -57,6 +57,7 @@ export interface DanmakuSettings { filterRules: string[]; // 过滤规则(正则表达式) unlimited: boolean; // 无限弹幕 synchronousPlayback: boolean; // 同步播放 + maxCount?: number; // 弹幕加载上限 } // 自动匹配请求 @@ -115,4 +116,5 @@ export interface DanmakuSelection { episodeTitle: string; searchKeyword?: string; // 用户搜索时使用的关键词 danmakuCount?: number; // 弹幕数量 + danmakuOriginalCount?: number; // 原始弹幕数量 } From 87cad0e3b6dd37a39308f100b5bbc472cc541945 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Wed, 21 Jan 2026 12:06:36 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=A6=81=E7=94=A8?= =?UTF-8?q?=E5=BC=B9=E5=B9=95=E7=83=AD=E5=8A=9B=E5=9B=BE=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/play/page.tsx | 6 ++--- src/components/UserMenu.tsx | 48 ++++++++++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx index eac5e03..6c9c661 100644 --- a/src/app/play/page.tsx +++ b/src/app/play/page.tsx @@ -4965,8 +4965,8 @@ function PlayPageClient() { return '打开设置'; }, }, - // 热力图开关(始终显示,不再依赖 danmakuHeatmapDisabled) - { + // 热力图开关(仅在未禁用时显示) + ...(!danmakuHeatmapDisabledRef.current ? [{ name: '弹幕热力', html: '弹幕热力', icon: '', @@ -4982,7 +4982,7 @@ function PlayPageClient() { } return newVal; }, - }, + }] : []), ...(webGPUSupported ? [ { name: 'Anime4K超分', diff --git a/src/components/UserMenu.tsx b/src/components/UserMenu.tsx index 0d8266d..16fcbc8 100644 --- a/src/components/UserMenu.tsx +++ b/src/components/UserMenu.tsx @@ -115,6 +115,7 @@ export const UserMenu: React.FC = () => { const [nextEpisodeDanmakuPreload, setNextEpisodeDanmakuPreload] = useState(true); const [disableAutoLoadDanmaku, setDisableAutoLoadDanmaku] = useState(false); const [danmakuMaxCount, setDanmakuMaxCount] = useState(0); + const [danmakuHeatmapDisabled, setDanmakuHeatmapDisabled] = useState(false); const [searchTraditionalToSimplified, setSearchTraditionalToSimplified] = useState(false); // 邮件通知设置 @@ -411,15 +412,12 @@ export const UserMenu: React.FC = () => { const savedDanmakuMaxCount = localStorage.getItem('danmakuMaxCount'); if (savedDanmakuMaxCount !== null) { - const value = parseInt(savedDanmakuMaxCount, 10); - // 兼容旧版本的0-4映射值 - if (value <= 4) { - const mappedValue = value === 0 ? 0 : value === 1 ? 1000 : value === 2 ? 3000 : value === 3 ? 5000 : 10000; - setDanmakuMaxCount(mappedValue); - localStorage.setItem('danmakuMaxCount', String(mappedValue)); - } else { - setDanmakuMaxCount(value); - } + setDanmakuMaxCount(parseInt(savedDanmakuMaxCount, 10)); + } + + const savedDanmakuHeatmapDisabled = localStorage.getItem('danmaku_heatmap_disabled'); + if (savedDanmakuHeatmapDisabled !== null) { + setDanmakuHeatmapDisabled(savedDanmakuHeatmapDisabled === 'true'); } // 加载首页模块配置 @@ -792,6 +790,13 @@ export const UserMenu: React.FC = () => { } }; + const handleDanmakuHeatmapDisabledToggle = (value: boolean) => { + setDanmakuHeatmapDisabled(value); + if (typeof window !== 'undefined') { + localStorage.setItem('danmaku_heatmap_disabled', String(value)); + } + }; + const handleSearchTraditionalToSimplifiedToggle = (value: boolean) => { setSearchTraditionalToSimplified(value); if (typeof window !== 'undefined') { @@ -912,6 +917,7 @@ export const UserMenu: React.FC = () => { localStorage.setItem('nextEpisodeDanmakuPreload', 'true'); localStorage.setItem('disableAutoLoadDanmaku', 'false'); localStorage.setItem('danmakuMaxCount', '0'); + localStorage.setItem('danmaku_heatmap_disabled', 'false'); localStorage.setItem('homeModules', JSON.stringify(defaultHomeModules)); localStorage.setItem('searchTraditionalToSimplified', 'false'); window.dispatchEvent(new CustomEvent('homeModulesUpdated')); @@ -1900,6 +1906,30 @@ export const UserMenu: React.FC = () => {
+ {/* 禁用弹幕热力图 */} +
+
+

+ 禁用弹幕热力图 +

+

+ 开启后不显示弹幕热力图和热力图开关 +

+
+ +
+ {/* 弹幕加载上限 */}
From 3755307117fbed7d744f7e44b1b64f0dcea32bfd Mon Sep 17 00:00:00 2001 From: mtvpls Date: Wed, 21 Jan 2026 20:37:53 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E5=8F=8D=E7=88=AC=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/douban-comments/route.ts | 15 +- src/app/api/douban-recommendations/route.ts | 21 +-- src/lib/douban-anti-crawler.ts | 199 ++++++++++++++++++++ 3 files changed, 205 insertions(+), 30 deletions(-) create mode 100644 src/lib/douban-anti-crawler.ts diff --git a/src/app/api/douban-comments/route.ts b/src/app/api/douban-comments/route.ts index 4e3ca9b..e761727 100644 --- a/src/app/api/douban-comments/route.ts +++ b/src/app/api/douban-comments/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import * as cheerio from 'cheerio'; +import { fetchDoubanWithVerification } from '@/lib/douban-anti-crawler'; export const runtime = 'nodejs'; @@ -25,20 +26,10 @@ export async function GET(request: NextRequest) { } try { - // 请求豆瓣短评页面 + // 请求豆瓣短评页面(使用反爬验证) const url = `https://movie.douban.com/subject/${doubanId}/comments?start=${start}&limit=${limit}&status=P&sort=new_score`; - const response = await fetch(url, { - headers: { - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', - Accept: - 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', - 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', - Referer: 'https://movie.douban.com/', - Cookie: 'bid=sadjkfhadskbfasdfdjh' - }, - }); + const response = await fetchDoubanWithVerification(url); if (!response.ok) { return NextResponse.json( diff --git a/src/app/api/douban-recommendations/route.ts b/src/app/api/douban-recommendations/route.ts index f0e2425..23d4e0a 100644 --- a/src/app/api/douban-recommendations/route.ts +++ b/src/app/api/douban-recommendations/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; import * as cheerio from 'cheerio'; import { fetchDoubanData } from '@/lib/douban'; +import { fetchDoubanWithVerification } from '@/lib/douban-anti-crawler'; export const runtime = 'nodejs'; @@ -26,26 +27,10 @@ export async function GET(request: NextRequest) { } try { - // 请求豆瓣电影页面,使用和其他豆瓣API相同的请求头 + // 请求豆瓣电影页面(使用反爬验证) const url = `https://movie.douban.com/subject/${doubanId}/`; - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 10000); - - const response = await fetch(url, { - 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: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', - Origin: 'https://movie.douban.com', - Cookie: 'bid=sadjkfhadskbfasdfdjh' - }, - }); - - clearTimeout(timeoutId); + const response = await fetchDoubanWithVerification(url); if (!response.ok) { return NextResponse.json( diff --git a/src/lib/douban-anti-crawler.ts b/src/lib/douban-anti-crawler.ts new file mode 100644 index 0000000..2f12c5e --- /dev/null +++ b/src/lib/douban-anti-crawler.ts @@ -0,0 +1,199 @@ +import * as cheerio from 'cheerio'; +import { createHash } from 'crypto'; + +/** + * 计算 SHA-512 哈希值 + */ +function sha512(data: string): string { + return createHash('sha512').update(data).digest('hex'); +} + +/** + * 工作量证明算法 - 寻找满足难度要求的 nonce + * @param data 要哈希的数据 + * @param difficulty 难度(前导零的数量) + * @returns 满足条件的 nonce + */ +function proofOfWork(data: string, difficulty = 4): number { + let nonce = 0; + const targetSubStr = '0'.repeat(difficulty); + + while (true) { + nonce += 1; + const hash = sha512(data + nonce); + if (hash.startsWith(targetSubStr)) { + return nonce; + } + } +} + +/** + * 解析豆瓣验证页面,提取表单数据 + */ +function parseVerificationPage(html: string): { + tok: string; + cha: string; + red: string; +} | null { + const $ = cheerio.load(html); + + const tok = $('#tok').val() as string; + const cha = $('#cha').val() as string; + const red = $('#red').val() as string; + + if (!tok || !cha || !red) { + console.error('Failed to extract verification form data'); + return null; + } + + return { tok, cha, red }; +} + +/** + * 获取豆瓣访问 cookie(处理反爬验证) + * @param url 要访问的豆瓣 URL + * @returns cookie 字符串 + */ +export async function getDoubanCookie(url: string): Promise { + const 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', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', + 'Referer': 'https://movie.douban.com/', + }; + + try { + // 第一步:访问目标 URL,可能会被重定向到验证页面 + const firstResponse = await fetch(url, { + headers, + redirect: 'manual', // 手动处理重定向 + }); + + // 如果没有重定向,直接返回 cookie + if (firstResponse.status === 200) { + const cookies = firstResponse.headers.get('set-cookie'); + return cookies || ''; + } + + // 如果是 302 重定向到验证页面 + if (firstResponse.status === 302) { + const location = firstResponse.headers.get('location'); + if (!location || !location.includes('sec.douban.com')) { + throw new Error('Unexpected redirect location'); + } + + console.log('Detected anti-crawler verification, processing...'); + + // 第二步:访问验证页面 + const verifyResponse = await fetch(location, { + headers, + }); + + if (!verifyResponse.ok) { + throw new Error(`Failed to fetch verification page: ${verifyResponse.status}`); + } + + const verifyHtml = await verifyResponse.text(); + + // 第三步:解析验证页面,提取表单数据 + const formData = parseVerificationPage(verifyHtml); + if (!formData) { + throw new Error('Failed to parse verification page'); + } + + console.log('Calculating proof of work...'); + + // 第四步:计算工作量证明 + const sol = proofOfWork(formData.cha, 4); + + console.log('Proof of work calculated:', sol); + + // 第五步:提交验证表单 + const formBody = new URLSearchParams({ + tok: formData.tok, + cha: formData.cha, + sol: sol.toString(), + red: formData.red, + }); + + const submitResponse = await fetch('https://sec.douban.com/c', { + method: 'POST', + headers: { + ...headers, + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: formBody.toString(), + redirect: 'manual', // 手动处理重定向 + }); + + // 第六步:从响应中提取 cookie + const setCookieHeader = submitResponse.headers.get('set-cookie'); + if (!setCookieHeader) { + throw new Error('No cookie received after verification'); + } + + console.log('Successfully obtained douban cookie'); + return setCookieHeader; + } + + throw new Error(`Unexpected response status: ${firstResponse.status}`); + } catch (error) { + console.error('Failed to get douban cookie:', error); + throw error; + } +} + +/** + * 带反爬验证的豆瓣请求 + * @param url 要访问的豆瓣 URL + * @param options fetch 选项 + * @returns Response 对象 + */ +export async function fetchDoubanWithVerification( + url: string, + options: RequestInit = {} +): Promise { + const 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', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', + 'Referer': 'https://movie.douban.com/', + ...options.headers, + }; + + try { + // 先尝试直接访问 + let response = await fetch(url, { + ...options, + headers, + redirect: 'manual', + }); + + // 如果被重定向到验证页面,获取 cookie 后重试 + if (response.status === 302) { + const location = response.headers.get('location'); + if (location && location.includes('sec.douban.com')) { + console.log('Anti-crawler detected, obtaining cookie...'); + + // 获取验证 cookie + const cookie = await getDoubanCookie(url); + + // 使用 cookie 重新请求 + response = await fetch(url, { + ...options, + headers: { + ...headers, + Cookie: cookie, + }, + }); + } + } + + return response; + } catch (error) { + console.error('Failed to fetch douban with verification:', error); + throw error; + } +} From c8558307eefc885bcdf91634bd521fa76b1806a7 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Sun, 18 Jan 2026 19:54:49 +0800 Subject: [PATCH 4/6] v209 --- CHANGELOG | 21 +++++++++++++++++++++ VERSION.txt | 2 +- src/lib/changelog.ts | 23 +++++++++++++++++++++++ src/lib/version.ts | 2 +- 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b636747..f99da2e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,24 @@ +## [209.0.0] - 2026-01-18 +### Added +- 新增收藏更新邮件发送 +- 新增测速超时设置 +- tmdb详情支持查看季度信息,集数信息 +- tmdb支持设置反代 +- tmdb图片支持自定义设置url +- 豆瓣图片增加百度图片代理 + +### Changed +- 统一upstash和redis数据层 +- 优化docker镜像大小 +- Cloudflare Turnstile开启前检测sitekey和Secretkey不能为空 +- 豆瓣反爬增强 +- 竖向videocard的source name调整到海报右下角 +- 取消小雅获取剧集强制刷新 +- 本地设置选项卡增加图标 + +### Fixed +- 修复详情不应用豆瓣图片策略 + ## [208.0.0] - 2026-01-16 ### Added - ai问片增加非流式响应 diff --git a/VERSION.txt b/VERSION.txt index 3f6aa33..581633a 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -208.0.0 \ No newline at end of file +209.0.0 \ No newline at end of file diff --git a/src/lib/changelog.ts b/src/lib/changelog.ts index a3197e4..c931de7 100644 --- a/src/lib/changelog.ts +++ b/src/lib/changelog.ts @@ -11,6 +11,29 @@ export interface ChangelogEntry { export const changelog: ChangelogEntry[] = [ { + version: '209.0.0', + date: '2026-01-18', + added: [ + "新增收藏更新邮件发送", + "新增测速超时设置", + "tmdb详情支持查看季度信息,集数信息", + "tmdb支持设置反代", + "tmdb图片支持自定义设置url", + "豆瓣图片增加百度图片代理" + ], + changed: [ + "统一upstash和redis数据层", + "优化docker镜像大小", + "Cloudflare Turnstile开启前检测sitekey和Secretkey不能为空", + "豆瓣反爬增强", + "竖向videocard的source name调整到海报右下角", + "取消小雅获取剧集强制刷新", + "本地设置选项卡增加图标" + ], + fixed: [ + "修复详情不应用豆瓣图片策略" + ] + },{ version: '208.0.0', date: '2026-01-16', added: [ diff --git a/src/lib/version.ts b/src/lib/version.ts index 4c14370..6db9952 100644 --- a/src/lib/version.ts +++ b/src/lib/version.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ -const CURRENT_VERSION = '208.0.0'; +const CURRENT_VERSION = '209.0.0'; // 导出当前版本号供其他地方使用 export { CURRENT_VERSION }; From bcdd2464ca387104e63eaf0d7dc37a5d9d236bc8 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Wed, 21 Jan 2026 00:27:33 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AE=A2=E9=98=85?= =?UTF-8?q?=E5=8E=BB=E5=B9=BF=E5=91=8A=E5=BC=80=E5=85=B3=E4=B8=8D=E6=AD=A3?= =?UTF-8?q?=E7=A1=AE=E4=BF=AE=E6=94=B9=E9=93=BE=E6=8E=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/UserMenu.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/UserMenu.tsx b/src/components/UserMenu.tsx index 16fcbc8..82dcb3c 100644 --- a/src/components/UserMenu.tsx +++ b/src/components/UserMenu.tsx @@ -275,10 +275,11 @@ export const UserMenu: React.FC = () => { }, []); // 懒加载订阅 URL - 只在打开订阅面板时请求 - const fetchSubscribeUrl = async () => { + const fetchSubscribeUrl = async (adFilter?: boolean) => { try { const currentOrigin = window.location.origin; - const response = await fetch(`/api/tvbox/config?origin=${encodeURIComponent(currentOrigin)}&adFilter=${adFilterEnabled}`); + const filterValue = adFilter !== undefined ? adFilter : adFilterEnabled; + const response = await fetch(`/api/tvbox/config?origin=${encodeURIComponent(currentOrigin)}&adFilter=${filterValue}`); if (response.ok) { const data = await response.json(); setSubscribeUrl(data.url); @@ -588,8 +589,8 @@ export const UserMenu: React.FC = () => { const handleAdFilterToggle = async (checked: boolean) => { setAdFilterEnabled(checked); - // 当去广告开关改变时,重新获取订阅URL - await fetchSubscribeUrl(); + // 当去广告开关改变时,重新获取订阅URL,传入新的值 + await fetchSubscribeUrl(checked); }; const handleCopySubscribeUrl = async () => { From 473769386571781ba2edc2f913346db04385a203 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Wed, 21 Jan 2026 21:00:36 +0800 Subject: [PATCH 6/6] v2091 --- CHANGELOG | 13 +++++++++++++ VERSION.txt | 2 +- src/lib/changelog.ts | 16 ++++++++++++++++ src/lib/version.ts | 2 +- 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f99da2e..ea6407f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,16 @@ +## [209.1.0] - 2026-01-21 +### Added +- 增加禁用自动匹配弹幕 +- 增加弹幕上限设置 +- 增加禁用弹幕热力图开关 + +### Changed +- 恢复无数据库支持 +- 反爬增强 + +### Fixed +- 修复订阅去广告开关不正确修改链接问题 + ## [209.0.0] - 2026-01-18 ### Added - 新增收藏更新邮件发送 diff --git a/VERSION.txt b/VERSION.txt index 581633a..b9434cd 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -209.0.0 \ No newline at end of file +209.1.0 \ No newline at end of file diff --git a/src/lib/changelog.ts b/src/lib/changelog.ts index c931de7..e244a24 100644 --- a/src/lib/changelog.ts +++ b/src/lib/changelog.ts @@ -11,6 +11,22 @@ export interface ChangelogEntry { export const changelog: ChangelogEntry[] = [ { + version: '209.1.0', + date: '2026-01-21', + added: [ + "增加禁用自动匹配弹幕", + "增加弹幕上限设置", + "增加禁用弹幕热力图开关" + ], + changed: [ + "恢复无数据库支持", + "反爬增强" + ], + fixed: [ + "修复订阅去广告开关不正确修改链接问题" + ] + }, + { version: '209.0.0', date: '2026-01-18', added: [ diff --git a/src/lib/version.ts b/src/lib/version.ts index 6db9952..9998ea6 100644 --- a/src/lib/version.ts +++ b/src/lib/version.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ -const CURRENT_VERSION = '209.0.0'; +const CURRENT_VERSION = '209.1.0'; // 导出当前版本号供其他地方使用 export { CURRENT_VERSION };