Created
December 18, 2019 00:19
-
-
Save Petschko/bf6804804562622deefaf55c0f672aaf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@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