您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
从lurl和myppt自动下载媒体
当前为
// ==UserScript== // @name dowload from lurl&myppt automatically // @name:zh-CN 从lurl&myppt自动下载 // @name:zh-TW 從lurl&myppt自動下載 // @namespace org.jw23.dcardtools // @version 0.1 // @description It will download the media from lurl and myppt automatically // @description:zh-TW 從lurl和mypppt自動下載媒體 // @description:zh-CN 从lurl和myppt自动下载媒体 // @author jw23 // @match https://lurl.cc/* // @match https://myppt.cc/* // @run-at document-start // @grant GM_download // @grant GM.xmlHttpRequest // @grant unsafeWindow // @connect *.lurl.cc // @connect *.myppt.cc // @license CC BY-NC // ==/UserScript== // type: image? video? const urlGuards = [ { guard: (url) => { return url && /lurl\.cc\/\d+/.test(url) }, extractFilename: (url) => { let splices = url.split('/') let [last] = splices.slice(-1) return last } }, { guard: (url) => { return url && /myppt\.cc\/\d+/.test(url) }, extractFilename: (url) => { let splices = url.split('/') let [last] = splices.slice(-1) return last } }, ]; const pwdRules = [ { urlGuad: url => url.indexOf('lurl.cc') != -1, cssSelector: 'div.col-sm-12 span.login_span', extractRegx: /\d{4}-(\d{2})-(\d{2})/, join: (month, year) => `${month}${year}`, inputSelector: 'input#password' }, { urlGuad: url => url.indexOf('myppt.cc') != -1, cssSelector: 'div.col-sm-12 span.login_span', extractRegx: /\d{4}-(\d{2})-(\d{2})/, join: (month, year) => `${month}${year}`, inputSelector: 'input#pasahaicsword' }, ]; const videoGuards = [ { urlGuard: (url) => { return url.startsWith('https://lurl.cc/') !== -1 }, videoSelector: '.vjs-tech source', extractFilename: (url) => { let splices = url.split('/') let [last] = splices.slice(-1) return last } } ] let oldFetch = unsafeWindow.fetch; function hookFetch(...args) { oldFetch(...args).then(resp => { for (let guard of urlGuards) { console.log("capture the url ", args[0]) if (guard.guard(args[0])) { downloadFromUrl(args[0], guard.extractFilename(args[0])) } return resp; } }) } function hookDrawImage() { // 1. 获取 Canvas 2D 上下文的原型 const ctxPrototype = unsafeWindow.CanvasRenderingContext2D.prototype; // 2. 保存原始的 drawImage 方法 const originalDrawImage = ctxPrototype.drawImage; // 3. 创建并应用我们的钩子函数 ctxPrototype.drawImage = function (...args) { // 第一个参数就是被绘制的图像源 const imageSource = args[0]; // 检查源是否是 HTMLImageElement 并且有 src 属性 if (imageSource && imageSource instanceof unsafeWindow.HTMLImageElement && imageSource.src) { const url = imageSource.src; // 运行你的守卫逻辑 for (const guard of urlGuards) { if (guard.guard(url)) { console.log(`[Canvas 劫持] 捕获到 drawImage URL: ${url}`); downloadFromUrl(url, guard.extractFilename(url)); break; } } } // 如果源是另一个 Canvas,你也可以处理 else if (imageSource && imageSource instanceof unsafeWindow.HTMLCanvasElement) { console.log("[Canvas 劫持] 正在绘制另一个 Canvas,无法直接获取 URL。"); } // 4. **至关重要**: 调用原始的 drawImage 方法,否则页面无法正常显示 return originalDrawImage.apply(this, args); }; console.log("Canvas.drawImage 劫持成功!"); } function extractPwd(...pwdRules) { for (let pr of pwdRules) { if (pr.urlGuad(document.URL)) { console.log("Match the site, and run fiiling pwd") waitUtil(pr.inputSelector).then(ele => { console.log("find the input ", ele) let updatedDataNode = document.querySelector(pr.cssSelector); let updatedData = updatedDataNode.textContent; let matches = pr.extractRegx.exec(updatedData) console.log("find the node contains pwd, mathes", updatedData, matches) if (matches && matches.length > 2) { console.log(updatedData) let pwd = pr.join(...matches.slice(1)) console.log(ele, pwd) ele.value = pwd } }).catch(err => { console.log("filling pwd failed.", err) }) return } } } function waitUtil(cssSelector) { return new Promise((resolve, reject) => { const observer = new MutationObserver((mutations, obs) => { const ele = document.querySelector(cssSelector); if (ele) { resolve(ele) obs.disconnect() } }) observer.observe(document.body, { childList: true, subtree: true, }); }) } function downloadFromUrl(url, filename) { const headers = { // 关键修复 #1: 添加 Referer 头,告诉服务器我们是从哪个页面来的 'Referer': window.origin, // 关键修复 #2: 模仿浏览器的 Accept 头,让自己看起来更像一个真正的浏览器 'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8', // 为了保险起见,可以把 User-Agent 也明确加上,尽管它看起来是自动继承的 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0' }; let updateProgress = showProgress(); GM.xmlHttpRequest({ method: 'GET', url: url, headers: headers, responseType: 'blob', onprogress: progress => { if (progress.lengthComputable) { const percent = Math.round((progress.loaded / progress.total) * 100); updateProgress(percent / 100) console.log(`[强制模式] 正在获取数据... ${percent}%`); } }, onload: response => { downloadFromBlob(response.response, filename) }, onerror: error => console.error(`[强制模式] 获取数据失败:`, error) }); } function downloadFromBlob(blob, filname) { const blobUrl = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = blobUrl; link.download = filname; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(blobUrl); } /** * 使用闭包创建一个 showProgress 函数. * 这样可以拥有一个私有的 nextTopPosition 变量, 用于在多次调用之间保持状态. */ const showProgress = (() => { // 这个变量存在于闭包中, 不会污染全局作用域, 并且在多次调用之间保持它的值. let nextTopPosition = 10; // 第一个进度条的初始top值 const BAR_HEIGHT = 8; // 进度条的高度 const PADDING_BETWEEN_BARS = 6; // 进度条之间的垂直间距 // IIFE返回的这个函数才是我们最终使用的 showProgress 函数 return function () { // --- 这部分是每次调用时实际执行的代码 --- const progressContainer = document.createElement('div'); progressContainer.style.position = 'fixed'; // 使用闭包中保存的 nextTopPosition 变量 progressContainer.style.top = `${nextTopPosition}px`; progressContainer.style.right = '10px'; progressContainer.style.width = '250px'; progressContainer.style.height = `${BAR_HEIGHT}px`; progressContainer.style.backgroundColor = '#e0e0e0'; progressContainer.style.borderRadius = '5px'; progressContainer.style.overflow = 'hidden'; progressContainer.style.zIndex = '999999'; progressContainer.style.transition = 'opacity 0.5s ease-out'; const progressBar = document.createElement('div'); progressBar.style.width = '0%'; progressBar.style.height = '100%'; progressBar.style.backgroundColor = '#4CAF50'; progressBar.style.borderRadius = '5px'; progressBar.style.transition = 'width 0.2s ease-in-out'; progressContainer.appendChild(progressBar); document.body.appendChild(progressContainer); // 为下一次调用 showProgress 更新 top 值 // 这个操作会直接修改闭包中的 nextTopPosition nextTopPosition += BAR_HEIGHT + PADDING_BETWEEN_BARS; /** * 设置进度. * @param {number} progress - 0 到 1 之间的小数. */ const setProgress = (progress) => { // 注意:你原始代码中的 `initTop += (height + 6)` 被移除了 // 因为位置应该在创建时就固定, 而不是在更新进度时改变 const clampedProgress = Math.max(0, Math.min(1, progress)); progressBar.style.width = `${clampedProgress * 100}%`; // 当进度完成时,自动淡出并移除 if (clampedProgress >= 1) { setTimeout(() => { progressContainer.style.opacity = '0'; setTimeout(() => { progressContainer.remove(); }, 500); // 匹配 transition 的时间 }, 300); // 延迟一会再消失 } }; // 返回进度设置函数 return setProgress; }; })(); (function () { 'use strict'; // drawImage of canvas hookDrawImage() // fetch unsafeWindow.fetch = hookFetch; document.addEventListener('DOMContentLoaded', () => { extractPwd(...pwdRules) // watch video videoGuards.forEach(guard => { if (guard.urlGuard(document.URL)) { waitUtil(guard.videoSelector).then(ele => { console.log("find element ", ele) ele && ele.src && downloadFromUrl(ele.src, guard.extractFilename(ele.src)) }) } }) }) })();