您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Download/extract all files from a Claude project as a single ZIP
当前为
// ==UserScript== // @name Claude Project Files Extractor // @namespace http://tampermonkey.net/ // @version 2.0 // @description Download/extract all files from a Claude project as a single ZIP // @author sharmanhall // @match https://claude.ai/* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // Load JSZip from CDN first function loadJSZip() { return new Promise((resolve, reject) => { if (typeof JSZip !== 'undefined') { console.log('JSZip already available'); resolve(); return; } console.log('Loading JSZip from CDN...'); const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js'; script.onload = () => { console.log('JSZip script loaded'); // Wait a bit for it to be available setTimeout(() => { if (typeof JSZip !== 'undefined') { console.log('JSZip is now available'); resolve(); } else { reject(new Error('JSZip loaded but not available')); } }, 500); }; script.onerror = () => { console.error('Failed to load JSZip'); reject(new Error('Failed to load JSZip')); }; document.head.appendChild(script); }); } // Helper function to wait for modal to close async function waitForModalClose(timeout = 3000) { const startTime = Date.now(); while (Date.now() - startTime < timeout) { const modal = document.querySelector('[role="dialog"]'); if (!modal) return true; await new Promise(resolve => setTimeout(resolve, 100)); } return false; } // Function to close modal async function closeModal() { // Try multiple close methods const closeButtons = document.querySelectorAll('button[aria-label*="close"], button[aria-label*="Close"], [data-testid*="close"]'); for (const btn of closeButtons) { btn.click(); await new Promise(resolve => setTimeout(resolve, 200)); } // Press Escape document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })); document.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape', bubbles: true })); // Wait for modal to close await waitForModalClose(); } // Extract project knowledge files async function extractProjectFiles() { const files = []; console.log('🔍 Looking for project knowledge files...'); // Find file cards in the sidebar const fileCards = document.querySelectorAll('div[class*="cursor-pointer"], button[class*="cursor-pointer"], [role="button"]'); for (const card of fileCards) { try { const text = card.textContent; // Look for cards with file indicators if (text.includes('PDF') || text.includes('TEXT') || text.includes('lines')) { console.log(`📄 Found file card: ${text.substring(0, 50)}...`); // Extract file info using better regex let title = 'Unknown File'; let fileType = 'txt'; // Try to parse the file card text const cleanText = text.replace(/\s+/g, ' ').trim(); // Look for patterns like "filename.pdf214 linespdf" const match = cleanText.match(/^(.+?)(\d+\s*lines\s*(pdf|text))/i); if (match) { title = match[1].trim(); fileType = match[3] === 'pdf' ? 'pdf.txt' : 'txt'; } // Special handling for project instructions/prompts (based on title only for now) if (title.includes('Project Assistant') || title.includes('When responding to requests') || title.includes('specialized project assistant') || title.includes('You are a specialized') || title.toLowerCase().includes('prompt')) { title = 'Project Instructions'; fileType = 'txt'; } // Clean up long titles generically if (title.length > 60) { // Extract meaningful parts from long titles const words = title.split(' '); if (words.length > 8) { // Take first few meaningful words and last word if it looks like a file type const start = words.slice(0, 4).join(' '); const end = words[words.length - 1]; if (end.includes('.') || end.toLowerCase().includes('pdf') || end.toLowerCase().includes('doc')) { title = `${start}...${end}`; } else { title = `${start}...`; } } } console.log(`⚡ Clicking: ${title}`); card.click(); // Wait for modal to load await new Promise(resolve => setTimeout(resolve, 2000)); // Extract content from modal let content = ''; const modal = document.querySelector('[role="dialog"]'); if (modal) { // Get all text but filter out UI elements const allText = modal.textContent; const lines = allText.split('\n') .map(line => line.trim()) .filter(line => line.length > 3) .filter(line => !line.match(/^(Close|Download|Export|PDF|TEXT|\d+\s*lines|Select file)$/i)); if (lines.length > 10) { content = lines.join('\n'); } } // Special handling for project instructions/prompts (check content after extraction) if (content.includes('Project Assistant Prompt') || content.includes('specialized project assistant') || content.includes('Your Mission') || content.includes('You are a specialized') || (content.includes('prompt') && content.includes('assistant'))) { title = 'Project Instructions'; fileType = 'txt'; } console.log(`✅ Extracted ${content.length} characters from: ${title}`); if (content.length > 50) { files.push({ filename: `${title.replace(/[^a-zA-Z0-9\s\-_\.]/g, '_')}.${fileType}`, content: content }); } // Close modal await closeModal(); await new Promise(resolve => setTimeout(resolve, 1000)); } } catch (error) { console.error('Error processing file:', error); } } return files; } // Create and download ZIP async function createZIP(files, projectName) { try { console.log('📦 Creating ZIP with JSZip...'); if (typeof JSZip === 'undefined') { throw new Error('JSZip not available'); } const zip = new JSZip(); // Add each file to ZIP files.forEach(file => { console.log(`📁 Adding to ZIP: ${file.filename}`); zip.file(file.filename, file.content); }); // Add metadata const metadata = { exportDate: new Date().toISOString(), projectTitle: projectName, url: window.location.href, fileCount: files.length }; zip.file('_metadata.json', JSON.stringify(metadata, null, 2)); console.log('🔄 Generating ZIP blob...'); // Generate ZIP const zipBlob = await zip.generateAsync({ type: "blob", compression: "DEFLATE", compressionOptions: { level: 6 } }); console.log(`✅ ZIP created! Size: ${zipBlob.size} bytes`); // Download ZIP const timestamp = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 16); const filename = `${projectName.replace(/[^a-zA-Z0-9]/g, '_')}_export_${timestamp}.zip`; const url = URL.createObjectURL(zipBlob); const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); return true; } catch (error) { console.error('❌ ZIP creation failed:', error); return false; } } // Download individual files as fallback function downloadIndividualFiles(files, projectName) { console.log('📥 Falling back to individual downloads...'); files.forEach((file, index) => { setTimeout(() => { const blob = new Blob([file.content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = file.filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }, index * 300); // Stagger downloads }); } // Get project title function getProjectTitle() { const titleSelectors = ['h1', '[data-testid*="title"]', '.text-xl']; for (const selector of titleSelectors) { const element = document.querySelector(selector); if (element && element.textContent.trim()) { return element.textContent.trim(); } } return 'Claude_Project'; } // Main export function async function exportProject() { const button = document.querySelector('#claude-export-btn'); try { // Update button status const updateStatus = (msg) => { if (button) button.textContent = `🔄 ${msg}`; console.log(msg); }; updateStatus('Loading ZIP library...'); await loadJSZip(); updateStatus('Finding files...'); const files = await extractProjectFiles(); if (files.length === 0) { updateStatus('❌ No files found'); setTimeout(() => { if (button) button.textContent = '📁 Export Project Files'; }, 2000); return; } const projectName = getProjectTitle(); updateStatus(`Creating ZIP (${files.length} files)...`); const zipSuccess = await createZIP(files, projectName); if (zipSuccess) { updateStatus(`✅ ZIP exported! (${files.length} files)`); setTimeout(() => { if (button) button.textContent = '📁 Export Project Files'; }, 3000); } else { updateStatus('ZIP failed - downloading individual files...'); downloadIndividualFiles(files, projectName); setTimeout(() => { if (button) button.textContent = '📁 Export Project Files'; }, 3000); } } catch (error) { console.error('💥 Export failed:', error); if (button) button.textContent = '❌ Export Failed'; setTimeout(() => { if (button) button.textContent = '📁 Export Project Files'; }, 3000); } } // Add export button function addExportButton() { const existingButton = document.querySelector('#claude-export-btn'); if (existingButton) existingButton.remove(); const button = document.createElement('button'); button.id = 'claude-export-btn'; button.textContent = '📁 Export Project Files'; button.style.cssText = ` position: fixed; bottom: 20px; right: 20px; padding: 12px 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; cursor: pointer; z-index: 10000; font-size: 14px; font-weight: 600; box-shadow: 0 4px 15px rgba(0,0,0,0.2); transition: all 0.3s ease; `; button.addEventListener('click', exportProject); document.body.appendChild(button); } // Initialize function init() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', addExportButton); } else { addExportButton(); } // Re-add button on navigation let currentUrl = location.href; const observer = new MutationObserver(() => { if (location.href !== currentUrl) { currentUrl = location.href; setTimeout(addExportButton, 1000); } }); observer.observe(document.body, { childList: true, subtree: true }); } init(); })();