您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动滚动收集爱发电公开帖子图片/媒体链接,等加载完保存JSON并下载(仅限公开内容,遵守网站条款)
// ==UserScript== // @name 爱发电图片批量下载器 // @namespace http://tampermonkey.net/ // @version 6.0 // @description 自动滚动收集爱发电公开帖子图片/媒体链接,等加载完保存JSON并下载(仅限公开内容,遵守网站条款) // @author vifdi (by Grok4 Fast) // @match https://afdian.com/* // @match https://afdian.net/* // @grant GM_download // @license MIT // ==/UserScript== /** * 爱发电图片批量下载 * * 用途:辅助用户在爱发电网站自动滚动加载公开帖子,收集图片/音频/视频链接,保存为JSON文档,并下载到自定义文件夹。 * 仅支持公开内容(有权限的帖子),不绕过付费墙。 * * 警告: * - 此脚本仅供个人学习/使用,严禁用于商业或非法目的。 * - 使用前确保遵守爱发电服务条款(https://afdian.net/terms),勿滥用导致账号封禁。 * - 作者不承担任何法律/道德责任。 * * MIT License: https://opensource.org/licenses/MIT */ (function() { 'use strict'; // 创建浮动按钮 let floatButton = document.createElement("button"); floatButton.textContent = "启动自动收集"; floatButton.style.cssText = ` position: fixed; bottom: 20px; right: 20px; z-index: 9999; padding: 10px 20px; font-size: 1rem; color: #fff; background: #007bff; border: none; border-radius: 5px; cursor: pointer; `; document.body.appendChild(floatButton); let autoScrolling = false; let lastXHRTime = Date.now(); let allLinks = []; // 全收集:[{title, pics: [{url, filename}]}] let uniquePosts = new Set(); // 去重post_id let totalPics = 0; let hasMore = true; let scrollCheck = 0; let rootFolder = ''; // 自定义根文件夹 floatButton.addEventListener('click', function() { if (!autoScrolling) { // 弹出输入框自定义根文件夹 rootFolder = prompt("输入根文件夹名 (默认: afdian_public_dump):", "afdian_public_dump") || "afdian_public_dump"; rootFolder = rootFolder.replace(/[/\\:*?"<>|]/g, '_'); // 转义非法字符 this.textContent = "停止收集"; this.style.background = "#28a745"; // 绿色运行中 autoScrolling = true; allLinks = []; uniquePosts.clear(); totalPics = 0; hasMore = true; scrollCheck = 0; autoScroll(); } else { this.textContent = "启动自动收集"; this.style.background = "#007bff"; autoScrolling = false; } }); function autoScroll() { if (autoScrolling) { const prevHeight = document.body.scrollHeight; // 渐进滚动:模拟用户行为,避免检测 window.scrollTo(0, document.body.scrollHeight); setTimeout(() => { if (document.body.scrollHeight === prevHeight && Date.now() - lastXHRTime > 4000 && !hasMore) { scrollCheck++; if (scrollCheck >= 3) { // 稳定3次=全加载 console.log("全公开帖子加载完成,开始保存JSON+下载"); saveAndDownloadAll(); autoScrolling = false; floatButton.textContent = "收集完成"; return; } } else { scrollCheck = 0; } setTimeout(autoScroll, 1500); // 1.5s间隔,平衡速度与安全 }, 800); } } // 双重劫持XHR+Fetch function detectRequests() { // XHR const originalXHROpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url) { this.addEventListener('load', function() { if (this.responseURL && this.responseURL.includes("/api/post/get-list")) { lastXHRTime = Date.now(); try { const responseData = JSON.parse(this.responseText); hasMore = responseData.data?.has_more || false; handleResponseData(responseData); } catch (e) { console.error("XHR解析失败: ", e); } } }); return originalXHROpen.apply(this, arguments); }; // Fetch const originalFetch = window.fetch; window.fetch = function(...args) { return originalFetch.apply(this, args).then(response => { const url = args[0]; if (url && url.includes("/api/post/get-list")) { lastXHRTime = Date.now(); return response.clone().text().then(text => { try { const responseData = JSON.parse(text); hasMore = responseData.data?.has_more || false; handleResponseData(responseData); } catch (e) { console.error("Fetch解析失败: ", e); } return response; }); } return response; }); }; } function handleResponseData(data) { const list = data.data?.list || []; list.forEach((item) => { const postId = item.post_id; // 开源合法:只处理有权限的公开帖 if (item.has_right_errMsg !== null || uniquePosts.has(postId)) return; uniquePosts.add(postId); // 无title用content作为文件夹名(截取50字符) let title = item.title || (item.content ? item.content.substring(0, 50) : `post_${postId}`); title = title.replace(/[/\\:*?"<>|]/g, '_'); const pics = item.pics || []; console.log(`收集公开帖子: ${title}, ${pics.length} 张图片`); allLinks.push({ title, pics: pics.map((url, idx) => { const ext = url.split('.').pop().split('?')[0] || 'jpg'; return { url, filename: `${title}_${idx + 1}.${ext}` }; }) }); totalPics += pics.length; // 支持audio/video if (item.audio) allLinks[allLinks.length - 1].audio = { url: item.audio, filename: `${title}_audio.mp3` }; if (item.video) allLinks[allLinks.length - 1].video = { url: item.video, filename: `${title}_video.mp4` }; }); floatButton.textContent = `收集中: ${totalPics} 张 (剩余: ${hasMore ? '有' : '无'})`; } function saveAndDownloadAll() { try { // 保存JSON到根 const jsonData = JSON.stringify(allLinks, null, 2); const blob = new Blob([jsonData], { type: 'application/json' }); const blobUrl = URL.createObjectURL(blob); GM_download({ url: blobUrl, name: `${rootFolder}/afdian_public_links.json`, saveAs: false }); console.log(`JSON文档保存完成 (根: ${rootFolder})`); // 批量下载到根/子文件夹 allLinks.forEach(post => { const subFolder = `${rootFolder}/${post.title}`; post.pics?.forEach(pic => { GM_download({ url: pic.url, name: `${subFolder}/${pic.filename}`, saveAs: false }); }); if (post.audio) { GM_download({ url: post.audio.url, name: `${subFolder}/${post.audio.filename}`, saveAs: false }); } if (post.video) { GM_download({ url: post.video.url, name: `${subFolder}/${post.video.filename}`, saveAs: false }); } }); console.log(`批量下载启动: ${totalPics} 张图片 + 其他 (仅公开内容)`); } catch (e) { console.error("下载过程出错: ", e); alert("下载出错,请检查控制台"); } } detectRequests(); console.log("爱发电图片批量下载 加载完成 - 仅公开内容,遵守条款"); })();