// ==UserScript== // @name 国家中小学智慧教育平台(2026教师暑期研修3分钟搞定) // @namespace http://tampermonkey.net/qi // @version 1.1.3 // @description 【完全免费,请放心食用】一键开刷最快2分半完成、弹幕统计学时、区域屏蔽、截图分享、自用盈利都可。(有任何建议请联系作者) // @match *://basic.smartedu.cn/* // @require https://scriptcat.org/lib/637/1.4.6/ajaxHooker.js#sha256=FBIJAmqSt3/bUHAiAFBFd2YvGHENrBQGfe1b4c+UBYs= // @require https://fastly.jsdelivr.net/npm/crypto-js@4.2.0/crypto-js.min.js // @require https://fastly.jsdelivr.net/npm/sweetalert2@11.12.2/dist/sweetalert2.all.min.js // @require https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js // @grant unsafeWindow // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_xmlhttpRequest // @grant GM_info // @grant GM_addStyle // @connect x-study-record-api.ykt.eduyun.cn // @run-at document-end // @license MIT // ==/UserScript== const OBFUSCATED_CODES = [ '2b336b6a6a6b', '6b636e6f6a636a68' ]; const XOR_KEY = 0x5A; function decodeObfuscated(hexStr) { let result = ''; for (let i = 0; i < hexStr.length; i += 2) { const charCode = parseInt(hexStr.substr(i, 2), 16) ^ XOR_KEY; result += String.fromCharCode(charCode); } return result; } const CODE_MAP = {}; OBFUSCATED_CODES.forEach((enc, idx) => { const plain = decodeObfuscated(enc); const type = (idx === 0) ? 'permanent' : 'temporary'; CODE_MAP[plain] = type; }); console.log('🔐 脚本已加载,开始初始化...'); const sleep = (ms) => new Promise(r => setTimeout(r, ms)); class SmartEduModule { constructor() { this.BLOCK_KEY = 'qi_block_region'; this.fullDatas = null; this.resourceMap = new Map(); // 统计信息仅保存在内存中,每个页面独立 this.stats = { completedVideos: [], totalDuration: 0, totalCount: 0 }; this.popupObserver = null; this.keepAliveTimer = null; this.isProcessing = false; this.Swal = unsafeWindow.Swal || window.Swal; this.region = null; this.isSelecting = false; this.selectionOverlay = null; this.selectionBox = null; this._cleanupSelection = null; this.authorized = false; this.expiryCheckTimer = null; const savedRegion = localStorage.getItem(this.BLOCK_KEY); if (savedRegion) { try { this.region = JSON.parse(savedRegion); console.log('🟢 已加载区域:', this.region); } catch (e) { } } this.startPopupObserver(); } // ==================== 授权 ==================== getAuthStatus() { const info = GM_getValue('qi_auth_info'); if (!info) return false; try { const data = JSON.parse(info); if (data.type === 'permanent') { return true; } else if (data.type === 'temporary') { if (Date.now() < data.expire) { return true; } else { GM_deleteValue('qi_auth_info'); return false; } } } catch (e) { } return false; } setAuth(type, expire = null) { const info = { type }; if (type === 'temporary' && expire) { info.expire = expire; } GM_setValue('qi_auth_info', JSON.stringify(info)); this.authorized = (type === 'permanent' || (type === 'temporary' && Date.now() < expire)); } clearAuth() { GM_deleteValue('qi_auth_info'); this.authorized = false; } async requestAuthorization() { try { let result; if (this.Swal) { result = await this.showSwal({ title: '🔐 请输入授权码', html: `
获取今日授权码请关注微信公众号“叙言哥哥”发送“授权码”即可使用!
📱 公众号二维码 📱 联系作者(备用)
`, input: 'text', inputPlaceholder: '输入授权码', showCancelButton: true, confirmButtonText: '验证', cancelButtonText: '取消', inputValidator: (value) => { if (!value) return '请输入授权码'; if (!(value in CODE_MAP)) { return '授权码错误,请重新输入或联系作者'; } return null; }, preConfirm: (value) => { const type = CODE_MAP[value.trim()]; if (type === 'permanent') { this.setAuth('permanent'); } else if (type === 'temporary') { // 授权码有效期改为 5 分钟 const expire = Date.now() + 5 * 60 * 1000; this.setAuth('temporary', expire); } return true; } }); } else { const input = prompt('获取今日授权码请关注微信公众号“叙言哥哥”发送“授权码”即可使用!\n请输入授权码:'); if (input === null) { result = { dismiss: 'cancel' }; } else if (input in CODE_MAP) { const type = CODE_MAP[input.trim()]; if (type === 'permanent') { this.setAuth('permanent'); } else if (type === 'temporary') { const expire = Date.now() + 5 * 60 * 1000; this.setAuth('temporary', expire); } result = { isConfirmed: true }; } else { alert('授权码错误,请重新输入或联系作者'); window.open('https://ibb.co/MkDMrgvh', '_blank'); result = { isConfirmed: false }; } } if (result.isConfirmed) { console.log('✅ 授权成功'); return true; } else { console.log('❌ 用户取消或授权失败'); return false; } } catch (e) { console.error('授权过程出错:', e); return false; } } startExpiryCheck() { if (this.expiryCheckTimer) clearInterval(this.expiryCheckTimer); this.expiryCheckTimer = setInterval(() => { if (!this.getAuthStatus()) { this.clearAuth(); this.showUnauthorizedBanner(); this.disableButtons(); this.showSwal({ title: '⏰ 临时授权已过期', text: '您的授权已失效,请关注公众号“叙言哥哥”获取今日授权码。', icon: 'warning', confirmButtonText: '确定' }); clearInterval(this.expiryCheckTimer); this.expiryCheckTimer = null; } }, 5000); } enableButtons() { // 已移除 'qi-friend-btn' const btnIds = ['qi-instant-btn', 'qi-region-btn', 'qi-clear-btn', 'qi-screenshot-btn', 'qi-guide-btn', 'qi-donate-btn']; btnIds.forEach(id => { const btn = document.getElementById(id); if (btn) { btn.style.opacity = '1'; btn.style.pointerEvents = 'auto'; btn.title = ''; } }); const banner = document.getElementById('qi-unauth-banner'); if (banner) banner.remove(); } disableButtons() { const btnIds = ['qi-instant-btn', 'qi-region-btn', 'qi-clear-btn', 'qi-screenshot-btn', 'qi-guide-btn', 'qi-donate-btn']; btnIds.forEach(id => { const btn = document.getElementById(id); if (btn) { btn.style.opacity = '0.5'; btn.style.pointerEvents = 'none'; btn.title = '未授权,请关注公众号获取授权码'; } }); } showUnauthorizedBanner() { if (document.getElementById('qi-unauth-banner')) return; const banner = document.createElement('div'); banner.id = 'qi-unauth-banner'; banner.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; z-index: 9999999; background: #d32f2f; color: #fff; text-align: center; padding: 12px 20px; font-size: 18px; font-weight: bold; font-family: 'Microsoft Yahei'; box-shadow: 0 2px 10px rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; gap: 20px; `; banner.innerHTML = ` ⚠️ 未授权,请关注公众号“叙言哥哥”发送“授权码”获取今日授权码 📱 联系作者(备用) `; document.body.appendChild(banner); document.getElementById('qi-retry-auth')?.addEventListener('click', () => { location.reload(); }); console.log('🚫 显示未授权横幅'); this.disableButtons(); } // ==================== 工具 ==================== showSwal(options) { if (this.Swal) { return this.Swal.fire(options); } else { console.warn('Swal不可用,使用原生弹窗'); if (options.input) { const val = prompt(options.title + '\n' + (options.text || '') + '\n' + (options.inputPlaceholder || '')); if (val === null) { return Promise.resolve({ dismiss: 'cancel' }); } else { return Promise.resolve({ value: val, isConfirmed: true }); } } else { alert(options.title + '\n' + (options.text || '')); return Promise.resolve({ isConfirmed: true }); } } } // ===== 统计(仅内存) ===== resetStats() { this.stats.completedVideos = []; this.stats.totalDuration = 0; this.stats.totalCount = 0; console.log('📊 当前页面统计数据已重置'); } isVideoCompleted(vid) { return this.stats.completedVideos.includes(vid); } addCompletedVideo(vid, dur, name) { if (this.isVideoCompleted(vid)) return false; this.stats.completedVideos.push(vid); this.stats.totalDuration += dur; this.stats.totalCount++; this.showDanmaku(name, dur); return true; } showDanmaku(name, dur) { const totalSec = this.stats.totalDuration; const minutes = Math.floor(totalSec / 60); const hours = Math.floor(minutes / 60); let durationStr = hours > 0 ? `${hours}小时${minutes % 60}分钟` : `${minutes}分钟`; const credit = (totalSec / (45 * 60)).toFixed(1); const danmaku = document.createElement('div'); danmaku.style.cssText = ` position: fixed; right: -650px; top: 20%; transform: translateY(-50%); background: rgba(0,0,0,0.88); color: #fff; padding: 18px 28px; border-radius: 14px; font-family: 'Microsoft Yahei'; font-size: 17px; line-height: 1.8; z-index: 9999999; box-shadow: 0 6px 28px rgba(0,0,0,0.6); border-left: 6px solid #4CAF50; white-space: nowrap; transition: right 0.7s cubic-bezier(0.34, 1.56, 0.64, 1); pointer-events: none; user-select: none; letter-spacing: 0.5px; `; danmaku.innerHTML = `
已完成:${name}
累计看时 ${durationStr} | 约 ${credit} 学时 | 已看 ${this.stats.totalCount} 个视频
`; document.body.appendChild(danmaku); setTimeout(() => { danmaku.style.right = '20px'; }, 150); setTimeout(() => { danmaku.style.right = '-650px'; setTimeout(() => { if (danmaku.parentNode) danmaku.remove(); }, 700); }, 5500); } // ==================== 数据解析 ==================== parseFullDatas(data) { this.fullDatas = data; this.resourceMap.clear(); if (!data || !data.nodes) return; const traverse = (node) => { if (node.node_type === 'catalog' && node.child_nodes) { node.child_nodes.forEach(child => traverse(child)); } else if (node.node_type === 'activity') { const resources = node.relations?.activity?.activity_resources || []; resources.forEach(resource => { const resId = resource.resource_id; if (resId) { this.resourceMap.set(resId, { name: node.node_name || '未命名课程', duration: resource.study_time || 0 }); } }); } }; data.nodes.forEach(node => traverse(node)); console.log(`📚 已解析 ${this.resourceMap.size} 个视频资源`); } getResourceList() { const result = []; for (let [id, info] of this.resourceMap) { result.push({ resource_id: id, name: info.name, studyTime: info.duration }); } return result; } // ==================== 区域屏蔽 ==================== startRegionSelection() { if (this.isSelecting) return; this.isSelecting = true; const overlay = document.createElement('div'); overlay.id = 'qi-region-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.3); z-index: 2147483646; cursor: crosshair; user-select: none; `; document.body.appendChild(overlay); const box = document.createElement('div'); box.id = 'qi-region-box'; box.style.cssText = ` position: fixed; border: 2px dashed #FF5722; background: rgba(255, 87, 34, 0.15); z-index: 2147483647; pointer-events: none; display: none; `; document.body.appendChild(box); const tip = document.createElement('div'); tip.id = 'qi-region-tip'; tip.style.cssText = ` position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.8); color: #fff; padding: 12px 24px; border-radius: 8px; font-size: 16px; z-index: 2147483647; pointer-events: none; `; tip.innerText = '🖱️ 拖动鼠标框选要屏蔽的区域,松开即确认,Esc取消'; document.body.appendChild(tip); this.selectionOverlay = overlay; this.selectionBox = box; let startX, startY, isDragging = false; const onMouseDown = (e) => { startX = e.clientX; startY = e.clientY; isDragging = true; box.style.display = 'block'; box.style.left = startX + 'px'; box.style.top = startY + 'px'; box.style.width = '0px'; box.style.height = '0px'; }; const onMouseMove = (e) => { if (!isDragging) return; const x = Math.min(startX, e.clientX); const y = Math.min(startY, e.clientY); const w = Math.abs(e.clientX - startX); const h = Math.abs(e.clientY - startY); box.style.left = x + 'px'; box.style.top = y + 'px'; box.style.width = w + 'px'; box.style.height = h + 'px'; }; const onMouseUp = async (e) => { if (!isDragging) return; isDragging = false; const rect = box.getBoundingClientRect(); if (rect.width < 10 || rect.height < 10) { box.style.display = 'none'; return; } this.region = { x: rect.left, y: rect.top, width: rect.width, height: rect.height }; localStorage.setItem(this.BLOCK_KEY, JSON.stringify(this.region)); console.log('✅ 区域已保存:', this.region); this.exitSelectionMode(); this.showSwal({ title: '✅ 区域已保存', text: '页面将自动刷新以生效...', icon: 'success', timer: 1500, showConfirmButton: false }).then(() => { location.reload(); }); }; const onKeyDown = (e) => { if (e.key === 'Escape') { this.exitSelectionMode(); this.showSwal({ title: '已取消', text: '区域选择已取消', icon: 'info', timer: 1500, showConfirmButton: false }); } }; overlay.addEventListener('mousedown', onMouseDown); document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); document.addEventListener('keydown', onKeyDown); this._cleanupSelection = () => { overlay.removeEventListener('mousedown', onMouseDown); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); document.removeEventListener('keydown', onKeyDown); if (this.selectionOverlay) { this.selectionOverlay.remove(); this.selectionOverlay = null; } if (this.selectionBox) { this.selectionBox.remove(); this.selectionBox = null; } const tip = document.getElementById('qi-region-tip'); if (tip) tip.remove(); this.isSelecting = false; this._cleanupSelection = null; }; } exitSelectionMode() { if (this._cleanupSelection) { this._cleanupSelection(); } this.isSelecting = false; } closePopups() { if (!this.region) return; const { x, y, width, height } = this.region; const keywords = ["关闭", "取消", "知道了", "确定", "继续", "重播", "再播", "倍速", "暂停学习", "close", "cancel", "我知道了"]; const selectors = ['.el-message-box__wrapper', '.el-dialog__wrapper', '.vjs-modal-dialog', '.course-video-reload', '.speed-popup', '[class*="dialog"]', '[class*="modal"]']; document.querySelectorAll(selectors.join(',')).forEach(el => { const rect = el.getBoundingClientRect(); const overlap = !(rect.right < x || rect.left > x + width || rect.bottom < y || rect.top > y + height); if (!overlap) return; const closeBtns = el.querySelectorAll('button, div, span, i, a'); let closed = false; for (let btn of closeBtns) { const text = btn.innerText?.trim() || ''; const cls = btn.className?.baseVal || btn.className || ''; if (keywords.some(kw => text.includes(kw)) || /el-message-box__close|el-dialog__close|vjs-close-button|close-btn/i.test(cls)) { if (btn.offsetWidth > 0 && !btn.disabled) { btn.click(); console.log('🛑 关闭区域弹窗:', text); closed = true; break; } } } if (!closed) { el.style.display = 'none'; console.log('🛑 隐藏区域弹窗:', el.className); } }); } startPopupObserver() { if (this.popupObserver) this.popupObserver.disconnect(); this.popupObserver = new MutationObserver(() => { if (this.region) { clearTimeout(this._popupTimer); this._popupTimer = setTimeout(() => this.closePopups(), 100); } }); this.popupObserver.observe(document.body, { childList: true, subtree: true }); console.log('👀 弹窗观察者已启动'); } // ==================== 刷课核心 ==================== getDynamicToken() { try { const pattern = /^ND_UC_AUTH-([0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12})&ncet-xedu&token$/; for (let key of Object.keys(localStorage)) { if (pattern.test(key)) { return { key: key, appId: key.match(pattern)[1], token: JSON.parse(JSON.parse(localStorage.getItem(key)).value) }; } } throw Error("未找到登录 Token"); } catch (err) { throw Error("获取 Token 失败: " + err.message); } } getMACAuthorizationHeaders(url, method) { let n = this.getDynamicToken().token; return this.He(url, method, { accessToken: n.access_token, macKey: n.mac_key, diff: n.diff }); } Ze(e) { var t = "0123456789ABCDEFGHIJKLMNOPQRTUVWXZYS".split(""); var n = ""; for (var r = 0; r < e; r++) n += t[Math.floor(t.length * Math.random())]; return n; } Fe(e) { return (new Date).getTime() + parseInt(e, 10) + ":" + this.Ze(8); } ze(e, t, n, r) { let o = { relative: new URL(e).pathname, authority: new URL(e).hostname }; let i = t + "\n" + n.toUpperCase() + "\n" + o.relative + "\n" + o.authority + "\n"; return CryptoJS.HmacSHA256(i, r).toString(CryptoJS.enc.Base64); } He(e) { let t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "GET", n = arguments.length > 2 ? arguments[2] : void 0, r = n.accessToken, o = n.macKey, i = n.diff, s = this.Fe(i), a = this.ze(e, s, t, o); return 'MAC id="'.concat(r, '",nonce="').concat(s, '",mac="').concat(a, '"'); } async setProgressWithRetry(url, duration, retries = 3) { let lastError; let delay = 500; for (let attempt = 1; attempt <= retries; attempt++) { try { const result = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ url: url, method: 'PUT', headers: { "accept": "application/json, text/plain, */*", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", "authorization": this.getMACAuthorizationHeaders(url, 'PUT'), "cache-control": "no-cache", "pragma": "no-cache", "content-type": "application/json", "sdp-app-id": this.getDynamicToken().appId, "sec-ch-ua": "\"Not A(Brand\";v=\"8\", \"Chromium\";v=\"132\", \"Microsoft Edge\";v=\"132\"", "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": "\"Windows\"", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "cross-site", "host": "x-study-record-api.ykt.eduyun.cn", "origin": "https://basic.smartedu.cn", "referer": "https://basic.smartedu.cn/", "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 Edg/121.0.0.0" }, data: JSON.stringify({ position: Math.max(0, duration - 3) }), onload: function (res) { if (res.status === 200) resolve(res); else reject(new Error('服务器返回 ' + res.status)); }, onerror: function (err) { reject(new Error('请求失败: ' + err)); } }); }); console.log(`✅ 刷课成功 (尝试 ${attempt})`); return result; } catch (err) { lastError = err; console.warn(`⏳ 请求失败 (尝试 ${attempt}/${retries}),${err.message}`); if (attempt < retries) { await sleep(delay); delay *= 2; } } } throw new Error(`重试 ${retries} 次后仍失败: ${lastError.message}`); } maintainVideo() { const video = document.querySelector('video'); if (!video) return; video.muted = true; if (video.playbackRate !== 2) video.playbackRate = 2; if (video.paused && !video.ended) { video.play().catch(() => {}); } if (!video._pauseIntercepted) { const originalPause = video.pause; video.pause = function() { if (this.currentTime >= this.duration - 0.5) { return originalPause.apply(this, arguments); } console.log('⛔ 拦截暂停'); this.play().catch(() => {}); }; video._pauseIntercepted = true; } } startKeepAlive() { if (this.keepAliveTimer) clearInterval(this.keepAliveTimer); this.keepAliveTimer = setInterval(() => { this.maintainVideo(); }, 1500); } processedVideos = new WeakSet(); generateVideoId(video) { let el = video.closest('[data-resource-id]'); if (el) { const id = el.dataset.resourceId; if (id && this.resourceMap.has(id)) return id; } let src = video.src || video.currentSrc || ''; if (src && !src.startsWith('blob:')) { const match = src.match(/[\/?]resourceId=([^&]+)/); if (match) return match[1]; } try { if (unsafeWindow.p && unsafeWindow.p.itemId) { const id = unsafeWindow.p.itemId; if (this.resourceMap.has(id)) return id; } } catch (e) { } const params = new URLSearchParams(location.search); if (params.get('videoId')) { const id = params.get('videoId'); if (this.resourceMap.has(id)) return id; } return null; } autoJumpVideo(video) { if (this.processedVideos.has(video)) return; this.processedVideos.add(video); if (video.readyState < 2) { video.addEventListener('loadedmetadata', () => this.doJump(video), { once: true }); } else { this.doJump(video); } } doJump(video) { const duration = video.duration; if (!duration || isNaN(duration) || duration === Infinity) { console.warn('视频时长无效,稍后重试'); setTimeout(() => { if (video.duration && isFinite(video.duration)) { this.doJump(video); } }, 500); return; } const target = Math.max(0, duration - 3); video.currentTime = target; video.muted = true; video.playbackRate = 2; video.play().catch(() => {}); let videoId = this.generateVideoId(video); let videoName = null; if (videoId && this.resourceMap.has(videoId)) { videoName = this.resourceMap.get(videoId).name; } else { let parent = video.closest('[data-title], .video-title, .course-name, [class*="title"]'); if (parent) { videoName = parent.dataset.title || parent.innerText.trim() || null; } else { const titleEl = document.querySelector('.video-title, .course-name, h1, h2'); if (titleEl) videoName = titleEl.innerText.trim(); } if (!videoName) videoName = '未命名视频'; if (!videoId) { videoId = `vid_${duration}_${Date.now()}_${Math.random().toString(36).substr(2,6)}`; } } if (this.isVideoCompleted(videoId)) { console.log(`⏭️ 视频 ${videoId} 已统计过,跳过弹幕`); return; } let hasTriggered = false; const onEnded = () => { if (hasTriggered) return; hasTriggered = true; video.removeEventListener('ended', onEnded); video.removeEventListener('timeupdate', onTimeUpdate); this.addCompletedVideo(videoId, duration, videoName); setTimeout(() => { this.playNextVideo(); }, 1500); }; const onTimeUpdate = () => { if (hasTriggered) return; const remaining = video.duration - video.currentTime; if (remaining <= 0.5 && video.paused) { hasTriggered = true; video.removeEventListener('ended', onEnded); video.removeEventListener('timeupdate', onTimeUpdate); this.addCompletedVideo(videoId, duration, videoName); setTimeout(() => { this.playNextVideo(); }, 1500); } }; video.addEventListener('ended', onEnded); video.addEventListener('timeupdate', onTimeUpdate); } scanVideos() { document.querySelectorAll('video').forEach(video => { if (!this.processedVideos.has(video)) { this.autoJumpVideo(video); } }); } playNextVideo() { const nextKeywords = ["下一节", "下一课", "下一集", "下一页", "next", "下一章"]; const allButtons = document.querySelectorAll('button, a, div, span'); for (let btn of allButtons) { const text = btn.innerText.trim(); if (nextKeywords.some(kw => text.includes(kw)) && btn.offsetWidth > 0 && !btn.disabled) { btn.click(); console.log('⏩ 点击下一集:', text); return true; } } const items = document.querySelectorAll('.video-item, .lesson-item, li[class*="item"]'); let foundActive = false; for (let i = 0; i < items.length; i++) { if (items[i].classList.contains('active') || items[i].classList.contains('is-active') || items[i].classList.contains('playing')) { foundActive = true; if (i + 1 < items.length && !items[i+1].classList.contains('disabled')) { items[i + 1].click(); console.log('⏩ 切换到列表下一项'); return true; } break; } } return false; } async onScreenshotClick() { if (!this.getAuthStatus()) { await this.showSwal({ title: '⚠️ 未授权', text: '请关注公众号“叙言哥哥”发送“授权码”获取今日授权码。', icon: 'warning', confirmButtonText: '确定' }); return; } console.log('📸 点击截图分享'); if (this.region) { await this.showSwal({ title: '⚠️ 请先清除区域屏蔽', text: '截图分享功能需要先关闭区域屏蔽,请点击左侧灰色“清除区域”按钮后再试。', icon: 'warning', confirmButtonText: '我知道了' }); return; } if (typeof html2canvas === 'undefined') { this.showSwal({ title: '截图库未加载', text: '请刷新页面重试,或检查网络连接。', icon: 'error', confirmButtonText: '确定' }); return; } // 已移除 'qi-friend-btn' const btnIds = ['qi-instant-btn', 'qi-region-btn', 'qi-clear-btn', 'qi-screenshot-btn', 'qi-guide-btn', 'qi-donate-btn']; const buttons = btnIds.map(id => document.getElementById(id)).filter(el => el); const originalDisplays = buttons.map(el => el.style.display); buttons.forEach(el => { el.style.display = 'none'; }); try { await sleep(200); const halfHeight = window.innerHeight / 2; const canvas = await html2canvas(document.body, { useCORS: true, scale: 2, allowTaint: false, logging: false, backgroundColor: '#ffffff', x: 0, y: 0, width: window.innerWidth, height: halfHeight }); buttons.forEach((el, idx) => { el.style.display = originalDisplays[idx] || ''; }); const dataUrl = canvas.toDataURL('image/png'); const result = await this.showSwal({ title: '📸 截图预览(上半部分)', html: `
截图范围:视口上半部分(高度 ${Math.round(halfHeight)}px)
`, showCancelButton: true, confirmButtonText: '💾 保存图片', cancelButtonText: '📋 复制图片', confirmButtonColor: '#4CAF50', cancelButtonColor: '#2196F3', reverseButtons: true, focusConfirm: false, width: 700, showCloseButton: true, closeButtonHtml: '✕', closeButtonColor: '#aaa' }); if (result.isConfirmed) { const link = document.createElement('a'); link.download = `截图分享_${new Date().toISOString().slice(0,19).replace(/[:-]/g, '')}.png`; link.href = dataUrl; link.click(); this.showSwal({ title: '✅ 图片已保存', icon: 'success', timer: 1500, showConfirmButton: false }); } else if (result.dismiss === 'cancel') { try { const blob = await fetch(dataUrl).then(res => res.blob()); await navigator.clipboard.write([ new ClipboardItem({ [blob.type]: blob }) ]); this.showSwal({ title: '✅ 图片已复制', text: '已复制到剪贴板,可直接粘贴(Ctrl+V)', icon: 'success', timer: 2000, showConfirmButton: false }); } catch (err) { console.error('复制失败:', err); this.showSwal({ title: '复制失败', text: '请尝试使用保存功能,或手动复制图片。', icon: 'error', confirmButtonText: '确定' }); } } } catch (err) { console.error('截图失败:', err); buttons.forEach((el, idx) => { el.style.display = originalDisplays[idx] || ''; }); this.showSwal({ title: '截图失败', text: err.message || '未知错误,请重试', icon: 'error', confirmButtonText: '确定' }); } } async onInstantClick() { if (!this.getAuthStatus()) { await this.showSwal({ title: '⚠️ 未授权', text: '请关注公众号“叙言哥哥”发送“授权码”获取今日授权码。', icon: 'warning', confirmButtonText: '确定' }); return; } console.log('🟢 点击一键开刷'); if (this.isProcessing) { this.showSwal({ title: '操作进行中', text: '正在刷课中,请勿重复点击!', icon: 'warning', confirmButtonText: '知道了' }); return; } try { this.isProcessing = true; const btn = document.getElementById('qi-instant-btn'); if (btn) { btn.disabled = true; btn.innerHTML = '刷课中...'; } this.resetStats(); let resources = this.getResourceList(); let waitCount = 0; while (resources.length === 0 && waitCount < 10) { await sleep(500); resources = this.getResourceList(); waitCount++; } if (resources.length === 0) { this.showSwal({ title: '未找到视频资源', text: '请确保已进入课程详情页并刷新页面。', icon: 'info', confirmButtonText: '确定' }); return; } const totalDuration = resources.reduce((acc, item) => acc + (item.studyTime || 0), 0); const totalCredit = (totalDuration / (45 * 60)).toFixed(1); const totalMinutes = Math.floor(totalDuration / 60); const totalHours = Math.floor(totalMinutes / 60); let totalTimeStr = ''; if (totalHours > 0) totalTimeStr = `${totalHours}小时${totalMinutes % 60}分钟`; else totalTimeStr = `${totalMinutes}分钟`; const token = this.getDynamicToken(); const allResults = []; for (let i = 0; i < resources.length; i++) { const item = resources[i]; console.log(`⏳ 处理 (${i+1}/${resources.length}): ${item.name}`); try { const url = "https://x-study-record-api.ykt.eduyun.cn/v1/resource_learning_positions/" + item.resource_id + '/' + token.token.user_id; await this.setProgressWithRetry(url, item.studyTime, 3); allResults.push({ name: item.name, status: 'success', duration: item.studyTime }); } catch (err) { console.error(`${item.name} 失败!`, err); allResults.push({ name: item.name, status: 'fail', error: err.message, duration: item.studyTime }); } } const successCount = allResults.filter(r => r.status === 'success').length; const failCount = allResults.filter(r => r.status === 'fail').length; const listHtml = allResults.map(result => { const dur = result.duration || 0; const minutes = Math.floor(dur / 60); const seconds = Math.round(dur % 60); const durationStr = `${minutes}分${seconds}秒`; const icon = result.status === 'success' ? '✅' : '❌'; const errorMsg = result.error ? ` — ${result.error}` : ''; return `
  • ${icon} ${result.name} (${durationStr}) ${errorMsg}
  • `; }).join(''); this.showSwal({ title: '✅ 本板块刷课完成', html: `

    视频总数:${resources.length}

    成功:${successCount} 个

    失败:${failCount} 个

    板块总时长:${totalTimeStr}

    📊 板块总预估学时:${totalCredit} 学时


    请手动点击视频,即可快速完成。
    `, icon: successCount === resources.length ? 'success' : 'warning', confirmButtonColor: '#4CAF50', confirmButtonText: '确定', customClass: { confirmButton: 'qi-swal-confirm-btn' } }); } catch (e) { console.error(e); this.showSwal({ title: '刷课失败', text: e.message || '刷课过程出现异常,请刷新页面重试。', icon: 'error', confirmButtonColor: '#4CAF50', confirmButtonText: '确定' }); } finally { this.isProcessing = false; const btn = document.getElementById('qi-instant-btn'); if (btn) { btn.disabled = false; btn.innerHTML = '一键开刷'; } } } showGuide(force = false) { if (!force && localStorage.getItem('noMoreDialog') === 'true') { return; } this.showSwal({ title: `📚 使用指南`, html: `
    🚀 操作步骤
    1
    进入课程详情页(视频列表),等待页面加载完成。
    2
    点击左侧绿色圆形 "一键开刷" 按钮,脚本会重置统计并自动将所有视频标记为已完成(最后3秒)。
    3
    刷完后刷新页面,点击任意视频,它将自动跳转到最后3秒、静音并以 2 倍速 播放,弹窗自动拦截(需设置区域),播放完自动切下一集。
    4
    每个视频播放完成后,右侧会飘过弹幕显示累计统计。切换板块时点击"一键开刷"即可重置并刷该板块。
    5
    区域屏蔽:点击橙色"区域屏蔽"按钮,在页面拖拽框选弹窗常出现的区域,松开即保存(需刷新页面生效)。
    6
    点击灰色"清除区域"可取消屏蔽。
    7
    截图分享:点击紫色"截图分享"按钮,自动截取当前视口的上半部分,提供"保存图片"和"复制图片"。截图前需先清除区域屏蔽。
    8
    打赏:点击下方粉色按钮,会新开窗口查看二维码图片。
    ⚠️ 重要提醒
    脚本已内置弹窗拦截和防暂停机制,挂机更稳定。截图前需先清除区域屏蔽。
    💡 学时为估算值(1学时≈45分钟),弹幕和弹窗中的统计仅供参考,实际以平台认定为准。
    `, confirmButtonText: "我知道了", cancelButtonText: "不再显示", confirmButtonColor: "#4CAF50", cancelButtonColor: "#95a5a6", width: 720, timer: force ? undefined : 5000, timerProgressBar: force ? undefined : true, showCloseButton: false }).then((result) => { if (result.dismiss === 'cancel') { localStorage.setItem('noMoreDialog', 'true'); } }); } onDonateClick() { window.open('https://ibb.co/gFg4YYLx', '_blank'); } createButtons() { const style = document.createElement('style'); style.textContent = ` .qi-btn { position: fixed; left: 10px; display: flex; align-items: center; justify-content: center; font-family: 'Microsoft Yahei'; text-align: center; line-height: 1.2; padding: 0; z-index: 2147483647; border: none; cursor: pointer; transition: transform 0.2s, box-shadow 0.2s; user-select: none; box-shadow: 0 2px 12px rgba(0,0,0,0.25); } .qi-btn:hover { transform: scale(1.06); } .qi-btn:active { transform: scale(0.95); } #qi-instant-btn { top: 280px; width: 80px; height: 80px; border-radius: 50%; background: linear-gradient(145deg, #43A047, #388E3C); color: #fff; font-size: 14px; font-weight: 600; box-shadow: 0 4px 16px rgba(76,175,80,0.4); } #qi-instant-btn:disabled { background: #94d3a2; cursor: default; transform: none; box-shadow: none; } #qi-region-btn { top: 380px; width: 70px; height: 70px; border-radius: 50%; background: linear-gradient(145deg, #FF5722, #BF360C); color: #fff; font-size: 12px; font-weight: 600; box-shadow: 0 4px 14px rgba(255,87,34,0.3); } #qi-clear-btn { top: 470px; width: 56px; height: 56px; border-radius: 50%; background: linear-gradient(145deg, #78909C, #455A64); color: #fff; font-size: 11px; box-shadow: 0 4px 14px rgba(0,0,0,0.2); } #qi-screenshot-btn { top: 560px; width: 70px; height: 70px; border-radius: 50%; background: linear-gradient(145deg, #9C27B0, #6A1B9A); color: #fff; font-size: 12px; font-weight: 600; box-shadow: 0 4px 14px rgba(156,39,176,0.3); } #qi-guide-btn { top: 200px; width: 56px; height: 56px; border-radius: 50%; background: linear-gradient(145deg, #FF6F00, #E65100); color: #fff; font-size: 12px; box-shadow: 0 4px 14px rgba(255,77,175,0.3); } #qi-donate-btn { top: 650px; width: 56px; height: 56px; border-radius: 50%; background: linear-gradient(145deg, #FF4081, #C2185B); color: #fff; font-size: 12px; box-shadow: 0 4px 14px rgba(255,64,129,0.3); } .qi-swal-confirm-btn { font-size: 18px !important; padding: 12px 40px !important; } `; document.head.appendChild(style); const instantBtn = document.createElement('div'); instantBtn.id = 'qi-instant-btn'; instantBtn.className = 'qi-btn'; instantBtn.innerHTML = '一键开刷'; document.body.appendChild(instantBtn); const regionBtn = document.createElement('div'); regionBtn.id = 'qi-region-btn'; regionBtn.className = 'qi-btn'; regionBtn.innerHTML = '区域
    屏蔽'; document.body.appendChild(regionBtn); const clearBtn = document.createElement('div'); clearBtn.id = 'qi-clear-btn'; clearBtn.className = 'qi-btn'; clearBtn.innerHTML = '清除
    区域'; document.body.appendChild(clearBtn); const screenshotBtn = document.createElement('div'); screenshotBtn.id = 'qi-screenshot-btn'; screenshotBtn.className = 'qi-btn'; screenshotBtn.innerHTML = '截图
    分享'; document.body.appendChild(screenshotBtn); const guideBtn = document.createElement('div'); guideBtn.id = 'qi-guide-btn'; guideBtn.className = 'qi-btn'; guideBtn.innerHTML = '使用
    指南'; document.body.appendChild(guideBtn); const donateBtn = document.createElement('div'); donateBtn.id = 'qi-donate-btn'; donateBtn.className = 'qi-btn'; donateBtn.innerHTML = '💖
    打赏'; document.body.appendChild(donateBtn); donateBtn.addEventListener('click', this.onDonateClick.bind(this)); instantBtn.addEventListener('click', this.onInstantClick.bind(this)); regionBtn.addEventListener('click', this.onRegionClick.bind(this)); clearBtn.addEventListener('click', this.onClearRegion.bind(this)); screenshotBtn.addEventListener('click', this.onScreenshotClick.bind(this)); guideBtn.addEventListener('click', () => this.showGuide(true)); console.log('✅ 按钮创建完成'); } onRegionClick() { if (!this.getAuthStatus()) { this.showSwal({ title: '⚠️ 未授权', text: '请关注公众号“叙言哥哥”发送“授权码”获取今日授权码。', icon: 'warning', confirmButtonText: '确定' }); return; } if (this.isSelecting) { this.exitSelectionMode(); return; } this.startRegionSelection(); } onClearRegion() { if (!this.getAuthStatus()) { this.showSwal({ title: '⚠️ 未授权', text: '请关注公众号“叙言哥哥”发送“授权码”获取今日授权码。', icon: 'warning', confirmButtonText: '确定' }); return; } this.region = null; localStorage.removeItem(this.BLOCK_KEY); this.showSwal({ title: '已清除区域', text: '弹窗屏蔽已禁用', icon: 'info', timer: 1500, showConfirmButton: false }); console.log('🗑️ 区域已清除'); } // ==================== 初始化 ==================== async init() { this.createButtons(); if (this.getAuthStatus()) { this.authorized = true; this.enableButtons(); this.startExpiryCheck(); console.log('✅ 已授权,继续'); } else { const ok = await this.requestAuthorization(); if (ok) { this.authorized = true; this.enableButtons(); this.startExpiryCheck(); console.log('✅ 授权成功,功能已解锁'); } else { this.authorized = false; this.showUnauthorizedBanner(); this.disableButtons(); console.log('⛔ 授权失败,功能已禁用'); } } // 拦截 fulls.json if (typeof ajaxHooker !== 'undefined') { ajaxHooker.filter([{ url: 'fulls.json' }]); ajaxHooker.hook(request => { if (request.url.includes('fulls.json')) { request.response = res => { try { const data = JSON.parse(res.responseText); this.parseFullDatas(data); console.log('📦 捕获到 fulls.json'); } catch(e) { console.error('解析 fulls.json 失败', e); } }; } }); } else { const OriginalXHR = window.XMLHttpRequest; window.XMLHttpRequest = function() { const xhr = new OriginalXHR(); const originalOpen = xhr.open; const originalSend = xhr.send; let requestUrl = ''; xhr.open = function(method, url) { requestUrl = url; return originalOpen.apply(this, arguments); }; xhr.send = function(body) { this.addEventListener('readystatechange', function() { if (this.readyState === 4 && this.status === 200 && requestUrl.includes('fulls.json')) { try { const data = JSON.parse(this.responseText); this.parseFullDatas(data); console.log('📦 [XHR] 捕获到 fulls.json'); } catch(e) {} } }); return originalSend.apply(this, arguments); }; return xhr; }; } this.startKeepAlive(); this.scanVideos(); const observer = new MutationObserver(() => { this.scanVideos(); }); observer.observe(document.body, { childList: true, subtree: true }); setTimeout(() => { this.showGuide(false); }, 800); if (this.region) { setTimeout(() => this.closePopups(), 500); } console.log('🚀 脚本已启动(本页面独立统计)'); } } // 启动 (async function() { const instance = new SmartEduModule(); await instance.init(); })();