Skip to content

Instantly share code, notes, and snippets.

@Petschko
Created December 18, 2019 00:19
Show Gist options
  • Save Petschko/bf6804804562622deefaf55c0f672aaf to your computer and use it in GitHub Desktop.
Save Petschko/bf6804804562622deefaf55c0f672aaf to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Deviantart batch Downloader
// @namespace https://petschko.org/deviantart/batch-downloader
// @description Batch download all faves
// @author Peter Dragicevic [[email protected]]
// @version 1.0.0
// @encoding utf-8
// @homepage https://petschko.org/
// @include http://deviantart.com/*
// @include https://deviantart.com/*
// @include http://*.deviantart.com/*
// @include https://*.deviantart.com/*
// @grant unsafeWindow
// @grant GM.xmlHttpRequest
// @grant GM.addStyle
// @grant GM.getResourceText
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.info
// @grant GM.registerMenuCommand
// @run-at document-start
// @connect *
// ==/UserScript==
(function() {
'use strict';
// Setting vars
const scriptName = 'Deviantart-Downloader';
const serverAjaxUrl = 'http://localhost/serverSaveFile.php';
const createDirsForArtists = true;
const debug = true; // Turns on all kind of messages, rather annoying for daily use, mostly for development, keep it false
// Internal vars
let dlButton = null;
let favesFound = 0;
let favesProdcessed = 0;
/**
* Posts an AJAX request to the local web server
*
* @param {String} data - Data of the AJAX request
* @param {function} success - response callback
* @returns {*}
*/
function postAjax(data, success) {
// Use the GM.xmlHttpRequest function to ignore that shitty cross-origin police
return GM.xmlHttpRequest({
method: 'POST',
url: serverAjaxUrl,
data: data,
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
onload: function(res) {
success(res.responseText);
},
onerror: function(res) {
const msg = '[' + scriptName + ']: An XHR error occurred.'
+ "\nresponseText: " + res.responseText
+ "\nreadyState: " + res.readyState
+ "\nresponseHeaders: " + res.responseHeaders
+ "\nstatus: " + res.status
+ "\nstatusText: " + res.statusText
+ "\nfinalUrl: " + res.finalUrl;
console.error(msg);
}
});
}
/**
* Checks if the user is currently in Eclipse
*
* @returns {boolean} - User is in Eclipse
*/
function isEclipseEnabled() {
let switchToEclipse = document.getElementById('oh-menu-eclipse-toggle');
return ! switchToEclipse;
}
/**
* Loads a full HTML Page
*
* @param {string} url - URL to load
* @param {function} callback - Callback function with DOM-Object
*/
function load_page(url, callback){
if(debug)
console.log('[' + scriptName + ']: Load url: ' + url + ' ...');
let qr = new XMLHttpRequest();
/**
* Handles Statechange
*/
qr.onreadystatechange = function() {
if(qr.readyState === 4) {
if(qr.status === 200) {
if(debug)
console.log('[' + scriptName + ']: 200 Response from ' + url);
// Parse HTML to DOM-Object
let parser = new DOMParser();
callback(parser.parseFromString(qr.responseText, "text/html"));
} else {
console.warn('[' + scriptName + ']: ' + qr.status + ' Response from ' + url);
callback(null);
}
}
};
/**
* Handles Error
*/
qr.onerror = function() {
console.error('[' + scriptName + '] XHR Error: ' + qr.statusText);
};
qr.open('get', url, true);
qr.send();
}
/**
* Refreshes the Download-Button
*
* @param {boolean} defaultText - Change back to default text
*/
function refreshDlButtonText(defaultText) {
if(defaultText)
dlButton.innerHTML = 'Download Faves (This page)';
else
dlButton.innerHTML = 'Downloading... (' + favesProdcessed + '/' + favesFound + ')';
}
/**
* Increases the counter of the DL-Button
*/
function imageCounterUp() {
favesProdcessed++;
if(favesProdcessed === favesFound) {
// Reset button after downloading
refreshDlButtonText(true);
dlButton.disabled = false;
let audio = new Audio('http://localhost/AreYouReadyByTechnoBase.mp3');
audio.play();
} else
refreshDlButtonText(false);
}
/**
* Extracts the URL from the Fave-Node
*
* @param {Element} faveListElement - Fave-Node
* @param {int} index - Index of the Fave-Node (Mostly used for debug)
* @returns {null|string} - URL or null if none found
*/
function extractUrlFromFaveNode(faveListElement, index) {
let link = faveListElement.getElementsByClassName('torpedo-thumb-link');
if(! link || link.length < 1) {
if(debug)
console.warn('[' + scriptName + ']: Was unable to find link of node... (Index: ' + index + ')');
return null;
}
if(debug)
console.log('[' + scriptName + ']: Found link of node: ' + link[0].href + '! (Index: ' + index + ')');
return link[0].href;
}
/**
* Extracts the Artist-Name from Fave-Node
*
* @param {Element} faveListElement - Fave-Node
* @param {int} index - Index of the Fave-Node (Mostly used for debug)
* @returns {string|null} - Artist-Name or null if none found
*/
function extractArtistFromFaveNode(faveListElement, index) {
let artistInfo = faveListElement.getElementsByClassName('artist');
if(! artistInfo || artistInfo.length < 1) {
if(debug)
console.warn('[' + scriptName + ']: Was unable to find Artistname of node... (Index: ' + index + ')');
return null;
}
artistInfo = artistInfo[0].getElementsByClassName('username');
if(! artistInfo || artistInfo.length < 1) {
if(debug)
console.warn('[' + scriptName + ']: Was unable to find Artistname of node... (Index: ' + index + ')');
return null;
}
if(debug)
console.log('[' + scriptName + ']: Found Artistname of node: ' + artistInfo[0].innerHTML + '! (Index: ' + index + ')');
return artistInfo[0].innerHTML;
}
/**
* Extract the best image Download-URL from the given page
*
* @param {Element} html - HTML-Site
* @param {int} index - Index of the image (Mostly used for debug)
* @return {null|string} - Download URL or null if none found
*/
function extractDownloadUrl(html, index) {
// Try if download button exists
/*let buttonArea = html.getElementsByClassName('dev-meta-actions');
if(! buttonArea || buttonArea.length < 1) {
// todo try to get dl btn to work
}
buttonArea = null;*/
// Else get url from image itself
let bigImageEl = html.getElementsByClassName('dev-content-full');
if(! bigImageEl || bigImageEl.length < 1) {
// Try get small img if that big doesnt exists
let smallImageEl = html.getElementsByClassName('dev-content-normal');
if(! smallImageEl ||smallImageEl.length < 1) {
if(debug)
console.warn('[' + scriptName + ']: Could not find the Download-URL of the image... (Index: ' + index + ')');
return null;
}
return smallImageEl[0].src;
}
return bigImageEl[0].src;
}
/**
* Extracts the title from the given page
*
* @param {Element} html - HTML-Site
* @param {int} index - Index of the image (Mostly used for debug)
* @returns {string|null} - Title of the Image or null if not found
*/
function extractTitle(html, index) {
let title = html.getElementsByClassName('title');
if(! title || title.length < 1) {
if(debug)
console.warn('[' + scriptName + ']: Could not find the Title of the image... (Index: ' + index + ')');
return null;
}
return title[0].innerHTML;
}
/**
* Downloads the Image
*
* @param {string} url - URL of the Image
* @param {string} artist - Artist of the image
* @param {int} index - Index of the image (Mostly used for debug)
*/
function downloadImage(url, artist, index) {
load_page(url, function(html) {
// Handle empty response
if(html === null) {
imageCounterUp();
if(debug)
console.warn('[' + scriptName + ']: Got an empty response on image... (Index: ' + index + ')');
return;
}
let downloadUrl = extractDownloadUrl(html, index);
let imgTitle = extractTitle(html, index);
if(downloadUrl == null) {
imageCounterUp();
console.warn('[' + scriptName + ']: Got an empty Download-URL on image... (Index: ' + index + ')');
return;
}
let data = 'url=' + downloadUrl + '&artist=' + artist + '&imageTitle=' + imgTitle + '&directoryTreeByArtists=' + createDirsForArtists;
postAjax(data, function(callback) {
if(callback === '1') {
console.log('[' + scriptName + ']: Successfully downloaded & saved image "' + imgTitle + '" by ' + artist);
} else {
console.error('[' + scriptName + ']: Error downloading and save image "' + imgTitle + '" by ' + artist + ' | Reason:\n' + callback);
}
imageCounterUp();
});
});
}
/**
* Starts the download process on the page
*
* @param {int} timePerImgMin - Max-Pause between images
* @param {int} timePerImgMax - Max-Pause between images
*/
function downloadPage(timePerImgMin, timePerImgMax) {
let faveList = document.getElementsByClassName('faved');
let currentTimeout = 0;
favesFound = faveList.length;
favesProdcessed = 0;
refreshDlButtonText(false);
for(let i = 0; i < faveList.length; i++) {
let url = extractUrlFromFaveNode(faveList[i], i);
let artist = extractArtistFromFaveNode(faveList[i], i);
let timeout = Math.floor((Math.random() * (timePerImgMax - timePerImgMin)) + timePerImgMin);
if(url && artist) {
setTimeout((function(url, artist, index) {
return function() {
console.log('[' + scriptName + ']: Processing -> ' + url + ' by ' + artist + ' - Node i: ' + index);
downloadImage(url, artist, index);
};
})(url, artist, i), timeout + currentTimeout);
// Increase the current timeout for the next one
currentTimeout += timeout;
} else {
imageCounterUp();
if(debug)
console.warn('[' + scriptName + ']: Artist-Name and/or URL was not found in node... (Index: ' + i + ')');
}
}
}
/**
* Adds the Download-Button to the Document
*/
function addDlBtn() {
dlButton = document.createElement('button');
refreshDlButtonText(true);
dlButton.type = 'button';
dlButton.addEventListener('click', function(event) {
dlButton.disabled = true;
downloadPage(2500, 5000);
});
// Some simple styles~
dlButton.style.margin = '20px 10px';
dlButton.style.padding = '6px';
let addTo = document.getElementById('gmi-GPage');
if(! addTo)
return;
addTo.insertBefore(dlButton, addTo.childNodes[0]);
}
/**
* Runs start tasks on Page load
*/
function init() {
// Exit if the user is on Eclipse
//if(isEclipseEnabled())
// return;
// Fix the poor links from the old version...
if(new RegExp(/favourites/).test(window.location.href))
addDlBtn();
}
// Adds start EventListener
window[window.addEventListener ? 'addEventListener' : 'attachEvent'](
window.addEventListener ? 'load' : 'onload', init, false);
})();
@McBobb
Copy link

McBobb commented May 25, 2020

This is showing errors in lines 7, 8 and 22, saying it expected one of a list of words, but finding (what is on that line) instead

Also, assuming it's working even with those errors, I don't understand how it's supposed to work/how to download folders.

@Petschko
Copy link
Author

Petschko commented May 26, 2020

@McBobb

Hehe I didn't knew people would find that, was just for me personally~

It DID worked, but it required a PHP-File and a LOCAL Webserver as well, which isnt posted here (See line 30)
The PHP-File was basically for ordering the image into a directory/download it

But it also was for the old Version of DA, so it dont work anymore, it was just for me to save all my favorite art, before the eclipse happend

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment