您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Attempts to download the actual model (glTF/glb preferred) and textures (PNG) from Sketchfab viewer. Downloads geometry only if actual data is located.
当前为
// ==UserScript== // @name ncikkis sketchfab downloader // @namespace http://tampermonkey.net/ // @version 1.2 // Version incremented for revised geometry logic // @description Attempts to download the actual model (glTF/glb preferred) and textures (PNG) from Sketchfab viewer. Downloads geometry only if actual data is located. // @author ncikkis // @match *://sketchfab.com/3d-models/* // @icon https://www.google.com/s2/favicons?sz=64&domain=sketchfab.com // @grant GM_download // @grant GM_xmlhttpRequest // Kept for potential future network inspection needs // @grant unsafeWindow // @run-at document-idle // ==/UserScript== (function() { 'use strict'; const LOG_PREFIX = '[ncikkis_SFD]:'; let apiInstance = null; let downloadButton = null; console.log(`${LOG_PREFIX} Initializing v1.2...`); function findApiInstance() { // Attempt to find the Sketchfab viewer API instance (speculative) if (unsafeWindow.viewerApp) { if (typeof unsafeWindow.viewerApp.getViewer === 'function') { let viewer = unsafeWindow.viewerApp.getViewer(); if (viewer && typeof viewer.getApi === 'function') return viewer.getApi(); } if(unsafeWindow.viewerApp && unsafeWindow.viewerApp.viewers) { const viewerKeys = Object.keys(unsafeWindow.viewerApp.viewers); if (viewerKeys.length > 0) { const viewer = unsafeWindow.viewerApp.viewers[viewerKeys[0]]; if (viewer && viewer.api) return viewer.api; } } } if (unsafeWindow.api) return unsafeWindow.api; console.log(`${LOG_PREFIX} Viewer API instance not found via common patterns.`); return null; } function createDownloadButton() { if (document.getElementById('ncikkis-sf-download-button')) return; const viewerElement = document.querySelector('.viewer'); if (!viewerElement) { console.log(`${LOG_PREFIX} Viewer element not found.`); return; } downloadButton = document.createElement('button'); downloadButton.id = 'ncikkis-sf-download-button'; downloadButton.textContent = 'Download Model'; // Apply styles (same as before) downloadButton.style.position = 'absolute'; downloadButton.style.top = '10px'; downloadButton.style.right = '10px'; downloadButton.style.zIndex = '9999'; downloadButton.style.padding = '8px 12px'; downloadButton.style.backgroundColor = '#FF0000'; downloadButton.style.color = 'white'; downloadButton.style.border = 'none'; downloadButton.style.borderRadius = '4px'; downloadButton.style.cursor = 'pointer'; downloadButton.style.fontSize = '12px'; downloadButton.style.fontFamily = 'sans-serif'; downloadButton.style.fontWeight = 'bold'; downloadButton.style.boxShadow = '0 2px 5px rgba(0,0,0,0.3)'; downloadButton.addEventListener('click', handleDownload); viewerElement.style.position = 'relative'; viewerElement.appendChild(downloadButton); console.log(`${LOG_PREFIX} Download button injected.`); } function getModelName() { const titleElement = document.querySelector('.model-name__label'); if (titleElement) return titleElement.textContent.trim().replace(/[^a-zA-Z0-9_-]/g, '_'); const urlParts = window.location.pathname.split('/'); const modelSlug = urlParts[urlParts.length - 2]; if (modelSlug && modelSlug !== '3d-models') return modelSlug.replace(/[^a-zA-Z0-9_-]/g, '_'); const modelId = urlParts[urlParts.length - 1]; if (modelId) return modelId.replace(/[^a-zA-Z0-9_-]/g, '_'); return 'sketchfab_model'; } function handleDownload() { if (!apiInstance) { console.error(`${LOG_PREFIX} API instance not available.`); alert('ncikkis Downloader: Could not access Sketchfab API instance.'); return; } console.log(`${LOG_PREFIX} Download initiated. Attempting to locate actual model data...`); downloadButton.textContent = 'Processing...'; downloadButton.disabled = true; const modelName = getModelName(); let texturesDownloaded = false; let geometryDownloaded = false; // Callback to run after both attempts (textures & geometry) are complete const finalizeCheck = () => { if (!geometryDownloaded) { console.warn(`${LOG_PREFIX} Failed to locate or extract actual geometry data.`); alert(`ncikkis Downloader: Failed to locate downloadable geometry data for this model. Texture download may have been attempted.`); } else { console.log(`${LOG_PREFIX} Geometry download initiated.`); if (!texturesDownloaded) { console.warn(`${LOG_PREFIX} Texture download attempt complete (may have failed or found no textures).`); alert(`ncikkis Downloader: Geometry download initiated. Texture download may have failed or no textures were found.`); } else { console.log(`${LOG_PREFIX} Texture download attempt complete.`); alert(`ncikkis Downloader: Geometry and texture download initiated. Check your downloads.`); } } // Reset button regardless of success downloadButton.textContent = 'Download Model'; downloadButton.disabled = false; }; let textureAttemptComplete = false; let geometryAttemptComplete = false; // --- Attempt Texture Download --- try { apiInstance.getTextureList((err, textures) => { if (err || !textures || textures.length === 0) { console.warn(`${LOG_PREFIX} Could not retrieve texture list via API or no textures found.`); } else { console.log(`${LOG_PREFIX} Found ${textures.length} textures. Downloading...`); texturesDownloaded = true; // Mark attempt even if individual downloads fail textures.forEach((texture, index) => { if (texture && texture.url) { const textureName = texture.name || `texture_${index}`; const sanitizedTextureName = textureName.replace(/[^a-zA-Z0-9_-]/g, '_'); const filename = `${modelName}_${sanitizedTextureName}.png`; console.log(`${LOG_PREFIX} Downloading texture: ${filename} from ${texture.url}`); GM_download({ url: texture.url, name: filename, onerror: (err) => console.error(`${LOG_PREFIX} Error downloading texture ${filename}:`, err), onload: () => console.log(`${LOG_PREFIX} Texture ${filename} download initiated.`) }); } else { console.warn(`${LOG_PREFIX} Invalid texture object or URL for texture index ${index}:`, texture); } }); } textureAttemptComplete = true; if (geometryAttemptComplete) finalizeCheck(); }); } catch (e) { console.error(`${LOG_PREFIX} Error during API interaction (getTextureList):`, e); textureAttemptComplete = true; if (geometryAttemptComplete) finalizeCheck(); } // --- Attempt Geometry Download (Focus on glTF/glb) --- console.log(`${LOG_PREFIX} Searching for geometry data (glTF/glb preferred)...`); try { // Speculative ways to find the model URL or data: // 1. Check API response (Hypothetical: maybe getNodeMap or another function returns model URL) // 2. Inspect `unsafeWindow` for viewer configuration objects // 3. Check performance entries for loaded resources matching common patterns let modelUrl = null; let modelFormat = null; // 'gltf' or 'glb' // Example: Hypothetical check within unsafeWindow structure (adjust based on actual inspection) if (unsafeWindow.viewerApp && unsafeWindow.viewerApp.config && unsafeWindow.viewerApp.config.model) { let modelConf = unsafeWindow.viewerApp.config.model; if (modelConf.gltfUrl) { modelUrl = modelConf.gltfUrl; modelFormat = 'gltf'; } else if (modelConf.glbUrl) { modelUrl = modelConf.glbUrl; modelFormat = 'glb'; } // Add more checks based on observed structure } // Example: Check performance entries (less reliable, might miss dynamically loaded URLs) if (!modelUrl) { try { const resources = performance.getEntriesByType('resource'); const sketchfabResources = resources.filter(r => r.name.includes('sketchfab.com') || r.name.includes('skfb.ly')); // Look for likely candidates based on URL patterns (e.g., containing 'models', 'gltf', 'glb') const gltfResource = sketchfabResources.find(r => r.name.endsWith('.gltf') || r.name.includes('format=gltf')); const glbResource = sketchfabResources.find(r => r.name.endsWith('.glb') || r.name.includes('format=glb')); if (glbResource) { modelUrl = glbResource.name; modelFormat = 'glb'; } else if (gltfResource) { modelUrl = gltfResource.name; modelFormat = 'gltf'; } } catch (perfError) { console.warn(`${LOG_PREFIX} Could not access or parse performance entries:`, perfError); } } // Example: Hypothetical API call (if Sketchfab API offered such a thing) // if (!modelUrl && typeof apiInstance.getModelUrl === 'function') { ... } // --- Conditional Download --- if (modelUrl && modelFormat) { console.log(`${LOG_PREFIX} Found potential model URL (${modelFormat}): ${modelUrl}`); const filename = `${modelName}.${modelFormat}`; try { GM_download({ url: modelUrl, name: filename, onerror: (err) => { console.error(`${LOG_PREFIX} Error downloading geometry file ${filename}:`, err); geometryDownloaded = false; // Mark as failed on error geometryAttemptComplete = true; if (textureAttemptComplete) finalizeCheck(); }, onload: () => { console.log(`${LOG_PREFIX} Geometry file ${filename} download initiated.`); geometryDownloaded = true; // Mark success geometryAttemptComplete = true; if (textureAttemptComplete) finalizeCheck(); }, ontimeout: () => { console.error(`${LOG_PREFIX} Timeout downloading geometry file ${filename}.`); geometryDownloaded = false; geometryAttemptComplete = true; if (textureAttemptComplete) finalizeCheck(); } }); } catch (downloadError) { console.error(`${LOG_PREFIX} Error setting up geometry download:`, downloadError); geometryDownloaded = false; geometryAttemptComplete = true; if (textureAttemptComplete) finalizeCheck(); } } else { // If no URL or data found after all checks console.warn(`${LOG_PREFIX} Could not locate a downloadable geometry URL (glTF/glb) via known methods.`); geometryDownloaded = false; geometryAttemptComplete = true; if (textureAttemptComplete) finalizeCheck(); } } catch (e) { console.error(`${LOG_PREFIX} Error during geometry search/download attempt:`, e); geometryDownloaded = false; geometryAttemptComplete = true; if (textureAttemptComplete) finalizeCheck(); } } // --- Initialization Logic --- const initCheckInterval = setInterval(() => { apiInstance = findApiInstance(); const viewerElement = document.querySelector('.viewer'); if (apiInstance && viewerElement) { clearInterval(initCheckInterval); console.log(`${LOG_PREFIX} Sketchfab API instance potentially found.`, apiInstance); createDownloadButton(); } else if (document.readyState === "complete") { // Allow interval to continue checking } }, 2000); setTimeout(() => { if (!apiInstance || !document.getElementById('ncikkis-sf-download-button')) { clearInterval(initCheckInterval); console.warn(`${LOG_PREFIX} Initialization timed out. API instance or viewer element not found.`); } }, 30000); })();