// ==UserScript==
// @name B站学习监督助手
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 只允许访问指定UP主的视频,其他显示全屏学习警告,支持自定义倒计时和详细日志
// @author FCY
// @match *://www.bilibili.com/video/*
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @run-at document-idle
// @require https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js
// @resource fontAwesome https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
// 调试模式开关(设为false可关闭日志)
const DEBUG_MODE = false;
function log(...args) {
if (DEBUG_MODE) {
console.log("[B站学习监督助手]", ...args);
}
}
log("脚本已加载,版本8.4");
// ====== 全局变量 ======
let videoTimeTrackingInterval = null; // 视频播放时间跟踪计时器
let lastVideoPlayTime = 0; // 上次视频播放时间(用于计算增量)
let isTrackingActive = false; // 跟踪状态标志
// ====== 默认配置 ======
const defaultConfig = {
showCountdown: true,
countdownTitle: "考试",
examDate: getDefaultDate(),
warningMessages: [
"刷B站一时爽,考试挂科泪两行!",
"你收藏夹里的学习视频都看完了吗?",
"说好的只看10分钟呢?已经过去2小时了!",
"题都刷完了吗?还在这看视频!",
"再刷B站考试就要挂科了!",
"想想你的目标,放下手机去学习!",
"今日的懈怠,明日的懊悔!",
"你离梦想只差一个专注的学习时间!",
"现在每学一分钟,考试多拿一分!",
"沉迷B站,考试完蛋!快去看书!"
],
allowedUpIds: [],
// 新增:每日观看时长限制(分钟)
dailyWatchLimit: 35
};
// 获取默认日期(下个月的今天)
function getDefaultDate() {
const now = new Date();
const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, now.getDate());
return nextMonth.toISOString().split('T')[0];
}
// 加载配置
let config = GM_getValue('config', JSON.parse(JSON.stringify(defaultConfig)));
log("配置加载完成", config);
// 注册菜单命令
GM_registerMenuCommand("学习监督设置", openSettingsPanel);
// 新增:注册非白名单观看时长菜单命令
GM_registerMenuCommand("查看非白名单观看时长", showWatchTimeInfo);
// 新增:显示非白名单观看时长信息
function showWatchTimeInfo() {
const watchedSeconds = getTodayWatchedSeconds();
const watchedMinutes = Math.floor(watchedSeconds / 60);
const remainingSeconds = watchedSeconds % 60;
const limitMinutes = config.dailyWatchLimit;
const remainingMinutes = Math.max(0, limitMinutes - watchedMinutes);
alert(`📊 今日非白名单观看时长统计:
⏱️ 已观看: ${watchedMinutes}分${remainingSeconds}秒
⏳ 剩余: ${remainingMinutes}分钟
📏 每日限额: ${limitMinutes}分钟`);
}
// 检测当前页面是否为视频页面
function isVideoPage() {
const isVideo = window.location.pathname.startsWith('/video/');
log("当前页面是否为视频页面:", isVideo);
return isVideo;
}
// 获取今日日期字符串(YYYY-MM-DD)
function getTodayDateString() {
const now = new Date();
return now.toISOString().split('T')[0];
}
// 初始化今日观看时间
function initTodayWatchTime() {
const today = getTodayDateString();
const watchTimeData = GM_getValue('watchTimeData', {});
// 如果今天的数据不存在或日期已变更,重置为0
if (!watchTimeData[today]) {
watchTimeData[today] = 0;
GM_setValue('watchTimeData', watchTimeData);
}
return watchTimeData;
}
// 获取今日已观看时间(秒)
function getTodayWatchedSeconds() {
const watchTimeData = GM_getValue('watchTimeData', {});
const today = getTodayDateString();
return watchTimeData[today] || 0;
}
// 更新今日观看时间
function updateTodayWatchTime(seconds) {
const watchTimeData = GM_getValue('watchTimeData', {});
const today = getTodayDateString();
watchTimeData[today] = seconds;
GM_setValue('watchTimeData', watchTimeData);
}
// 重置今日观看时间
function resetTodayWatchTime() {
const watchTimeData = GM_getValue('watchTimeData', {});
const today = getTodayDateString();
watchTimeData[today] = 0;
GM_setValue('watchTimeData', watchTimeData);
log("今日观看时间已重置");
// 显示提示
alert("今日观看时间已重置为0分钟");
}
// 开始跟踪视频播放时间(非白名单UP主)
function startVideoTimeTracking() {
log("开始跟踪视频播放时间...");
// 先清除之前的计时器(如果有)
stopVideoTimeTracking();
// 获取视频元素
const video = document.querySelector('video');
if (!video) {
log("未找到视频元素,无法跟踪播放时间");
return;
}
// 初始化上次播放时间
lastVideoPlayTime = Date.now();
isTrackingActive = true;
// 每秒钟更新一次
videoTimeTrackingInterval = setInterval(() => {
// 如果视频正在播放
if (!video.paused && !video.ended) {
const now = Date.now();
const deltaSeconds = Math.floor((now - lastVideoPlayTime) / 1000);
lastVideoPlayTime = now;
if (deltaSeconds > 0) {
// 更新今日观看时间
const watchedSeconds = getTodayWatchedSeconds() + deltaSeconds;
updateTodayWatchTime(watchedSeconds);
log(`更新观看时间:${watchedSeconds}秒`);
// 检查是否超过限制
const limitSeconds = config.dailyWatchLimit * 60;
if (watchedSeconds >= limitSeconds) {
log("今日观看时长已达上限,显示警告页面");
replaceEntirePage(true);
}
}
}
}, 1000);
}
// 停止跟踪视频播放时间
function stopVideoTimeTracking() {
if (videoTimeTrackingInterval) {
clearInterval(videoTimeTrackingInterval);
videoTimeTrackingInterval = null;
isTrackingActive = false;
log("已停止视频播放时间跟踪");
}
}
// 主入口
if (isVideoPage()) {
log("检测到视频页面,开始初始化...");
// 初始化今日观看时间
initTodayWatchTime();
// 初始检查
startVideoCheck();
// 设置核心监听器
setupCoreListeners();
}
// 设置核心监听器
function setupCoreListeners() {
log("设置核心监听器...");
// 监听B站SPA路由变化事件
window.addEventListener('popstate', handlePageChange);
window.addEventListener('pushState', handlePageChange);
window.addEventListener('replaceState', handlePageChange);
// 监听视频切换事件
setupVideoChangeListener();
// 监听DOM变化 - 这是关键改进
setupDOMMutationObserver();
}
// 启动视频检查
function startVideoCheck() {
log("开始视频检查...");
// 尝试立即检查
const upElement = getUpElement();
if (upElement) {
log("UP元素已存在,立即检查");
checkUpPermission();
return;
}
log("UP元素不存在,等待DOM变化...");
// 设置超时以防元素永远不出现
setTimeout(() => {
const targetElement = getUpElement();
if (!targetElement) {
log("等待UP元素超时,尝试检查");
checkUpPermission();
}
}, 3000);
}
// 设置DOM变化监听器
function setupDOMMutationObserver() {
log("设置DOM变化监听器...");
// 创建MutationObserver实例
const observer = new MutationObserver(mutations => {
for (const mutation of mutations) {
// 检查是否有新增的节点包含UP主信息
if (mutation.addedNodes.length > 0) {
for (const node of mutation.addedNodes) {
if (node.nodeType === 1 && (node.matches('.up-detail-top, .up-info, .upname') ||
node.querySelector('.up-detail-top, .up-info, .upname'))) {
log("检测到UP元素变化");
handlePageChange();
return;
}
}
}
}
});
// 开始观察整个文档的变化
observer.observe(document.documentElement, {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
log("DOM变化监听器已激活");
}
// 设置视频切换监听
function setupVideoChangeListener() {
log("设置视频切换监听...");
// 监听推荐视频点击
document.body.addEventListener('click', function(event) {
const target = event.target;
if (target.closest('.recommend-card') ||
target.closest('.video-card') ||
target.closest('.bili-video-card')) {
log("检测到推荐视频点击");
setTimeout(() => {
log("执行视频切换检查");
startVideoCheck();
}, 1200);
}
});
// 监听播放器事件 - 优化后的版本
function setupPlayerEvents() {
log("尝试设置播放器事件监听...");
// 尝试获取视频元素
const video = document.querySelector('video');
if (video) {
log("找到视频元素,添加播放状态监听");
// 清除旧的事件监听器
video.removeEventListener('play', handleVideoPlay);
video.removeEventListener('pause', handleVideoPause);
video.removeEventListener('ended', handleVideoEnded);
// 添加新的事件监听器
video.addEventListener('play', handleVideoPlay);
video.addEventListener('pause', handleVideoPause);
video.addEventListener('ended', handleVideoEnded);
// 如果视频已经在播放,检查是否需要开始计时
if (!video.paused && !video.ended) {
log("视频已在播放状态,检查计时");
const currentUpId = getCurrentUpId();
if (currentUpId && !isUpAllowed(currentUpId)) {
if (!isTrackingActive) {
log("非白名单视频正在播放,但未计时,启动计时");
startVideoTimeTracking();
} else {
log("非白名单视频正在播放,计时已激活");
}
}
}
} else {
log("未找到视频元素,稍后重试");
// 如果未找到视频元素,稍后重试
setTimeout(setupPlayerEvents, 1000);
}
}
// 视频播放事件处理
function handleVideoPlay() {
log("视频开始播放");
handleVideoEvent();
// 如果是非白名单视频,开始计时
const currentUpId = getCurrentUpId();
if (currentUpId && !isUpAllowed(currentUpId)) {
log("非白名单视频开始播放,启动计时");
startVideoTimeTracking();
}
}
// 视频暂停事件处理
function handleVideoPause() {
log("视频暂停");
handleVideoEvent();
stopVideoTimeTracking();
}
// 视频结束事件处理
function handleVideoEnded() {
log("视频结束");
handleVideoEvent();
stopVideoTimeTracking();
}
// 初始设置播放器事件
setupPlayerEvents();
// 添加额外的重试机制
const playerObserver = new MutationObserver(() => {
log("检测到DOM变化,重新设置播放器事件");
setupPlayerEvents();
});
playerObserver.observe(document.body, {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
// 视频事件处理
function handleVideoEvent() {
log("检测到视频状态变化");
clearTimeout(window.videoCheckTimeout);
window.videoCheckTimeout = setTimeout(() => {
log("执行视频事件检查");
startVideoCheck();
}, 800);
}
// 页面变化处理
function handlePageChange() {
log("检测到页面变化");
// 停止任何进行中的时间跟踪
stopVideoTimeTracking();
if (isVideoPage()) {
log("当前是视频页面,开始检查...");
// 防止重复检查
clearTimeout(window.videoCheckTimeout);
window.videoCheckTimeout = setTimeout(() => {
startVideoCheck();
}, 800);
}
}
// 获取UP元素
function getUpElement() {
return document.querySelector('.up-detail-top') ||
document.querySelector('.up-info') ||
document.querySelector('.upname') ||
document.querySelector('.up-detail') ||
document.querySelector('.up-info-container');
}
// 获取当前UP主ID
function getCurrentUpId() {
log("开始获取UP主ID...");
// 尝试多种选择器获取UP主信息
let upLink = document.querySelector('a.up-name') ||
document.querySelector('.up-info .name') ||
document.querySelector('.username') ||
document.querySelector('.up-name') ||
document.querySelector('.up-info_name');
if (!upLink) {
log("未找到UP主链接元素");
// 尝试最后一种方法:通过UP主头像容器查找
const avatarContainer = document.querySelector('.up-detail-top') ||
document.querySelector('.up-info') ||
document.querySelector('.up-avatar') ||
document.querySelector('.up-info_avatar');
if (avatarContainer && avatarContainer.querySelector('a')) {
upLink = avatarContainer.querySelector('a');
log("通过头像容器找到UP主链接");
}
}
if (!upLink) {
log("无法找到UP主信息");
return null;
}
const href = upLink.getAttribute('href') || "";
log("UP主链接:", href);
// 提取UP主ID
let currentUpId = null;
const upIdMatch = href.match(/space\.bilibili\.com\/(\d+)/);
if (upIdMatch && upIdMatch.length >= 2) {
currentUpId = upIdMatch[1];
} else {
// 尝试从URL中直接提取数字ID
const numMatch = href.match(/\/(\d+)/);
if (numMatch && numMatch.length >= 2) {
currentUpId = numMatch[1];
}
}
if (!currentUpId) {
log("无法从链接中提取UP主ID:", href);
return null;
}
log("当前UP主ID:", currentUpId);
return currentUpId;
}
// 检查UP主是否在白名单
function isUpAllowed(upId) {
return config.allowedUpIds.some(up => up.id === upId);
}
// 检测UP主权限 - 修复后的版本
function checkUpPermission() {
log("开始检查UP权限...");
const currentUpId = getCurrentUpId();
if (!currentUpId) {
log("无法获取UP主ID,跳过检查");
return;
}
log("允许的UP主列表:", config.allowedUpIds.map(u => u.id));
// 检查是否在允许列表中
const isAllowed = isUpAllowed(currentUpId);
log("权限检查结果:", isAllowed ? "允许" : "禁止");
// 处理非白名单UP主 - 修复逻辑
if (!isAllowed) {
const watchedSeconds = getTodayWatchedSeconds();
const limitSeconds = config.dailyWatchLimit * 60;
log(`今日已观看非白名单视频: ${watchedSeconds}秒, 限制: ${limitSeconds}秒`);
if (watchedSeconds >= limitSeconds) {
log("今日观看时长已达上限,显示警告页面");
replaceEntirePage(true); // 显示时间限制警告
return;
} else {
log("未超过每日观看时长,允许观看");
removeWarningPage(); // 确保移除警告页面
// 检查视频是否正在播放,如果是则开始计时
const video = document.querySelector('video');
if (video && !video.paused && !video.ended) {
log("非白名单视频正在播放,启动计时");
startVideoTimeTracking();
} else {
log("非白名单视频未在播放,等待播放事件");
}
return;
}
}
// 白名单UP主处理
log("视频在白名单中,允许观看");
removeWarningPage();
}
// 移除警告页面
function removeWarningPage() {
if (document.getElementById('study-warning-page')) {
log("移除警告页面");
window.location.reload();
}
}
// 替换整个页面为警告页面
function replaceEntirePage(timeLimitExceeded = false) {
log("创建警告页面...", timeLimitExceeded ? "(时间限制)" : "");
// 停止任何进行中的时间跟踪
stopVideoTimeTracking();
// 检查是否已经创建了警告页面
if (document.getElementById('study-warning-page')) {
log("警告页面已存在,跳过创建");
return;
}
// 创建全屏警告页面
const warningPage = document.createElement('div');
warningPage.id = 'study-warning-page';
// 随机选择一条警告信息
let randomMessage;
if (timeLimitExceeded) {
randomMessage = `今日非学习UP主观看时长已达上限(${config.dailyWatchLimit}分钟)!快去学习!`;
} else {
randomMessage = config.warningMessages[Math.floor(Math.random() * config.warningMessages.length)];
}
// 生成倒计时HTML(如果启用)
const countdownHTML = config.showCountdown ? `
距离${config.countdownTitle}还有:
00:00:00
` : '';
// 新增:今日观看时间显示
const watchedSeconds = getTodayWatchedSeconds();
const watchedMinutes = Math.floor(watchedSeconds / 60);
const remainingSeconds = watchedSeconds % 60;
const limitMinutes = config.dailyWatchLimit;
warningPage.innerHTML = `
⚠️📚⏰
${randomMessage}
今日已观看: ${watchedMinutes}分${remainingSeconds}秒
每日限额: ${limitMinutes}分钟
${countdownHTML}
"学习如逆水行舟,不进则退;心似平原走马,易放难收"
`;
// 替换整个页面
document.body.innerHTML = '';
document.body.appendChild(warningPage);
// 添加样式
addStyles();
// 添加粒子效果
createParticles();
// 如果启用倒计时,初始化倒计时
if (config.showCountdown) {
updateCountdown();
setInterval(updateCountdown, 1000);
}
// 添加设置按钮事件
document.getElementById('study-settings-btn').addEventListener('click', openSettingsPanel);
log("警告页面创建完成");
// 在警告页面中也需要监听视频切换
setupVideoChangeListener();
}
// 添加样式
function addStyles() {
GM_addStyle(`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
color: white;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
padding: 20px;
}
#study-warning-page .container {
max-width: 1200px;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 30px;
z-index: 10;
}
#study-warning-page .header {
text-align: center;
// margin-bottom: 10px;
}
#study-warning-page h1 {
font-size: 3rem;
margin-bottom: 10px;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
}
#study-warning-page .subtitle {
font-size: 1.3rem;
opacity: 0.9;
max-width: 600px;
margin: 0 auto 15px;
}
#study-warning-page .debug-info {
background: rgba(255, 255, 255, 0.1);
padding: 5px 15px;
border-radius: 20px;
font-size: 0.9rem;
margin-top: 10px;
display: inline-block;
}
#study-warning-page .main-content {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 25px;
width: 100%;
}
#study-warning-page .warning-section {
flex: 1;
min-width: 300px;
max-width: 600px;
background: rgba(0, 0, 0, 0.4);
border-radius: 20px;
padding: 30px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
text-align: center;
border: 3px solid #ffcc00;
animation: glow 2s infinite alternate;
}
#study-warning-page .warning-icon {
font-size: 5rem;
margin-bottom: 20px;
animation: pulse 2s infinite;
}
#study-warning-page .warning-message {
font-size: 1.8rem;
font-weight: bold;
margin: 25px 0;
line-height: 1.6;
}
/* 新增:时间统计样式 */
#study-warning-page .time-stats {
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 15px;
margin: 20px 0;
border: 2px solid #3498db;
}
#study-warning-page .time-stat {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
margin: 10px 0;
font-size: 1.3rem;
}
#study-warning-page .time-stat i {
color: #3498db;
}
#study-warning-page .countdown {
background: rgba(0, 0, 0, 0.5);
border-radius: 15px;
padding: 20px;
margin: 25px 0;
border: 2px solid #ff5252;
}
#study-warning-page .countdown-title {
font-size: 1.4rem;
margin-bottom: 15px;
color: #ffcc00;
}
#study-warning-page #timer {
font-size: 2.2rem;
font-family: 'Courier New', monospace;
font-weight: bold;
letter-spacing: 3px;
color: #4cff00;
}
#study-warning-page .quote {
font-style: italic;
margin-top: 25px;
font-size: 1.2rem;
opacity: 0.9;
}
#study-warning-page .info-section {
display: flex;
flex-wrap: wrap;
gap: 20px;
width: 100%;
max-width: 800px;
}
#study-warning-page .info-card {
flex: 1;
min-width: 250px;
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 20px;
text-align: center;
border: 2px solid #00bfff;
transition: all 0.3s;
}
#study-warning-page .info-card:hover {
transform: translateY(-5px);
background: rgba(255, 255, 255, 0.15);
}
#study-warning-page .info-card i {
font-size: 3rem;
margin-bottom: 15px;
color: #00bfff;
}
#study-warning-page .info-card h3 {
font-size: 1.5rem;
margin-bottom: 10px;
}
#study-warning-page .info-card p {
font-size: 1rem;
opacity: 0.9;
}
#study-warning-page .settings-btn {
position: fixed;
bottom: 25px;
right: 25px;
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #6a11cb, #2575fc);
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: white;
cursor: pointer;
z-index: 100;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
transition: all 0.3s;
}
#study-warning-page .settings-btn:hover {
transform: scale(1.1) rotate(30deg);
}
#study-warning-page .particles {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
}
#study-warning-page .particle {
position: absolute;
background-color: rgba(255, 255, 255, 0.7);
border-radius: 50%;
animation: float linear infinite;
}
/* Animations */
@keyframes pulse {
0% { transform: scale(1); opacity: 0.8; }
50% { transform: scale(1.1); opacity: 1; }
100% { transform: scale(1); opacity: 0.8; }
}
@keyframes glow {
0% { box-shadow: 0 0 15px #ffcc00; }
100% { box-shadow: 0 0 40px #ffcc00; }
}
@keyframes float {
to {
transform: translateY(-100px) rotate(360deg);
opacity: 0;
}
}
/* Responsive design */
@media (max-width: 768px) {
#study-warning-page h1 {
font-size: 2.2rem;
}
#study-warning-page .subtitle {
font-size: 1.1rem;
}
#study-warning-page .warning-message {
font-size: 1.5rem;
}
#study-warning-page #timer {
font-size: 1.8rem;
}
#study-warning-page .warning-icon {
font-size: 4rem;
}
#study-warning-page .time-stat {
font-size: 1.1rem;
}
}
@media (max-width: 480px) {
#study-warning-page .warning-section {
padding: 20px;
}
#study-warning-page h1 {
font-size: 1.8rem;
}
#study-warning-page .warning-message {
font-size: 1.3rem;
}
#study-warning-page #timer {
font-size: 1.5rem;
}
#study-warning-page .info-card {
min-width: 100%;
}
#study-warning-page .time-stat {
font-size: 1rem;
}
}
`);
}
// 创建粒子效果
function createParticles() {
const particlesContainer = document.getElementById('particles');
if (!particlesContainer) return;
const particleCount = 50;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
particle.classList.add('particle');
// 随机大小
const size = Math.random() * 10 + 5;
particle.style.width = `${size}px`;
particle.style.height = `${size}px`;
// 随机位置
particle.style.left = `${Math.random() * 100}%`;
particle.style.top = `${Math.random() * 100}%`;
// 随机颜色
const colors = ['#ffcc00', '#ff5252', '#4cff00', '#00bfff'];
particle.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
// 随机动画
const duration = Math.random() * 10 + 5;
const delay = Math.random() * 5;
particle.style.animation = `float ${duration}s linear ${delay}s infinite`;
particlesContainer.appendChild(particle);
}
}
// 更新倒计时
function updateCountdown() {
const timer = document.getElementById('timer');
if (!timer) return;
const examDateObj = new Date(config.examDate);
const now = new Date();
const diff = examDateObj - now;
if (diff <= 0) {
timer.textContent = `${config.countdownTitle}已开始!`;
return;
}
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
timer.textContent =
`${days}天 ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
// 打开设置面板
function openSettingsPanel() {
// 创建设置面板容器
const panel = document.createElement('div');
panel.id = 'study-settings-panel';
panel.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
max-width: 700px;
max-height: 90vh;
background: white;
border-radius: 15px;
box-shadow: 0 10px 50px rgba(0, 0, 0, 0.3);
z-index: 100000;
overflow: auto;
padding: 25px;
color: #333;
`;
// 计算今日观看时间
const watchedSeconds = getTodayWatchedSeconds();
const watchedMinutes = Math.floor(watchedSeconds / 60);
const watchedSecs = watchedSeconds % 60;
const watchedTimeStr = `${watchedMinutes}分${watchedSecs}秒`;
const remainingMinutes = Math.max(0, config.dailyWatchLimit - watchedMinutes);
const remainingTimeStr = `${remainingMinutes}分钟`;
// 面板内容
panel.innerHTML = `
学习监督设置
非白名单观看时长统计
${config.dailyWatchLimit}分钟
每日限额
`;
// 添加到文档
document.body.appendChild(panel);
// 添加遮罩层
const overlay = document.createElement('div');
overlay.id = 'study-settings-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 99999;
`;
document.body.appendChild(overlay);
// 事件监听
document.getElementById('study-add-up-btn').addEventListener('click', addUpFromPanel);
document.getElementById('study-save-btn').addEventListener('click', saveSettings);
document.getElementById('study-reset-btn').addEventListener('click', resetSettings);
document.getElementById('study-close-btn').addEventListener('click', closeSettingsPanel);
document.getElementById('show-countdown').addEventListener('change', toggleCountdownSettings);
document.getElementById('reset-today-watch').addEventListener('click', resetTodayWatchTime);
overlay.addEventListener('click', closeSettingsPanel);
// 添加删除事件
document.querySelectorAll('.btn-delete').forEach(btn => {
btn.addEventListener('click', function() {
const item = this.closest('.up-item');
const id = item.getAttribute('data-id');
config.allowedUpIds = config.allowedUpIds.filter(up => up.id !== id);
item.remove();
// 如果没有UP主了,显示空状态
if (document.querySelectorAll('.up-item').length === 0) {
document.getElementById('study-up-list').innerHTML = `
`;
}
});
});
// 从面板添加UP主
function addUpFromPanel() {
const upLink = document.getElementById('study-up-link').value.trim();
const upName = document.getElementById('study-up-name').value.trim();
if (!upLink) {
alert('请输入UP主主页链接');
return;
}
if (!upName) {
alert('请输入UP主昵称');
return;
}
// 从链接中提取UP主ID
const idMatch = upLink.match(/space\.bilibili\.com\/(\d+)/);
let upId = null;
if (idMatch && idMatch.length >= 2) {
upId = idMatch[1];
} else {
// 尝试从URL中直接提取数字ID
const numMatch = upLink.match(/\/(\d+)/);
if (numMatch && numMatch.length >= 2) {
upId = numMatch[1];
}
}
if (!upId) {
alert('无法从链接中提取UP主ID,请确保链接格式正确');
return;
}
// 检查是否已存在
if (config.allowedUpIds.some(up => up.id === upId)) {
alert('该UP主已在允许列表中');
return;
}
// 添加到列表
config.allowedUpIds.push({
id: upId,
name: upName,
url: upLink
});
// 清空输入框
document.getElementById('study-up-link').value = '';
document.getElementById('study-up-name').value = '';
// 添加到列表显示
const upItem = document.createElement('div');
upItem.className = 'up-item';
upItem.setAttribute('data-id', upId);
upItem.innerHTML = `
`;
// 添加删除事件
upItem.querySelector('.btn-delete').addEventListener('click', function() {
const id = upItem.getAttribute('data-id');
config.allowedUpIds = config.allowedUpIds.filter(up => up.id !== id);
upItem.remove();
});
// 移除空状态(如果存在)
const emptyState = document.querySelector('.empty-state');
if (emptyState) emptyState.remove();
document.getElementById('study-up-list').appendChild(upItem);
// 显示成功消息
alert(`已添加UP主: ${upName} (ID: ${upId})`);
}
// 切换倒计时设置显示
function toggleCountdownSettings() {
const showCountdown = document.getElementById('show-countdown').checked;
document.getElementById('countdown-settings').style.display = showCountdown ? 'block' : 'none';
}
// 保存设置
function saveSettings() {
// 更新倒计时设置
config.showCountdown = document.getElementById('show-countdown').checked;
config.countdownTitle = document.getElementById('countdown-title').value || "考试";
config.examDate = document.getElementById('exam-date').value || getDefaultDate();
// 更新每日观看时长
const dailyLimit = parseInt(document.getElementById('daily-watch-limit').value);
config.dailyWatchLimit = isNaN(dailyLimit) ? 30 : Math.max(1, dailyLimit);
// 更新警告消息
const messagesText = document.getElementById('warning-messages').value;
config.warningMessages = messagesText.split('\n').filter(msg => msg.trim().length > 0);
// 保存配置
GM_setValue('config', config);
alert('设置已保存!');
closeSettingsPanel();
location.reload(); // 重新加载页面应用新设置
}
// 恢复默认设置
function resetSettings() {
if (confirm('确定要恢复默认设置吗?所有自定义设置将被重置。')) {
config = JSON.parse(JSON.stringify(defaultConfig));
GM_setValue('config', config);
alert('已恢复默认设置!');
closeSettingsPanel();
location.reload();
}
}
// 关闭设置面板
function closeSettingsPanel() {
const panel = document.getElementById('study-settings-panel');
const overlay = document.getElementById('study-settings-overlay');
if (panel) panel.remove();
if (overlay) overlay.remove();
}
}
})();