您需要先安装一个扩展,例如 篡改猴、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.33 // @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/* // @match https://mork.ro/* // @match https://risu.io/* // @run-at document-start // @grant GM_download // @grant GM.xmlHttpRequest // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @grant unsafeWindow // @connect lurl.cc // @connect *.lurl.cc // @connect risu.io // @connect mork.ro // @connect myppt.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 } }, { guard: (url) => { return url && url.indexOf('storage') !== -1 }, 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' }, { urlGuad: url => url.indexOf('mork.ro') != -1, cssSelector: '#passwordVerifyForm>h3:first-of-type', extractRegx: /\d{4}-(\d{2})-(\d{2})/, join: (month, year) => `${month}${year}`, inputSelector: 'input#id_pass_key' }, ]; 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 } }, { urlGuard: (url) => { return url.startsWith('https://myppt.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) { putMsg("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) { let { putMsg } = window.debugPanel // 第一个参数就是被绘制的图像源 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)) { putMsg(`[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) { let { putMsg } = window.debugPanel for (let pr of pwdRules) { if (pr.urlGuad(document.URL)) { putMsg("Match the site, and run fiiling pwd") waitUtil(pr.inputSelector).then(ele => { putMsg("find the input ", ele) let updatedDataNode = document.querySelector(pr.cssSelector); let updatedData = updatedDataNode.textContent; let matches = pr.extractRegx.exec(updatedData) putMsg("find the node contains pwd, mathes", updatedData, matches) if (matches && matches.length > 2) { putMsg(updatedData) let pwd = pr.join(...matches.slice(1)) putMsg(ele, pwd) ele.value = pwd } }).catch(err => { putMsg(`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, 'sec-fetch-site': 'same-site', 'sec-fetch-mode': 'no-cors', 'sec-fetch-dest': 'video', }; let updateProgress = showProgress(); let { putMsg } = window.debugPanel; 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) putMsg(`[强制模式] 正在获取数据... ${percent}%`); } }, onload: response => { putMsg("[强制模式]非二进制: ", response) downloadFromBlob(response.response, filename) }, onerror: error => { if (error.status === 200 && error.response) { putMsg(`[强制模式] 获取数据成功 (onerror fallback)`); downloadFromBlob(error.response, filename); } else { putMsg(`[强制模式] 获取数据失败: ${JSON.stringify(error)}`); const result = confirm("Whether to retry???") if (result) { downloadFromUrl(url, filename) } } } }); } 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; }; })(); /** * Displays and manages a debug panel in the bottom-left corner of the page. * @returns {{putMsg: function(any): void}} An object containing the putMsg method. */ function showDebugPanel(on = true) { if (!on) { let putMsg = (...msg) => { console.log(...msg) } window.debugPanel = { putMsg } return { putMsg: putMsg } } // Check if the debug panel already exists. If so, return its interface directly. if (document.getElementById('debug-panel-container')) { return window.debugPanel; } // 1. Create the HTML structure for the debug panel. const panelContainer = document.createElement('div'); panelContainer.id = 'debug-panel-container'; const panelHeader = document.createElement('div'); panelHeader.id = 'debug-panel-header'; panelHeader.textContent = 'Debug Panel'; const closeButton = document.createElement('span'); closeButton.id = 'debug-panel-close'; closeButton.textContent = '×'; // Close button character const panelContent = document.createElement('div'); panelContent.id = 'debug-panel-content'; panelHeader.appendChild(closeButton); panelContainer.appendChild(panelHeader); panelContainer.appendChild(panelContent); document.body.appendChild(panelContainer); // 2. Add CSS styles for the debug panel. const style = document.createElement('style'); style.textContent = ` #debug-panel-container { position: fixed; bottom: 15px; left: 15px; width: 400px; max-width: 90%; height: 250px; background-color: rgba(0, 0, 0, 0.75); border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); color: #fff; display: flex; flex-direction: column; font-family: monospace, sans-serif; font-size: 14px; z-index: 9999; transition: opacity 0.3s, transform 0.3s; } #debug-panel-header { padding: 8px 12px; background-color: rgba(0, 0, 0, 0.4); font-weight: bold; cursor: move; /* Optional: reserve for future drag functionality */ } #debug-panel-close { float: right; cursor: pointer; font-size: 20px; line-height: 1; font-weight: bold; } #debug-panel-close:hover { color: #ff5555; } #debug-panel-content { padding: 10px; overflow-y: auto; flex-grow: 1; color: #fff; word-wrap: break-word; /* Ensure long strings can wrap */ } .debug-message { padding-bottom: 5px; border-bottom: 1px solid #444; margin-bottom: 5px; color: #fff; } .debug-message:last-child { border-bottom: none; } `; document.head.appendChild(style); // 3. Implement the core functionality. const putMsg = (...msg) => { const isScrolledToBottom = panelContent.scrollHeight - panelContent.clientHeight <= panelContent.scrollTop + 1; // Format the message content. let formattedMsg = msg.map(item => { if (typeof item === 'object' && item !== null) { return JSON.stringify(item, null, 2); } else { return item } }).join(" ") const time = new Date().toLocaleTimeString(); const msgElement = document.createElement('div'); msgElement.className = 'debug-message'; msgElement.innerHTML = `<strong>[${time}]</strong><pre style="margin:0; white-space: pre-wrap;color:#fff;">${formattedMsg}</pre>`; panelContent.appendChild(msgElement); // If it was already scrolled to the bottom, auto-scroll to the new bottom. if (isScrolledToBottom) { panelContent.scrollTop = panelContent.scrollHeight; } }; // Close button functionality. closeButton.addEventListener('click', () => { panelContainer.style.opacity = '0'; panelContainer.style.transform = 'scale(0.9)'; setTimeout(() => panelContainer.style.display = 'none', 300); }); // Expose the interface to the global scope for easy access and to implement the singleton pattern. window.debugPanel = { putMsg }; return window.debugPanel; } (function () { 'use strict'; // drawImage of canvas hookDrawImage() // fetch unsafeWindow.fetch = hookFetch; document.addEventListener('DOMContentLoaded', () => { let on = GM_getValue('debug_mode', false); // 2. 注册一个菜单命令 GM_registerMenuCommand('是否debug', () => { // 点击菜单时,执行这里的代码 // a. 切换 on 变量的状态 (true -> false, false -> true) on = !on; // b. 将新的状态保存到存储中,以便下次打开页面时保持该状态 GM_setValue('debug_mode', on); // c. 给用户一个明确的反馈 alert(`debug功能已设置为:${on ? '✅ 开启' : '❌ 关闭'}\n\n请刷新页面使设置生效。`); }); let { putMsg } = showDebugPanel(on) extractPwd(...pwdRules) // watch video for (let guard of videoGuards) { if (guard.urlGuard(document.URL)) { waitUtil(guard.videoSelector).then(ele => { putMsg("find element", ele) ele && ele.src && downloadFromUrl(ele.src, guard.extractFilename(ele.src)) }).catch(err => { putMsg(`download video ${err}`) }) putMsg("match the url of the video", document.URL) break; } } }) })();