您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
管理岐黄天使学习平台的每日学习上限并更新UI显示
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/537107/1595129/dailyLimitManager.js
// ==UserScript== // @name 岐黄助手-每日上限管理器 // @namespace http://tampermonkey.net/ // @version 1.0.4 // 版本更新 // @description 管理岐黄天使学习平台的每日学习上限并更新UI显示 // @author AI 助手 // @match *://www.tcm512.com/* // @grant GM_setValue // @grant GM_getValue // ==/UserScript== (function() { 'use strict'; class DailyLimitManager { constructor(options = {}) { this.config = { limitVideos: options.limitVideos || 10, // 默认每天10个视频 resetHour: GM_getValue('qh_daily_limit_reset_hour', options.resetHour || 8), // 默认早上8点重置 selectors: options.selectors || { limitReachedHintBox: '.sc_tips_box[style*="display: block"]', // 网站提示达到上限的容器选择器 limitReachedTextHint: '.title', // 在上述容器中包含特定文本的元素 limitReachedTextContent: '每天最多只能学习10个视频', // 指示达到上限的具体文本 confirmButton: '.layui-layer-btn0' // "我知道了"按钮的选择器 }, onLimitReached: options.onLimitReached || function() { console.warn('[每日上限管理器] onLimitReached 回调未设置。'); }, onLimitReset: options.onLimitReset || function() { console.info('[每日上限管理器] 每日上限已重置。'); } }; this.state = { videosWatchedToday: GM_getValue('qh_videos_watched_today', 0), // 今天已观看视频数 lastResetDate: GM_getValue('qh_daily_limit_last_reset_date', new Date().toDateString()), // 上次重置日期 isLimitReached: GM_getValue('qh_daily_limit_reached', false), // 是否已达到上限 lastLimitDate: GM_getValue('qh_daily_limit_date', null), // 记录达到上限的日期 autoResetTimer: null, // 自动重置的setTimeout ID countdownInterval: null, // UI倒计时更新的setInterval ID pageCheckInterval: null, // 页面检查的setInterval ID }; this._checkAndResetOnNewDay(); // 初始化检查 if (this.state.isLimitReached) { this._scheduleAutoReset(); // 如果已达上限,确保已安排重置并启动倒计时 } this._updateUIDisplay(); // 初始化UI更新 console.log('[每日上限管理器] 初始化完成。状态:', JSON.parse(JSON.stringify(this.state))); } _getMillisecondsUntilReset() { const now = new Date(); let resetTime = new Date(); resetTime.setHours(this.config.resetHour, 0, 0, 0); if (now.getHours() >= this.config.resetHour) { // 如果当前时间已过重置小时,则安排到下一天 resetTime.setDate(resetTime.getDate() + 1); } return resetTime.getTime() - now.getTime(); } _formatCountdown(ms) { if (ms <= 0) return "00:00:00"; let seconds = Math.floor((ms / 1000) % 60); let minutes = Math.floor((ms / (1000 * 60)) % 60); let hours = Math.floor((ms / (1000 * 60 * 60)) % 24); hours = (hours < 10) ? "0" + hours : hours; minutes = (minutes < 10) ? "0" + minutes : minutes; seconds = (seconds < 10) ? "0" + seconds : seconds; return `${hours}:${minutes}:${seconds}`; } _updateUIDisplay() { if (typeof window.updateDailyLimitDisplay !== 'function') { // console.warn('[每日上限管理器] window.updateDailyLimitDisplay 函数不可用。'); return; } let statusText = ''; let countdownText = '--:--:--'; if (this.state.isLimitReached) { statusText = `已达上限 (${this.state.videosWatchedToday}/${this.config.limitVideos} 个视频)`; const msUntilReset = this._getMillisecondsUntilReset(); countdownText = this._formatCountdown(msUntilReset); } else { statusText = `今日已看 ${this.state.videosWatchedToday}/${this.config.limitVideos} 个视频`; const msUntilReset = this._getMillisecondsUntilReset(); countdownText = this._formatCountdown(msUntilReset); } if (this.config.limitVideos <= 0) { statusText = '无每日学习上限'; countdownText = '不适用'; // N/A } // 防抖机制:只在状态真正变化时更新UI和输出日志 const newDisplayState = `${statusText}|${countdownText}`; if (this.state.lastDisplayState !== newDisplayState) { this.state.lastDisplayState = newDisplayState; window.updateDailyLimitDisplay(statusText, countdownText); } } _checkAndResetOnNewDay() { const today = new Date().toDateString(); // console.log(`[每日上限管理器] 检查新的一天。今天是: ${today}, 上次重置: ${this.state.lastResetDate}`); if (this.state.lastResetDate !== today) { const now = new Date(); if (this.state.lastResetDate !== today && now.getHours() >= this.config.resetHour) { console.log('[每日上限管理器] 检测到新的一天且已过重置时间,正在重置每日上限。'); this.state.videosWatchedToday = 0; this.state.isLimitReached = false; this.state.lastResetDate = today; this.state.lastLimitDate = null; GM_setValue('qh_videos_watched_today', 0); GM_setValue('qh_daily_limit_reached', false); GM_setValue('qh_daily_limit_last_reset_date', today); GM_setValue('qh_daily_limit_date', null); if (this.state.autoResetTimer) { clearTimeout(this.state.autoResetTimer); this.state.autoResetTimer = null; } if (this.state.countdownInterval) { clearInterval(this.state.countdownInterval); this.state.countdownInterval = null; } this.config.onLimitReset(); this._scheduleAutoReset(); } else if (this.state.lastResetDate !== today && now.getHours() < this.config.resetHour) { if (!this.state.isLimitReached) { this.state.videosWatchedToday = 0; GM_setValue('qh_videos_watched_today', 0); this.state.lastResetDate = today; GM_setValue('qh_daily_limit_last_reset_date', today); console.log('[每日上限管理器] 新的一天,未到重置时间,学习上限未满。计数器已重置,等待今日的计划重置。'); } else { console.log('[每日上限管理器] 新的一天,未到重置时间,但学习上限已满。等待计划重置。'); } if (this.state.isLimitReached && !this.state.autoResetTimer) { this._scheduleAutoReset(); } } } this._updateUIDisplay(); } /** * 通过检查页面内容来判断是否已达到每日学习上限。 * @param {Document} docContext - 要检查的文档上下文 (主窗口或 iframe)。 * @returns {boolean} 如果检测到上限则为 true,否则为 false。 */ checkLimitReachedOnPage(docContext) { if (!docContext) { console.warn('[每日上限管理器] 在 checkLimitReachedOnPage 中 docContext 为空'); return false; } try { const hintBox = docContext.querySelector(this.config.selectors.limitReachedHintBox); if (hintBox) { const titleElement = hintBox.querySelector(this.config.selectors.limitReachedTextHint); if (titleElement && titleElement.textContent.includes(this.config.selectors.limitReachedTextContent)) { console.log('[每日上限管理器] 检测到每日上限提示:', titleElement.textContent); return true; } } } catch (e) { console.error('[每日上限管理器] 检查页面每日上限时出错:', e); } return false; } /** * 当一个视频被视为已观看时调用此方法,以可能更新上限状态。 */ notifyVideoWatched() { this._checkAndResetOnNewDay(); // 计数前确保日期是最新的 if (this.state.isLimitReached) { console.log('[每日上限管理器] 已达到上限,不增加视频计数。'); this._updateUIDisplay(); return; // 如果已通过程序设置上限,则不计数 } this.state.videosWatchedToday++; GM_setValue('qh_videos_watched_today', this.state.videosWatchedToday); console.log(`[每日上限管理器] 视频已观看。计数: ${this.state.videosWatchedToday}`); if (this.config.limitVideos > 0 && this.state.videosWatchedToday >= this.config.limitVideos) { console.log('[每日上限管理器] 视频观看数量已达上限。'); this.handleLimitReached(); // 处理达到上限的情况 } else { // 如果因为页面检测到上限而调用了 handleLimitReached,则 isLimitReached 可能已经是 true // 但如果仅通过计数达到上限,则在此处调用 const pageLimit = this.checkLimitReachedOnPage(document); // 检查当前页面是否也提示上限 if (pageLimit) { console.log('[每日上限管理器] 页面也检测到上限信息。'); this.handleLimitReached(); } } this._updateUIDisplay(); } /** * Handles the scenario when the daily limit is reached. * Sets internal state, informs UI, and schedules auto-reset. */ handleLimitReached() { // console.warn('[每日上限管理器] handleLimitReached 未完全实现。'); if (this.state.isLimitReached && this.state.lastLimitDate === new Date().toDateString()) { // 今天已经处理过并且记录了。 // 如果倒计时不存在或需要更新,则重新调度 if (!this.state.countdownInterval) { this._scheduleAutoReset(); // 确保倒计时启动 } this._updateUIDisplay(); // 确保UI是最新的 return; // 避免重复处理 } console.log('[每日上限管理器] 每日上限已通过页面检测或计数达到。'); this.state.isLimitReached = true; this.state.lastLimitDate = new Date().toDateString(); // 记录达到上限的日期 GM_setValue('qh_daily_limit_reached', true); GM_setValue('qh_daily_limit_date', this.state.lastLimitDate); this.config.onLimitReached(); // 调用外部回调 this._scheduleAutoReset(); // 安排自动重置并启动UI倒计时 this._updateUIDisplay(); // 更新UI显示 } /** * Starts a periodic check for the on-page limit hints. */ startPeriodicPageCheck(docContextRoot, interval = 300000) { // 默认5分钟检查一次 // console.warn('[每日上限管理器] startPeriodicPageCheck 未完全实现。'); if (this.state.pageCheckIntervalId) { clearInterval(this.state.pageCheckIntervalId); } this.state.lastLimitCheckTime = Date.now(); // 初始化上次检查时间 this.state.pageCheckIntervalId = setInterval(() => { const now = Date.now(); // 避免过于频繁的检查,例如当浏览器标签页在后台时定时器可能行为异常 if (now - this.state.lastLimitCheckTime > interval - 1000) { // 稍微提前一点,以防万一 this.state.lastLimitCheckTime = now; console.log('[每日上限管理器] 定期检查页面上的上限提示。'); if (this.checkLimitReachedOnPage(docContextRoot || document)) { console.log('[每日上限管理器] 通过定期检查在页面上检测到上限提示。'); this.handleLimitReached(); } else { // 如果页面不再提示上限,但脚本内部状态仍是 isLimitReached // 这可能意味着上限通过其他方式被解除了,或者之前是误报 // 暂时不自动重置,依赖于每日自动重置或手动重置 // console.log('[每日上限管理器] 定期检查未发现页面上限提示。'); } } }, Math.max(interval, 60000)); // 确保检查间隔至少为1分钟 console.log(`[每日上限管理器] 已启动页面上限提示的定期检查 (间隔: ${interval / 1000} 秒)。`); } stopPeriodicPageCheck() { if (this.state.pageCheckIntervalId) { clearInterval(this.state.pageCheckIntervalId); this.state.pageCheckIntervalId = null; console.log('[每日上限管理器] 已停止页面上限提示的定期检查。'); } } /** * Gets the current state of whether the limit is reached. * @returns {boolean} */ isLimitActive() { this._checkAndResetOnNewDay(); // 确保状态是最新的 return this.state.isLimitReached; } getVideosWatchedToday() { this._checkAndResetOnNewDay(); return this.state.videosWatchedToday; } getLimitVideos() { return this.config.limitVideos; } /** * Calculates and returns the countdown string to the next reset time. * @returns {string} Formatted countdown string (e.g., "HH:MM:SS") or "--:--:--" */ getCountdownString() { if (!this.state.isLimitReached) return "未达上限"; const msUntilReset = this._getMillisecondsUntilReset(); return this._formatCountdown(msUntilReset); } /** * Manually resets the daily limit. */ manualReset(callOnResetCallback = true) { // console.warn('[每日上限管理器] manualReset 未完全实现。'); console.log('[每日上限管理器] 手动重置已触发。'); this.state.videosWatchedToday = 0; this.state.isLimitReached = false; this.state.lastResetDate = new Date().toDateString(); // 将最后重置日期更新为今天 this.state.lastLimitDate = null; // 清除上次达到上限的日期 GM_setValue('qh_videos_watched_today', 0); GM_setValue('qh_daily_limit_reached', false); GM_setValue('qh_daily_limit_last_reset_date', this.state.lastResetDate); GM_setValue('qh_daily_limit_date', null); if (this.state.autoResetTimer) { clearTimeout(this.state.autoResetTimer); this.state.autoResetTimer = null; } if (this.state.countdownInterval) { clearInterval(this.state.countdownInterval); this.state.countdownInterval = null; } if (callOnResetCallback) { this.config.onLimitReset(); } this._scheduleAutoReset(); // 重新安排下一次自动重置并更新倒计时 this._updateUIDisplay(); console.log('[每日上限管理器] 手动重置后状态:', JSON.parse(JSON.stringify(this.state))); } /** * Updates the configured reset hour. * @param {number} hour - The new hour (0-23) for reset. */ setResetHour(hour) { // console.warn('[每日上限管理器] setResetHour 未完全实现。'); const hourInt = parseInt(hour, 10); if (!isNaN(hourInt) && hourInt >= 0 && hourInt <= 23) { console.log(`[每日上限管理器] 设置重置小时为: ${hourInt}`); this.config.resetHour = hourInt; GM_setValue('qh_daily_limit_reset_hour', hourInt); // 如果当前已达上限或只是普通情况,都需要重新计算并安排下一次重置 console.log('[每日上限管理器] 重置小时已更改,重新安排自动重置并更新UI。'); this._scheduleAutoReset(); // 会清除旧的timer和interval,并用新时间启动 this._updateUIDisplay(); // 立即更新UI倒计时 } else { console.error('[每日上限管理器] 提供了无效的重置小时:', hour); } } getResetHour() { return this.config.resetHour; } getState() { return JSON.parse(JSON.stringify(this.state)); // 返回状态的深拷贝 } setConfig(newConfig) { let changed = false; for (const key in newConfig) { if (Object.hasOwnProperty.call(this.config, key) && this.config[key] !== newConfig[key]) { this.config[key] = newConfig[key]; changed = true; } } if (changed) { console.log('[每日上限管理器] 配置已更新。正在检查是否需要调整上限状态...'); // 如果视频上限数量改变,可能需要重新评估 isLimitReached状态 if (newConfig.limitVideos !== undefined) { if (this.config.limitVideos > 0 && this.state.videosWatchedToday >= this.config.limitVideos) { if (!this.state.isLimitReached) { console.log('[每日上限管理器] 根据新配置,视频数量已达上限。'); this.handleLimitReached(); // 触发上限处理 } } else if (this.state.isLimitReached && (this.config.limitVideos <= 0 || this.state.videosWatchedToday < this.config.limitVideos)) { // 如果之前是上限状态,但新配置允许更多视频或无限制,则解除上限 console.log('[每日上限管理器] 根据新配置,视频数量未达上限或无限制。解除上限状态。'); this.manualReset(true); // 使用manualReset来重置状态并触发回调和重新调度 } } if (newConfig.resetHour !== undefined) { this.setResetHour(newConfig.resetHour); // 调用setResetHour来处理重置小时的变更逻辑 } this._updateUIDisplay(); console.log('[每日上限管理器] 配置更新完毕:', this.config); } else { console.log('[每日上限管理器] 无配置变更。'); } } // --- Internal methods --- _scheduleAutoReset() { if (this.state.autoResetTimer) { clearTimeout(this.state.autoResetTimer); this.state.autoResetTimer = null; } if (this.state.countdownInterval) { clearInterval(this.state.countdownInterval); this.state.countdownInterval = null; } const msUntilReset = this._getMillisecondsUntilReset(); console.log(`[每日上限管理器] 计划在 ${msUntilReset / 1000 / 60} 分钟后自动重置。`); this.state.autoResetTimer = setTimeout(() => { console.log('[每日上限管理器] 自动每日上限重置已触发。'); this._checkAndResetOnNewDay(); // 这个方法会处理实际的重置逻辑和回调 // _checkAndResetOnNewDay 会调用 _updateUIDisplay 和 _scheduleAutoReset (如果需要再次调度) }, msUntilReset); // 启动UI倒计时更新(降低频率,减少日志输出) this.state.countdownInterval = setInterval(() => { this._updateUIDisplay(); // 每30秒更新倒计时显示 }, 30000); // 从1秒改为30秒 this._updateUIDisplay(); // 立即更新一次UI } } // 确保 qh 对象存在 window.qh = window.qh || {}; window.qh.DailyLimitManager = DailyLimitManager; // 导出全局函数供UI模块使用 window.updateDailyLimitDisplay = function(statusText, countdownText) { // 减少日志输出频率,只在debug模式下输出 if (window.qh && window.qh.debugMode) { console.log('[每日上限管理器] 全局 updateDailyLimitDisplay 被调用:', statusText, countdownText); } // 尝试调用UI模块的函数 if (window.qh && typeof window.qh.updateDailyLimitDisplay === 'function') { window.qh.updateDailyLimitDisplay(statusText, countdownText); } else { // 直接更新DOM元素 const limitStatusEl = document.getElementById('qh-daily-limit-status'); const countdownEl = document.getElementById('qh-daily-limit-countdown'); if (limitStatusEl) { limitStatusEl.textContent = statusText; } if (countdownEl) { countdownEl.textContent = countdownText; } } }; console.log('[模块加载] dailyLimitManager 模块已加载, 版本 1.0.2'); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址