您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
检测 hifa.shuoguoyun.com 页面中视频暂停,通过聚合推送通知(Token内置),并在右侧面板显示状态和控制通知。
// ==UserScript== // @name 特定页面视频暂停检测 // @namespace http://tampermonkey.net/ // @version 0.7 // @description 检测 hifa.shuoguoyun.com 页面中视频暂停,通过聚合推送通知(Token内置),并在右侧面板显示状态和控制通知。 // @author Your Name // @match *://hifa.shuoguoyun.com/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @connect tui.juhe.cn // @license MPL-2.0 License // ==/UserScript== (function() { 'use strict'; const VIDEO_ID = 'bjy-player-teacher'; const PUSH_API_URL = 'https://tui.juhe.cn/api/plus/pushApi'; const SERVICE_ID = 'HsCOtSZ'; const USER_TOKEN = ''; // <<< 在这里设置您的固定Token let videoElement = null; let eventListenerAttached = false; let statusPanel = null; let statusTextElement = null; let lastStatusText = ''; let notificationToggle = null; // --- 节流函数 --- function throttle(func, limit) { let inThrottle; return function() { const args = arguments; const context = this; if (!inThrottle) { func.apply(context, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } } } // --- 注入CSS样式 --- GM_addStyle(` #videoStatusPanel_userscript { position: fixed; top: 70%; right: 0; transform: translateY(-50%); background-color: rgba(50, 50, 50, 0.85); color: white; padding: 10px 15px; border-top-left-radius: 8px; border-bottom-left-radius: 8px; z-index: 999999; font-family: Arial, sans-serif; font-size: 13px; box-shadow: -2px 0px 5px rgba(0,0,0,0.4); text-align: left; min-width: 150px; /* 可以适当减小宽度 */ display: flex; flex-direction: column; gap: 8px; } #videoStatusPanel_userscript div { margin-bottom: 3px; } #videoStatusText_userscript { font-weight: bold; color: #FFD700; text-align: center; padding: 8px 0; /* 增加一点上下padding */ border-top: 1px solid #777; margin-top: 8px; /* 增加与上方元素的间距 */ } #videoStatusPanel_userscript label { display: inline-block; margin-right: 8px; color: #ddd; } #videoStatusPanel_userscript input[type="checkbox"] { vertical-align: middle; } #videoStatusPanel_userscript .panel-title { text-align: center; font-weight: bold; padding-bottom: 5px; border-bottom: 1px solid #777; color: #eee; } #videoStatusPanel_userscript .input-group { display: flex; align-items: center; } `); // --- 创建状态面板 --- function createStatusPanel() { if (document.getElementById('videoStatusPanel_userscript')) { statusPanel = document.getElementById('videoStatusPanel_userscript'); statusTextElement = document.getElementById('videoStatusText_userscript'); notificationToggle = document.getElementById('notificationToggle_userscript'); return; } statusPanel = document.createElement('div'); statusPanel.id = 'videoStatusPanel_userscript'; const panelTitleElement = document.createElement('div'); panelTitleElement.className = 'panel-title'; panelTitleElement.textContent = '视频监控'; statusPanel.appendChild(panelTitleElement); // 通知开关 const notificationGroup = document.createElement('div'); notificationGroup.className = 'input-group'; const notificationLabel = document.createElement('label'); notificationLabel.htmlFor = 'notificationToggle_userscript'; notificationLabel.textContent = '开启暂停通知:'; notificationToggle = document.createElement('input'); notificationToggle.type = 'checkbox'; notificationToggle.id = 'notificationToggle_userscript'; notificationToggle.checked = GM_getValue('notificationEnabled_v07', false); // 使用新key避免与旧版本冲突 notificationToggle.addEventListener('change', (event) => { GM_setValue('notificationEnabled_v07', event.target.checked); console.log('通知状态已保存:', event.target.checked); if (event.target.checked && !USER_TOKEN) { // 检查硬编码的TOKEN alert('脚本内未配置有效的USER_TOKEN!'); } }); notificationGroup.appendChild(notificationLabel); notificationGroup.appendChild(notificationToggle); statusPanel.appendChild(notificationGroup); // 状态显示 statusTextElement = document.createElement('div'); statusTextElement.id = 'videoStatusText_userscript'; statusTextElement.textContent = '检测中...'; statusPanel.appendChild(statusTextElement); document.body.appendChild(statusPanel); console.log('状态面板已创建 (Token内置)。'); } function updateStatusPanelText(status) { if (statusTextElement && lastStatusText !== status) { statusTextElement.textContent = status; lastStatusText = status; } } // --- 发送通知 --- function sendPushNotification(title, content) { if (!USER_TOKEN) { console.error('错误:USER_TOKEN 未在脚本中定义!'); updateStatusPanelText('状态: Token未配置!'); alert('脚本内未配置有效的USER_TOKEN,无法发送通知。'); return; } const params = new URLSearchParams(); params.append('token', USER_TOKEN); params.append('title', title); params.append('content', content); params.append('service_id', SERVICE_ID); console.log('发送推送通知:', { title, content, service_id: SERVICE_ID }); GM_xmlhttpRequest({ method: 'POST', url: PUSH_API_URL, headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, data: params.toString(), onload: function(response) { console.log('推送通知响应:', response.responseText); try { const jsonResponse = JSON.parse(response.responseText); if (jsonResponse.code === 200 || jsonResponse.status === true || (typeof jsonResponse.message === 'string' && jsonResponse.message.includes("成功"))) { console.log('通知发送成功!'); updateStatusPanelText('状态: 已暂停 (通知成功)'); } else { console.error('通知发送失败:', jsonResponse.message || '未知错误'); updateStatusPanelText(`状态: 暂停 (通知失败: ${jsonResponse.message || ''})`); alert(`通知发送失败: ${jsonResponse.message || '请检查Token和网络'}`); } } catch (e) { console.error('解析推送响应错误:', e, response.responseText); updateStatusPanelText('状态: 已暂停 (通知响应异常)'); } }, onerror: function(response) { console.error('推送通知请求错误:', response); updateStatusPanelText('状态: 已暂停 (通知请求失败)'); alert('通知请求失败,请检查网络或油猴脚本的跨域权限设置。'); } }); } function handleVideoPause(event) { const currentTime = new Date().toLocaleString('zh-CN', { hour12: false }); const statusMsg = `状态: 已暂停 (时间: ${currentTime.split(' ')[1]})`; console.log(`视频 (ID: ${VIDEO_ID}) 已暂停于 ${currentTime}`); updateStatusPanelText(statusMsg); if (GM_getValue('notificationEnabled_v07', false)) { const title = "视频播放通知"; const content = `视频 ${document.title || '未知页面'} 已停止播放,停止时间为 ${currentTime}`; // 增加了页面标题 sendPushNotification(title, content); } } function handleVideoPlay(event) { console.log(`视频 (ID: ${VIDEO_ID}) 已播放.`); updateStatusPanelText('状态: 播放中'); } function attachListenersToVideo() { if (videoElement && !eventListenerAttached) { videoElement.addEventListener('pause', handleVideoPause); videoElement.addEventListener('play', handleVideoPlay); eventListenerAttached = true; console.log(`已为视频 (ID: ${VIDEO_ID}) 添加暂停/播放监听器。`); if (videoElement.paused) { updateStatusPanelText('状态: 已暂停'); } else { updateStatusPanelText('状态: 播放中'); } } else if (videoElement && eventListenerAttached) { if (videoElement.paused) { updateStatusPanelText('状态: 已暂停'); } else { updateStatusPanelText('状态: 播放中'); } } } const checkVideoPresenceAndState = throttle(() => { let videoJustFoundOrReconfirmed = false; if (!videoElement || !document.body.contains(videoElement)) { videoElement = document.getElementById(VIDEO_ID); if (videoElement) { console.log(`通过 Observer 找到/确认视频元素 (ID: ${VIDEO_ID})。`); videoJustFoundOrReconfirmed = true; eventListenerAttached = false; } else { updateStatusPanelText('状态: 未找到视频'); return; } } if (videoElement && (!eventListenerAttached || videoJustFoundOrReconfirmed)) { attachListenersToVideo(); } else if (videoElement && eventListenerAttached) { if (videoElement.paused) { if(!statusTextElement.textContent.includes('已暂停')) { updateStatusPanelText('状态: 已暂停'); } } else { if(!statusTextElement.textContent.includes('播放中')) { updateStatusPanelText('状态: 播放中'); } } } if (!videoElement || !eventListenerAttached) { const dialogWrapper = document.querySelector('div.el-dialog__wrapper[style*="z-index: 2003"]'); if (dialogWrapper && dialogWrapper.style.display !== 'none') { videoElement = document.getElementById(VIDEO_ID); if (videoElement && !eventListenerAttached) { console.log(`播放器容器可见,为视频 (ID: ${VIDEO_ID}) 附加监听器。`); attachListenersToVideo(); } } } }, 250); function init() { createStatusPanel(); videoElement = document.getElementById(VIDEO_ID); if (videoElement) { console.log(`初始找到视频元素 (ID: ${VIDEO_ID})。`); attachListenersToVideo(); } else { console.log(`页面加载时未找到视频元素 (ID: ${VIDEO_ID}),将通过 MutationObserver 监测。`); updateStatusPanelText('状态: 搜索视频中...'); } const observer = new MutationObserver((mutationsList, observerInstance) => { checkVideoPresenceAndState(); }); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'id', 'class'] }); console.log('MutationObserver 已启动。'); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();