Automatically downloads preferred format and saves attribution from Sketchfab for one tab at a time. If you can automate this further; give it a try
// ==UserScript==
// @name Sketchfab Auto PreferredFormat Downloader and Attribution Saver Script
// @namespace https://greasyfork.org/users/instancer-kirik
// @version 2.2.2
// @description Automatically downloads preferred format and saves attribution from Sketchfab for one tab at a time. If you can automate this further; give it a try
// @author instancer-kirik
// @match https://sketchfab.com/3d-models/*
// @grant GM_download
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_setClipboard
// @grant GM_notification
// @license Creative Commons Zero v1.0 Universal
// ==/UserScript==
// Built and tested with Violentmonkey on Arc browser for Windows.
(function() {
'use strict';
const DEBUG = true;
function logDebug(message) {
if (DEBUG) {
console.log(`[Sketchfab Script] ${message}`);
}
}
function getAttributionText() {
logDebug('Getting attribution text...');
const attributionElement = document.querySelector('.c-download__licence-box .NTUZeMcY');
if (attributionElement) {
return Promise.resolve(attributionElement.textContent.trim());
} else {
const copyCreditsButton = document.querySelector('.c-download__licence-box button.btn-primary-inverted-border');
if (copyCreditsButton) {
logDebug('Found "Copy credits" button. Clicking it...');
copyCreditsButton.click();
// Wait a bit for the clipboard to be populated
return new Promise(resolve => {
setTimeout(() => {
navigator.clipboard.readText().then(text => {
resolve(text.trim());
}).catch(err => {
logDebug('Failed to read clipboard: ', err);
resolve(fallbackAttributionGather());
});
}, 100);
});
} else {
return Promise.resolve(fallbackAttributionGather());
}
}
}
function handleDownloadModal() {
logDebug('Handling download modal...');
const preferredFormats = ['.blend', '.usdz', '.gltf', '.glb', '.obj','.fbx'];
let selectedButton = null;
for (let format of preferredFormats) {
const buttons = document.querySelectorAll('.c-download__links button.button-source, .c-download__links button.button-extra');
for (let button of buttons) {
const formatElement = button.closest('.AUfL6oST')?.querySelector('.H6stunQl div');
if (formatElement && formatElement.textContent.includes(format)) {
selectedButton = button;
break;
}
}
if (selectedButton) break;
}
if (selectedButton) {
const fileNameElement = document.querySelector('.c-download__title-text span');
const fileName = fileNameElement ? fileNameElement.textContent.trim() : 'sketchfab_model';
const formatElement = selectedButton.closest('.AUfL6oST')?.querySelector('.H6stunQl div');
const format = formatElement ? formatElement.textContent.trim() : '';
logDebug(`Selected format: ${format}`);
// Trigger the download
selectedButton.click();
// Get attribution
getAttributionText().then(attributionText => {
if (attributionText) {
saveAttribution(attributionText, `${fileName}${format}`);
}
}).catch(error => {
logDebug('Error getting attribution: ', error);
});
} else {
logDebug('No suitable download button found.');
}
}
function fallbackAttributionGather() {
const licenseBox = document.querySelector('.c-download__licence-box');
if (licenseBox) {
const titleElement = licenseBox.querySelector('a.skfb-link');
const creatorElement = licenseBox.querySelector('a.skfb-link[href^="/"]');
const licenseElement = licenseBox.querySelector('a.skfb-link[href^="http://creativecommons.org"]');
if (titleElement && creatorElement && licenseElement) {
return `${titleElement.textContent.trim()} by ${creatorElement.textContent.trim()} is licensed under ${licenseElement.textContent.trim()}`;
}
}
return 'No attribution found.';
}
function saveAttribution(attributionText, fileName) {
logDebug('Saving attribution...');
const attributionEntry = `\n${fileName}:\n${attributionText}\n`;
// Read existing attributions
let existingAttributions = '';
try {
existingAttributions = GM_getValue('attributions', '');
} catch (error) {
logDebug('Error reading existing attributions: ' + error);
}
// Append new attribution
const newAttributions = existingAttributions + attributionEntry;
// Save updated attributions
try {
GM_setValue('attributions', newAttributions);
} catch (error) {
logDebug('Error saving attributions: ' + error);
}
// Create or update attributions.txt file
const blob = new Blob([newAttributions], { type: 'text/plain' });
GM_download({
url: URL.createObjectURL(blob),
name: 'attributions.txt',
saveAs: false
});
}
function downloadModel(url, fileName) {
logDebug(`Downloading: ${fileName}`);
GM_download({
url: url,
name: fileName,
saveAs: false
});
}
function clickDownloadButton() {
const downloadButton = document.querySelector('button[data-selenium="open-download-popup"]');
if (downloadButton) {
logDebug('Clicking download button...');
downloadButton.click();
} else {
logDebug('Download button not found.');
}
}
function waitForModal() {
logDebug('Waiting for modal...');
const observer = new MutationObserver((mutations, obs) => {
const modal = document.querySelector('article[data-element="popup"]');
if (modal) {
logDebug('Modal found. Handling download...');
obs.disconnect();
setTimeout(handleDownloadModal, 1000); // Wait for modal content to load
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
// Main execution
function main() {
logDebug('Script started.');
clickDownloadButton();
waitForModal();
}
// Run the script after a short delay to ensure the page is fully loaded
setTimeout(main, 2000);
})();