tvbox黄色过滤

This commit is contained in:
mtvpls
2026-03-24 10:57:32 +08:00
parent 4437a4c461
commit 5d75491523
3 changed files with 122 additions and 70 deletions

View File

@@ -78,12 +78,12 @@ export const UserMenu: React.FC = () => {
// 订阅相关状态
const [subscribeEnabled, setSubscribeEnabled] = useState(false);
const [subscribeUrl, setSubscribeUrl] = useState('');
const [subscribeUrlWithAdFilter, setSubscribeUrlWithAdFilter] = useState('');
const [copySuccess, setCopySuccess] = useState(false);
const [copySuccessAdFilter, setCopySuccessAdFilter] = useState(false);
const [tvboxToken, setTvboxToken] = useState('');
const [isResettingToken, setIsResettingToken] = useState(false);
const [isLoadingSubscribeUrl, setIsLoadingSubscribeUrl] = useState(false);
const [subscribeAdFilterEnabled, setSubscribeAdFilterEnabled] = useState(false);
const [subscribeYellowFilterEnabled, setSubscribeYellowFilterEnabled] = useState(false);
// Body 滚动锁定 - 使用 overflow 方式避免布局问题
useEffect(() => {
@@ -156,7 +156,7 @@ export const UserMenu: React.FC = () => {
isOpen: false,
title: '',
message: '',
onConfirm: () => {},
onConfirm: () => undefined,
});
// 折叠面板状态
@@ -323,13 +323,7 @@ export const UserMenu: React.FC = () => {
const token = data.token;
setTvboxToken(token);
// 前端拼接订阅链接
const currentOrigin = window.location.origin;
const standardUrl = `${currentOrigin}/api/tvbox/subscribe?token=${token}`;
const adFilterUrl = `${currentOrigin}/api/tvbox/subscribe?token=${token}&adFilter=true`;
setSubscribeUrl(standardUrl);
setSubscribeUrlWithAdFilter(adFilterUrl);
setSubscribeUrl(buildSubscribeUrl(token, subscribeAdFilterEnabled, subscribeYellowFilterEnabled));
}
} catch (error) {
console.error('获取订阅URL失败:', error);
@@ -359,13 +353,7 @@ export const UserMenu: React.FC = () => {
const token = data.token;
setTvboxToken(token);
// 更新订阅链接
const currentOrigin = window.location.origin;
const standardUrl = `${currentOrigin}/api/tvbox/subscribe?token=${token}`;
const adFilterUrl = `${currentOrigin}/api/tvbox/subscribe?token=${token}&adFilter=true`;
setSubscribeUrl(standardUrl);
setSubscribeUrlWithAdFilter(adFilterUrl);
setSubscribeUrl(buildSubscribeUrl(token, subscribeAdFilterEnabled, subscribeYellowFilterEnabled));
if (messageEl) {
messageEl.textContent = '订阅token已重置';
@@ -398,6 +386,19 @@ export const UserMenu: React.FC = () => {
});
};
const buildSubscribeUrl = (token: string, adFilter: boolean, yellowFilter: boolean) => {
const currentOrigin = window.location.origin;
const url = new URL('/api/tvbox/subscribe', currentOrigin);
url.searchParams.set('token', token);
if (adFilter) {
url.searchParams.set('adFilter', 'true');
}
if (yellowFilter) {
url.searchParams.set('yellowFilter', 'true');
}
return url.toString();
};
// 获取认证信息和存储类型
useEffect(() => {
if (typeof window !== 'undefined') {
@@ -819,7 +820,6 @@ export const UserMenu: React.FC = () => {
const handleCloseSubscribe = () => {
setIsSubscribeOpen(false);
setCopySuccess(false);
setCopySuccessAdFilter(false);
};
const handleCopySubscribeUrl = async () => {
@@ -833,18 +833,11 @@ export const UserMenu: React.FC = () => {
console.error('复制失败:', error);
}
};
const handleCopySubscribeUrlWithAdFilter = async () => {
try {
await navigator.clipboard.writeText(subscribeUrlWithAdFilter);
setCopySuccessAdFilter(true);
setTimeout(() => {
setCopySuccessAdFilter(false);
}, 2000);
} catch (error) {
console.error('复制失败:', error);
}
};
useEffect(() => {
if (!tvboxToken || !isSubscribeOpen) return;
setSubscribeUrl(buildSubscribeUrl(tvboxToken, subscribeAdFilterEnabled, subscribeYellowFilterEnabled));
}, [tvboxToken, subscribeAdFilterEnabled, subscribeYellowFilterEnabled, isSubscribeOpen]);
const handleSubmitChangePassword = async () => {
setPasswordError('');
@@ -2833,18 +2826,18 @@ export const UserMenu: React.FC = () => {
<div className='space-y-4'>
{isLoadingSubscribeUrl ? (
<>
{/* 加载骨架 - 订阅链接(标准) */}
{/* 加载骨架 - 开关 */}
<div>
<div className='h-5 w-32 bg-gray-200 dark:bg-gray-700 rounded mb-2 animate-pulse'></div>
<div className='flex gap-2'>
<div className='flex-1 h-10 bg-gray-200 dark:bg-gray-700 rounded animate-pulse'></div>
<div className='w-20 h-10 bg-gray-200 dark:bg-gray-700 rounded animate-pulse'></div>
<div className='h-5 w-24 bg-gray-200 dark:bg-gray-700 rounded mb-3 animate-pulse'></div>
<div className='space-y-2'>
<div className='h-14 bg-gray-200 dark:bg-gray-700 rounded animate-pulse'></div>
<div className='h-14 bg-gray-200 dark:bg-gray-700 rounded animate-pulse'></div>
</div>
</div>
{/* 加载骨架 - 订阅链接(去广告) */}
{/* 加载骨架 - 订阅链接 */}
<div>
<div className='h-5 w-36 bg-gray-200 dark:bg-gray-700 rounded mb-2 animate-pulse'></div>
<div className='h-5 w-28 bg-gray-200 dark:bg-gray-700 rounded mb-2 animate-pulse'></div>
<div className='flex gap-2'>
<div className='flex-1 h-10 bg-gray-200 dark:bg-gray-700 rounded animate-pulse'></div>
<div className='w-20 h-10 bg-gray-200 dark:bg-gray-700 rounded animate-pulse'></div>
@@ -2860,10 +2853,51 @@ export const UserMenu: React.FC = () => {
</>
) : (
<>
{/* 订阅链接(标准) */}
<div className='space-y-3'>
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
</h4>
<button
type='button'
onClick={() => setSubscribeAdFilterEnabled((prev) => !prev)}
className='w-full flex items-center justify-between rounded-lg border border-gray-200 dark:border-gray-700 px-4 py-3 text-left bg-gray-50 dark:bg-gray-800/70'
>
<div>
<div className='text-sm font-medium text-gray-800 dark:text-gray-200'>
广
</div>
<div className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
</div>
</div>
<div className={`relative h-6 w-11 rounded-full transition-colors ${subscribeAdFilterEnabled ? 'bg-green-500' : 'bg-gray-300 dark:bg-gray-600'}`}>
<div className={`absolute top-0.5 h-5 w-5 rounded-full bg-white transition-transform ${subscribeAdFilterEnabled ? 'translate-x-5' : 'translate-x-0.5'}`} />
</div>
</button>
<button
type='button'
onClick={() => setSubscribeYellowFilterEnabled((prev) => !prev)}
className='w-full flex items-center justify-between rounded-lg border border-gray-200 dark:border-gray-700 px-4 py-3 text-left bg-gray-50 dark:bg-gray-800/70'
>
<div>
<div className='text-sm font-medium text-gray-800 dark:text-gray-200'>
</div>
<div className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
</div>
</div>
<div className={`relative h-6 w-11 rounded-full transition-colors ${subscribeYellowFilterEnabled ? 'bg-yellow-500' : 'bg-gray-300 dark:bg-gray-600'}`}>
<div className={`absolute top-0.5 h-5 w-5 rounded-full bg-white transition-transform ${subscribeYellowFilterEnabled ? 'translate-x-5' : 'translate-x-0.5'}`} />
</div>
</button>
</div>
<div>
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300 mb-2'>
</h4>
<div className='flex gap-2'>
<input
@@ -2880,31 +2914,11 @@ export const UserMenu: React.FC = () => {
{copySuccess ? '已复制' : '复制'}
</button>
</div>
</div>
{/* 订阅链接(去广告) */}
<div>
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300 mb-2'>
广
</h4>
<div className='flex gap-2'>
<input
type='text'
className='flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100'
value={subscribeUrlWithAdFilter}
readOnly
/>
<button
onClick={handleCopySubscribeUrlWithAdFilter}
className='px-4 py-2 bg-green-600 hover:bg-green-700 dark:bg-green-700 dark:hover:bg-green-600 text-white text-sm font-medium rounded-md transition-colors flex items-center gap-2 whitespace-nowrap'
>
<Copy className='w-4 h-4' />
{copySuccessAdFilter ? '已复制' : '复制'}
</button>
</div>
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
💡 广
</p>
{(subscribeAdFilterEnabled || subscribeYellowFilterEnabled) && (
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
💡
</p>
)}
</div>
{/* 重置Token按钮 */}