// ==UserScript==
// @name (dm) Deviant Art Gallery Ripper (old slowmode)
// @namespace DeviantRipperSlow
// @description Click button and generate a list of direct image link urls for all images for a users gallery.
// @version 1.1.14
// @lastupdated 2018-08-12
// @match *://*.deviantart.com/*
// @exclude *://*.deviantart.com/journal/*
// @exclude *://*.deviantart.com/prints/*
// @exclude *://justsitback.deviantart.com/*
// @exclude *://groups.deviantart.com/*
// @exclude *://shop.deviantart.com/*
// @exclude *://chat.deviantart.com/*
// @exclude *://today.deviantart.com/*
// @exclude *://forum.deviantart.com/*
// @exclude *://portfolio.deviantart.com/*
// @exclude *://www.deviantart.com/account/*
// @exclude *://www.deviantart.com/journals/*
// @exclude *://www.deviantart.com/correspondence/*
// @exclude *://www.deviantart.com/critiques/*
// @exclude *://www.deviantart.com/messages/*
// @grant GM_log
// @grant GM_xmlhttpRequest
// @downloadURL https://update.greasyfork.icu/scripts/5615/%28dm%29%20Deviant%20Art%20Gallery%20Ripper%20%28old%20slowmode%29.user.js
// @updateURL https://update.greasyfork.icu/scripts/5615/%28dm%29%20Deviant%20Art%20Gallery%20Ripper%20%28old%20slowmode%29.meta.js
// ==/UserScript==
/*
* Known issue: Scanning browse pages with google chrome fails
* because chrome does not allow cross site xmlHttp requests.
* This is by design and I don't know of any workaround.
* Button displays appropriate message and is disabled.
* see http://www.chromium.org/developers/design-documents/user-scripts
* this might have been worked around by DA changing their server
* settings for origins
*
* When browsing a search/gallery and clicking a thumbnail to load full image
* on HTML5 browsers the GET button disappears. This is because DA hides
* the panel holding the button. Refreshing the page while viewing the single
* image will redraw the GET button allowing you to grab all images in the
* single image artist's gallery.
*/
/*
Clicking the button will make script locate first page of user gallery
(if viewing image page) then parse out each image in gallery and then
get direct image links for the gallery. If looking at index page of
thumbnails it will start at current page and work deeper.
*/
/*
* special key notes:
*
* pressing control+alt and clicking on the button will
* toggle the debug log to make easy copy/paste for dev support.
*
* pressing shift and clicking the button will reset the script to start
* over again.
*/
//*************************
// Global Var's
var ScriptDebug = false;
var SuperVerbose = false;
var GM_log; if (typeof GM_log === 'undefined') { GM_log = function (str) { console.log(str); }; }
function DebugLog (str) {
pages.debugLogText = pages.debugLogText + str + "\r\n";
if (ScriptDebug === true) {
if (arguments.length === 1) { GM_log(str); }
else { GM_log(arguments); }
}
}
//recurse var used for thumbnail pages mainly. if set to non true and button
//clicked on single page it doesn't really do anything useful.
var pages = {
nodownloads: false, // set to true if you don't want actual download links
// but want the largest possible from page. good to
// use when not using firefox
debugLog: document.createElement('textarea'),
debugLogText: '',
tableFailed: document.createElement('table'),
isChrome: /Chrome/i.test(navigator.userAgent),
isFireFox: /Firefox/i.test(navigator.userAgent),
loggedIn: false,
recurse: true, // recurse into lower gallery pages
ignorePages: {
/*
used in button generation function. If any return true button is not generated.
place items here that are not easily matched to @exclude meta header
make expression trigger to true for it to ignore page, false to act normal.
*/
ProfileTab: (document.querySelector('li.active > a[gpage_name="userpage"]')) ? true : false,
SingleArt: (document.URL.indexOf('/art/') > -1) ? true : false
},
abort_links: false, // flag to abort link grabbing
Current: 0, // current counter reused for image and gallery parsing
TotalImages: 0, // total counter used for image parsing
URLs: [], // holder for url html list
URLbox: document.createElement('textarea'),
GalleryData: {},// holds thumbnail image target info
GalleryTemplate: function () {
//this.isFetched = false; // if page was fetched set during ScanImage
//this.id = null; // id of image set during ScanImage
//this.ddl = null; // ddl link to image file set by ScanImage
//this.xhttp = null; // xHttpRequest result holder
this.Failed = null; // flag if page failed to find a image link
this.error = ''; // error value to show on list
this.url = null; // url of image page set during ScanGallery
this.title = null; // title of the image for failure display
},
GalleryPages: [], // list of urls to thumbnail pages needed to fetch
ToParse: [], // list of urls of single image pages that need to be parsed for DDL
fetchStatus: 0, // status id for script checking status:
// 0 = not started, 1 = getting indexes
// 2 = getting image DDL, 3 = finished everything
// 4 = displayed urls (finished or aborted)
xmlHttp_Counter: {
MAXREQ: 4,
runCon: 0,
interval: null
}, // end xmlHttp_Counter
btn: { // button holder
btnID : null,
debugToggleCheck : function (eventID) {
DebugLog('debugToggleCheck()');
// if shift cause script reset
if ((eventID.shiftKey === true) && (eventID.altKey === false) && (eventID.ctrlKey === false)) {
DebugLog('*** Resetting script status. ***');
pages.ToParse = [];
pages.fetchStatus = 0;
pages.GalleryPages = [];
pages.xmlHttp_Counter.runCon = 0;
pages.URLs = [];
pages.Current = 0;
pages.TotalImages = 0;
pages.GalleryData = {};
pages.abort_links = false;
clearInterval(pages.xmlHttp_Counter.interval);
pages.xmlHttp_Counter.interval = null;
pages.debugLogText = '';
pages.debugLog.style['display'] = 'none';
pages.debugLog.value = '';
pages.URLbox.style['display'] = 'none';
pages.URLbox.innerHTML = '';
pages.tableFailed.style['display'] = 'none';
pages.tableFailed.innerHTML = '';
pages.btn.btnID.value = 'Get URLs for Gallery';
pages.btn.btnID.removeEventListener('click', pages.btn.AbortLinkChecking, false);
pages.btn.btnID.addEventListener('click', pages.btn.GetLinks, false);
}
// if control+alt toggle debug display
if ((eventID.altKey === true) && (eventID.ctrlKey === true)) {
DebugLog('debug log toggle current: ' + pages.debugLog.style['display']);
switch (pages.debugLog.style['display'])
{
case '':
pages.debugLog.style['display'] = 'none';
//pages.debugLog.value = '';
break;
case 'none':
pages.debugLog.value = pages.debugLogText;
pages.debugLog.style['display'] = '';
break;
}
}
},
// button click abort function
AbortLinkChecking : function (eventID) {
// ignore click if holding control+alt in order to trigger debug log view
if ((eventID.altKey === true) || (eventID.ctrlKey === true) || (eventID.shiftKey === true)) { return; }
pages.abort_links = true;
DebugLog('AbortLinkChecking()');
pages.btn.btnID.removeEventListener('click', pages.btn.AbortLinkChecking, false);
pages.btn.btnID.value = 'Aborted: ' + pages.btn.btnID.value;
DebugLog('FetchStatus: ' + pages.fetchStatus);
if (pages.fetchStatus > 1) pages.DoneDisplayUrlList();
pages.GalleryPages = [];
pages.ToParse = [];
}, // end AbortLinkChecking
// creates the click button for our page
GenerateButton : function () {
DebugLog('Call: GenerateButton()');
var new_button;
var btnLoc;
var ignores;
// Check if page is in the ignore list
for (ignores in pages.ignorePages) {
if (!pages.ignorePages.hasOwnProperty(ignores)) continue;
DebugLog('Checking ignore flag of ' + ignores + ' : ' + pages.ignorePages[ignores]);
if (pages.ignorePages[ignores] === true) {
DebugLog('We should ignore this page. No button creation.');
// get out if we hit a page match
return;
}
}
pages.loggedIn = (document.querySelector('td#oh-menu-deviant > a.oh-l > span > span.username')) ? true : false;
DebugLog('User is logged in: ' + pages.loggedIn);
pages.URLbox.style['display'] = 'none';
document.body.insertBefore(pages.URLbox, document.body.firstChild);
pages.tableFailed.style['display'] = 'none';
document.body.insertBefore(pages.tableFailed, document.body.firstChild);
pages.debugLog.style['display'] = 'none';
pages.debugLog.rows = 15;
pages.debugLog.style['width'] = '100%';
document.body.insertBefore(pages.debugLog, document.body.firstChild);
new_button = document.createElement('input');
new_button.type = 'button';
new_button.value = 'Get URLs for Gallery';
new_button.setAttribute('onsubmit', 'return false;');
new_button.addEventListener('click', pages.btn.debugToggleCheck, false);
/*
* use individual selector OR's to get target by preference instead of first
* dom object availability
*
* alternate: btnLoc = document.querySelector('#gmi-ResViewContainer, #gmi-GalleryEditor, #output');
*/
btnLoc =
document.querySelector('#gmi-ResViewContainer') ||
document.querySelector('#gmi-GalleryEditor') ||
document.querySelector('#output');
if (btnLoc) {
btnLoc.insertBefore(new_button, btnLoc.firstChild);
new_button.addEventListener('click', pages.btn.GetLinks, false);
}
else {
new_button.value = 'Root Thumbnail Page?';
document.body.insertBefore(new_button, document.body.firstChild);
}
// Disable button on base domain if using chrome due to same origin complications
/* DA seems to have changed server settings to fix same origin
* issues from www.deviantart.com and other subdomains commented
* for testing. will re-enable if problems show up.
* actually seems to have been Chrome whose behavior changed. If script is loaded
* as an extension it is allowed to violate same origin rules. If pasted into the
* developer console it errors out same origin failures.
if (document.location.hostname === 'www.deviantart.com' && pages.isChrome === true) {
new_button.value = 'Script will fail on root www.deviantart.com';
new_button.disabled = true;
}
*/
DebugLog('Created Button:');
DebugLog(new_button);
return new_button;
}, // end GenerateButton
// called when button is clicked to start grabbing thumnail pages
GetLinks : function (eventID) {
// ignore click if holding control+alt in order to trigger debug log view
if ((eventID.altKey === true) || (eventID.ctrlKey === true) || (eventID.shiftKey === true)) { return; }
DebugLog('Call: GetLinks()');
var galleryLink = document;
var a_gallery;
pages.btn.btnID.removeEventListener('click', pages.btn.GetLinks, false);
pages.btn.btnID.addEventListener('click', pages.btn.AbortLinkChecking, false);
if (!checkers.isThumbnailGallery(galleryLink)) {
DebugLog('Current page is not a gallery trying to find it.');
// find the gallery thumbnail link
a_gallery = document.querySelector('a[onclick*="gallery"]');
if (a_gallery) { galleryLink = a_gallery.href; }
if (!galleryLink) { throw 'Error finding upper gallery'; }
GM_log(galleryLink);
}
else {
galleryLink = document.location.href;
}
pages.GalleryPages.push(galleryLink);
DebugLog(pages.GalleryPages);
pages.fetchStatus = 1;
pages.xmlHttp_Counter.interval = setInterval(heartBeats.LoadGalleries, 50);
} // end GetLinks
}, // end btn
DoneDisplayUrlList : function () {
DebugLog('Call: DoneDisplayUrlList()');
if (pages.fetchStatus > 3) return;
var urlList;
urlList = pages.URLbox;
urlList.rows = 15;
//urlList.cols = 140;
urlList.style['width'] = '100%';
urlList.innerHTML = pages.URLs.join('\r\n');
urlList.style['display'] = '';
urlList.select();
pages.fetchStatus = 4;
if (pages.TotalImages !== pages.URLs.length) {
pages.btn.btnID.value = 'Some pages failed to find links (found ' + pages.URLs.length + ' of ' + pages.TotalImages + ')';
pages.ShowFailedUrlList();
}
else {
pages.btn.btnID.value = 'Displaying (found ' + pages.URLs.length + ' of ' + pages.TotalImages + ')';
}
}, // end DoneDisplayUrlList
ShowFailedUrlList : function () {
DebugLog('Call: ShowFailedUrlList()');
var counter;
var tableFailed, tableInner;
tableFailed = pages.tableFailed;
tableFailed.width = '100%';
tableFailed.border = '2 px';
tableInner = '';
for (counter in pages.GalleryData) {
if (!pages.GalleryData.hasOwnProperty(counter)) continue;
if (pages.GalleryData[counter].Failed === true) {
tableInner = tableInner +
'' +
pages.GalleryData[counter].title + ' ' +
pages.GalleryData[counter].error +
'
';
DebugLog(pages.GalleryData[counter]);
}
}
tableInner = '