您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
备份你珍贵的水贴为Markdown,包含图片下载,可拖拽调整按钮位置。
// ==UserScript== // @name Linux.do 下崽器 (新版) // @namespace http://linux.do/ // @version 2.0.0 // @description 备份你珍贵的水贴为Markdown,包含图片下载,可拖拽调整按钮位置。 // @author PastKing // @match https://www.linux.do/t/topic/* // @match https://linux.do/t/topic/* // @license MIT // @icon https://cdn.linux.do/uploads/default/optimized/1X/3a18b4b0da3e8cf96f7eea15241c3d251f28a39b_2_32x32.png // @grant none // @require https://unpkg.com/[email protected]/dist/turndown.js // @require https://unpkg.com/[email protected]/dist/jszip.min.js // ==/UserScript== (function() { 'use strict'; let isDragging = false; let isMouseDown = false; // 创建并插入下载按钮 function createDownloadButton() { const button = document.createElement('button'); button.innerHTML = ` <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="margin-right: 6px; vertical-align: middle;"> <path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/> </svg> 下载为压缩包 `; button.style.cssText = ` padding: 12px 20px; font-size: 14px; font-weight: 600; color: #ffffff; background: linear-gradient(135deg, #0088cc 0%, #0099dd 100%); border: none; border-radius: 8px; cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 4px 12px rgba(0, 136, 204, 0.3); position: fixed; z-index: 9999; display: flex; align-items: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; user-select: none; backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); `; // 添加悬停效果 button.onmouseover = function() { this.style.background = 'linear-gradient(135deg, #0077bb 0%, #0088cc 100%)'; this.style.transform = 'translateY(-2px)'; this.style.boxShadow = '0 6px 20px rgba(0, 136, 204, 0.4)'; }; button.onmouseout = function() { this.style.background = 'linear-gradient(135deg, #0088cc 0%, #0099dd 100%)'; this.style.transform = 'translateY(0)'; this.style.boxShadow = '0 4px 12px rgba(0, 136, 204, 0.3)'; }; // 添加点击效果 button.onmousedown = function() { this.style.transform = 'translateY(0) scale(0.98)'; }; button.onmouseup = function() { this.style.transform = 'translateY(-2px) scale(1)'; }; // 从localStorage获取保存的位置 const savedPosition = JSON.parse(localStorage.getItem('downloadButtonPosition')); if (savedPosition) { button.style.left = savedPosition.left; button.style.top = savedPosition.top; } else { button.style.right = '20px'; button.style.bottom = '20px'; } document.body.appendChild(button); return button; } // 创建进度提示组件 function createProgressToast(message) { // 移除已存在的提示 const existingToast = document.getElementById('download-progress-toast'); if (existingToast) { existingToast.remove(); } const toast = document.createElement('div'); toast.id = 'download-progress-toast'; toast.innerHTML = ` <div style="display: flex; align-items: center;"> <div style=" width: 20px; height: 20px; border: 2px solid #ffffff; border-top: 2px solid transparent; border-radius: 50%; animation: spin 1s linear infinite; margin-right: 10px; "></div> <span style="color: #ffffff; font-weight: 500;">${message}</span> </div> `; toast.style.cssText = ` position: fixed; top: 20px; right: 20px; background: linear-gradient(135deg, #0088cc 0%, #0099dd 100%); color: white; padding: 16px 24px; border-radius: 8px; box-shadow: 0 6px 20px rgba(0, 136, 204, 0.4); z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; font-size: 14px; backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); transform: translateX(100%); transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); `; // 添加旋转动画 const style = document.createElement('style'); style.textContent = ` @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; document.head.appendChild(style); document.body.appendChild(toast); // 显示动画 setTimeout(() => { toast.style.transform = 'translateX(0)'; }, 100); return toast; } // 移除进度提示 function removeProgressToast() { const toast = document.getElementById('download-progress-toast'); if (toast) { toast.style.transform = 'translateX(100%)'; setTimeout(() => { toast.remove(); }, 300); } } // 显示成功提示 function showSuccessToast(message) { const toast = document.createElement('div'); toast.innerHTML = ` <div style="display: flex; align-items: center;"> <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="margin-right: 10px; color: #ffffff;"> <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/> </svg> <span style="color: #ffffff; font-weight: 500;">${message}</span> </div> `; toast.style.cssText = ` position: fixed; top: 20px; right: 20px; background: linear-gradient(135deg, #28a745 0%, #34ce57 100%); color: white; padding: 16px 24px; border-radius: 8px; box-shadow: 0 6px 20px rgba(40, 167, 69, 0.4); z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; font-size: 14px; backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); transform: translateX(100%); transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); `; document.body.appendChild(toast); // 显示动画 setTimeout(() => { toast.style.transform = 'translateX(0)'; }, 100); // 自动隐藏 setTimeout(() => { toast.style.transform = 'translateX(100%)'; setTimeout(() => { toast.remove(); }, 300); }, 3000); } // 添加拖拽功能 function makeDraggable(element) { let startX, startY, startLeft, startTop; element.addEventListener('mousedown', startDragging); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', stopDragging); function startDragging(e) { isMouseDown = true; isDragging = false; startX = e.clientX; startY = e.clientY; startLeft = parseInt(element.style.left) || window.innerWidth - parseInt(element.style.right) - element.offsetWidth; startTop = parseInt(element.style.top) || window.innerHeight - parseInt(element.style.bottom) - element.offsetHeight; e.preventDefault(); } function drag(e) { if (!isMouseDown) return; isDragging = true; const dx = e.clientX - startX; const dy = e.clientY - startY; element.style.left = `${startLeft + dx}px`; element.style.top = `${startTop + dy}px`; element.style.right = 'auto'; element.style.bottom = 'auto'; } function stopDragging() { if (isMouseDown && isDragging) { localStorage.setItem('downloadButtonPosition', JSON.stringify({ left: element.style.left, top: element.style.top })); } isMouseDown = false; setTimeout(() => { isDragging = false; }, 10); } } // 下载图片 async function downloadImage(url) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP错误! 状态: ${response.status}`); } return await response.blob(); } catch (error) { console.warn(`下载图片失败: ${url}`, error); return null; } } // 获取文件扩展名 function getFileExtension(url) { try { const urlObj = new URL(url); const pathname = urlObj.pathname; const lastDot = pathname.lastIndexOf('.'); if (lastDot !== -1) { return pathname.slice(lastDot); } } catch (e) { // 忽略URL解析错误 } return '.jpg'; // 默认扩展名 } // 生成安全的文件名 function generateSafeFileName(originalName, index) { // 移除特殊字符 const safeName = originalName.replace(/[<>:"/\\|?*]/g, '_'); return `image_${index}_${safeName}`; } // 提取所有图片URL function extractImageUrls(htmlContent) { const tempDiv = document.createElement('div'); tempDiv.innerHTML = htmlContent; const images = tempDiv.querySelectorAll('img'); const imageUrls = []; images.forEach((img, index) => { const src = img.getAttribute('src'); if (src) { // 处理相对URL const absoluteUrl = new URL(src, window.location.href).href; const alt = img.getAttribute('alt') || `image_${index}`; const extension = getFileExtension(absoluteUrl); const fileName = generateSafeFileName(alt + extension, index); imageUrls.push({ originalUrl: src, absoluteUrl: absoluteUrl, fileName: fileName, alt: alt }); } }); return imageUrls; } // 获取文章内容 function getArticleContent() { const titleElement = document.querySelector('#topic-title > div > h1'); const contentElement = document.querySelector('#post_1 > div.row > div.topic-body.clearfix > div.regular.contents > div.cooked'); if (!titleElement) { console.error('无法找到文章标题'); return null; } if (!contentElement) { console.error('无法找到文章内容'); return null; } return { title: titleElement.textContent.trim(), content: contentElement.innerHTML }; } // 转换为Markdown并下载(包含图片) async function downloadAsMarkdown() { const article = getArticleContent(); if (!article) { alert('无法获取文章内容,请检查网页结构是否变更。'); return; } const button = document.querySelector('button'); const originalButtonHTML = button.innerHTML; // 禁用按钮并显示加载状态 button.disabled = true; button.style.opacity = '0.7'; button.style.cursor = 'not-allowed'; try { // 提取所有图片URL const imageUrls = extractImageUrls(article.content); let toast = createProgressToast('准备下载...'); // 创建ZIP文件 const zip = new JSZip(); const imagesFolder = zip.folder("images"); // 下载所有图片 const imageDownloadPromises = imageUrls.map(async (imageInfo, index) => { toast.querySelector('span').textContent = `下载图片 ${index + 1}/${imageUrls.length}...`; const imageBlob = await downloadImage(imageInfo.absoluteUrl); if (imageBlob) { imagesFolder.file(imageInfo.fileName, imageBlob); return { ...imageInfo, downloaded: true }; } return { ...imageInfo, downloaded: false }; }); const downloadedImages = await Promise.all(imageDownloadPromises); // 创建图片映射 const imageMap = new Map(); downloadedImages.forEach(img => { if (img.downloaded) { imageMap.set(img.originalUrl, `images/${img.fileName}`); } }); toast.querySelector('span').textContent = '生成Markdown...'; // 配置TurndownService const turndownService = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced' }); // 自定义规则处理代码块 turndownService.addRule('codeBlocks', { filter: function (node) { return node.nodeName === 'PRE' && node.querySelector('code'); }, replacement: function (content, node) { const codeElement = node.querySelector('code'); const codeContent = codeElement ? codeElement.textContent : node.textContent; const language = codeElement ? (codeElement.className.match(/language-(\w+)/) || ['', ''])[1] : ''; return `\n\`\`\`${language}\n${codeContent}\n\`\`\`\n`; } }); // 处理单独的code标签 turndownService.addRule('standaloneCode', { filter: function (node) { return node.nodeName === 'CODE' && node.parentNode && node.parentNode.nodeName !== 'PRE' && (node.textContent.includes('\n') || node.textContent.length > 50); }, replacement: function (content, node) { const codeContent = node.textContent; const language = (node.className.match(/language-(\w+)/) || ['', ''])[1]; return `\n\`\`\`${language}\n${codeContent}\n\`\`\`\n`; } }); // 自定义规则处理图片和链接 turndownService.addRule('images_and_links', { filter: ['a', 'img'], replacement: function (content, node) { // 处理图片 if (node.nodeName === 'IMG') { const alt = node.alt || ''; const src = node.getAttribute('src') || ''; const title = node.title ? ` "${node.title}"` : ''; // 使用本地路径(如果图片已下载) const localPath = imageMap.get(src) || src; return ``; } // 处理链接 else if (node.nodeName === 'A') { const href = node.getAttribute('href'); const title = node.title ? ` "${node.title}"` : ''; // 检查链接是否包含图片 const img = node.querySelector('img'); if (img) { const alt = img.alt || ''; const src = img.getAttribute('src') || ''; const imgTitle = img.title ? ` "${img.title}"` : ''; const localPath = imageMap.get(src) || src; return `[](${href}${title})`; } // 普通链接 return `[${node.textContent}](${href}${title})`; } } }); const markdown = `# ${article.title}\n\n${turndownService.turndown(article.content)}`; // 添加Markdown文件到ZIP zip.file(`${article.title}.md`, markdown); toast.querySelector('span').textContent = '生成压缩包...'; const zipBlob = await zip.generateAsync({type: "blob"}); // 下载ZIP文件 const url = URL.createObjectURL(zipBlob); const a = document.createElement('a'); a.href = url; a.download = `${article.title}.zip`; a.click(); URL.revokeObjectURL(url); // 移除进度提示 removeProgressToast(); // 显示成功消息 const downloadedCount = downloadedImages.filter(img => img.downloaded).length; const totalCount = imageUrls.length; showSuccessToast(`下载完成!成功下载图片: ${downloadedCount}/${totalCount}`); } catch (error) { console.error('下载过程中出现错误:', error); removeProgressToast(); alert('下载过程中出现错误,请查看控制台了解详情。'); } finally { // 恢复按钮状态 button.innerHTML = originalButtonHTML; button.disabled = false; button.style.opacity = '1'; button.style.cursor = 'pointer'; } } // 主函数 function main() { const downloadButton = createDownloadButton(); makeDraggable(downloadButton); downloadButton.addEventListener('click', function(e) { if (!isDragging) { downloadAsMarkdown(); } }); // 监听DOM变化,确保按钮始终存在 const observer = new MutationObserver(function(mutations) { if (!document.body.contains(downloadButton)) { document.body.appendChild(downloadButton); } }); // 观察document.body的子节点变化 observer.observe(document.body, { childList: true, subtree: true }); // 处理页面可见性变化 document.addEventListener('visibilitychange', function() { if (document.visibilityState === 'visible' && !document.body.contains(downloadButton)) { document.body.appendChild(downloadButton); } }); } // 运行主函数 main(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址