// ==UserScript== // @name 粉笔课程课件下载器 // @namespace https://www.fenbi.com/ // @version 1.1.0 // @description 进入粉笔课程回放页后自动捕获 keynote 文件并下载(双重下载机制:脚本管理器下载 + 原生浏览器触发,支持 IDM/Motrix/Aria 劫持) // @author 超子&TraeWork // @license CC BY-NC 4.0 // @match https://www.fenbi.com/spa/pwa/class/* // @match https://*.fenbi.com/spa/pwa/class/* // @grant unsafeWindow // @grant GM_download // @grant GM_xmlhttpRequest // @run-at document-start // ==/UserScript== (function () { 'use strict'; var win = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window; var captured = false; var keynoteUrl = null; function extractHash(url) { var m = url.match(/keynotes\/([a-f0-9]+)/i); return m ? m[1] : null; } // 下载方式一:脚本管理器原生下载(跨域可用,不受浏览器限制) function downloadViaGM(url, filename) { if (typeof GM_download !== 'undefined') { try { GM_download({ url: url, name: filename, saveAs: false, onload: function () { console.log('[粉笔课件下载器] GM_download 下载完成: ' + filename); }, onerror: function (err) { console.warn('[粉笔课件下载器] GM_download 失败,尝试 fallback:', err); downloadViaBlob(url, filename); } }); return true; } catch (e) { console.warn('[粉笔课件下载器] GM_download 调用失败:', e); } } return false; } // 下载方式二:GM_xmlhttpRequest + Blob(脚本管理器的另一种方式) function downloadViaBlob(url, filename) { if (typeof GM_xmlhttpRequest === 'undefined') { downloadViaAnchor(url, filename); return; } try { GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'blob', onload: function (resp) { if (resp.status === 200 && resp.response) { var blobUrl = URL.createObjectURL(resp.response); var a = document.createElement('a'); a.href = blobUrl; a.download = filename; a.style.display = 'none'; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(function () { URL.revokeObjectURL(blobUrl); }, 1000); console.log('[粉笔课件下载器] GM_xhr + Blob 下载完成: ' + filename); } else { downloadViaAnchor(url, filename); } }, onerror: function () { downloadViaAnchor(url, filename); } }); } catch (e) { downloadViaAnchor(url, filename); } } // 下载方式三:原生 a.click(用于被 IDM / Motrix / Aria 等下载工具劫持) function downloadViaAnchor(url, filename) { var a = document.createElement('a'); a.href = url; a.download = filename; a.style.display = 'none'; document.body.appendChild(a); a.click(); document.body.removeChild(a); console.log('[粉笔课件下载器] 原生 a.click 下载触发: ' + filename); } function triggerDownload(url) { var hash = extractHash(url); var filename = hash ? hash + '.pdf' : 'keynote.pdf'; // 优先用脚本管理器下载(保证能下到文件) var gmOk = downloadViaGM(url, filename); // 同时也触发原生 a.click(让 IDM/Motrix 等下载工具能劫持到) // 延迟一小段,避免和 GM_download 同时开始造成混乱 setTimeout(function () { downloadViaAnchor(url, filename); }, 500); showNotification('已下载课件: ' + filename); } function onKeynoteFound(url) { if (captured) return; captured = true; keynoteUrl = url; console.log('[粉笔课件下载器] 捕获到 keynote URL: ' + url); setTimeout(function () { triggerDownload(url); }, 2000); } function showNotification(msg) { var div = document.createElement('div'); div.textContent = msg; div.style.cssText = 'position:fixed;top:20px;right:20px;z-index:99999;' + 'background:#4CAF50;color:white;padding:12px 20px;border-radius:8px;' + 'font-size:14px;font-family:sans-serif;box-shadow:0 4px 12px rgba(0,0,0,0.3);' + 'transition:opacity 0.5s;opacity:1;'; document.body.appendChild(div); setTimeout(function () { div.style.opacity = '0'; }, 4000); setTimeout(function () { if (div.parentNode) div.parentNode.removeChild(div); }, 5000); } function installHooks() { var origOpen = win.XMLHttpRequest.prototype.open; win.XMLHttpRequest.prototype.open = function (method, url) { if (url && typeof url === 'string' && (url.indexOf('keynoteali') !== -1 || (url.indexOf('keynotes/') !== -1 && url.indexOf('fbstatic') !== -1))) { onKeynoteFound(url); } return origOpen.apply(this, arguments); }; if (win.fetch) { var origFetch = win.fetch; win.fetch = function () { var url = arguments[0]; if (typeof url === 'string' && (url.indexOf('keynoteali') !== -1 || url.indexOf('keynotes/') !== -1)) { onKeynoteFound(url); } else if (url && url.url && typeof url.url === 'string' && url.url.indexOf('keynoteali') !== -1) { onKeynoteFound(url.url); } return origFetch.apply(this, arguments); }; } } function scanPerformanceEntries() { if (captured) return; try { var resources = win.performance.getEntriesByType('resource'); for (var i = 0; i < resources.length; i++) { var name = resources[i].name; if (name.indexOf('keynoteali') !== -1 || (name.indexOf('keynotes/') !== -1 && name.indexOf('fbstatic') !== -1)) { onKeynoteFound(name); return; } } } catch (e) {} } var observer = null; function startObserver() { if (typeof win.PerformanceObserver === 'undefined') return; try { observer = new win.PerformanceObserver(function (list) { var entries = list.getEntries(); for (var i = 0; i < entries.length; i++) { var name = entries[i].name; if (name.indexOf('keynoteali') !== -1 || (name.indexOf('keynotes/') !== -1 && name.indexOf('fbstatic') !== -1)) { onKeynoteFound(name); } } }); observer.observe({ entryTypes: ['resource'] }); } catch (e) {} } installHooks(); startObserver(); var scanCount = 0; var scanTimer = setInterval(function () { scanCount++; scanPerformanceEntries(); if (captured || scanCount > 30) { clearInterval(scanTimer); } }, 1000); console.log('[粉笔课件下载器] 脚本已加载 v1.1.0,等待捕获 keynote URL...'); })();