--- /dev/null
+// ==UserScript==
+// @name (dm) Deviant Art Gallery Ripper
+// @namespace DeviantRipper
+// @description Click button and generate a list of direct image link urls for all images for a users gallery.
+// @version 1.0.15
+// @lastupdated 2013-08-29
+// @include http://*.deviantart.com/favourites/*
+// @match http://*.deviantart.com/favourites/*
+// @include http://*.deviantart.com/gallery/*
+// @match http://*.deviantart.com/gallery/*
+// @!nclude http://*.deviantart.com/art/*
+// @!atch http://*.deviantart.com/art/*
+// @include http://browse.deviantart.com/*
+// @match http://browse.deviantart.com/*
+// @include http://backend.deviantart.com/rss.xml*
+// @match http://backend.deviantart.com/rss.xml*
+// ==/UserScript==
+
+/*
+ Known issue: ?
+
+ Documentation needed.
+
+ */
+
+var GM_log;
+if (typeof GM_log === 'undefined') {
+ GM_log = function (str) { console.log(str); };
+}
+
+var debug = false;
+//var debug = true;
+
+/*
+ * xHttp object
+ * variables:
+ * maxreq - maximum number of async requests to do
+ * runcon - number of running connections
+ * interval - interval holder
+ * links - array() object used to help with triggering
+ * new connections for asyncLoad
+ * functions:
+ *
+ * startInterval - starts the interval loop
+ * accepts args: (heartbeat_function, interval)
+ * heartbeat_function = function called on each trigger pulse
+ * interval = integer of ms between interval triggers
+ *
+ * stopInterval() - stops the interval loop
+ * no args
+ *
+ * load - synchronous xmlHttpRequest
+ * only handles simple "GET"
+ * accepts args: (string url)
+ *
+ * asyncLoad - asynchronous xmlHttpRequest
+ * accepts args: (string url or associative array of options,
+ * function callback, optional args)
+ * url = url string to load
+ * if object can have params like GM_xmlHttpRequest ex:
+ * {url:String, method:String, headers:{associative array},
+ * data:String, onload:Function, onerror:Function,
+ * onreadystatechange:Function}
+ * onload, onerror, onreadystatechange are called with
+ * function(xmlHttpRequest event object, objparams,
+ * callback, extra_args);
+ *
+ * See below in asyncLoad definition comment for more
+ * information.
+ *
+ * function = callback function called as
+ * callback(xhtr, string url, optional args)
+ * do not specify a callback if using an onload above
+ *
+ * optional args = single variable passed verbatim to
+ * the callback function
+ *
+ * default_heartbeat
+ * default heartbeat function
+ *
+ * default_next_url
+ * default routine to handle next url fetch
+ *
+ * default_callback_xhtr
+ * a default callback to use when retrieving a request
+ */
+function XHttp() {
+ var xHttpInstance = this;
+ this.maxreq = 4;
+ this.runcon = 0;
+ this.interval = null;
+ this.links = [];
+
+ this.default_heartbeat = function () {
+ if ((xHttpInstance.runcon < xHttpInstance.maxreq) &&
+ (xHttpInstance.links.length > 0)) {
+ // do something here when you have an opening to get more stuff
+ xHttpInstance.default_next_url();
+ }
+ if ((xHttpInstance.links.length === 0) &&
+ (xHttpInstance.runcon === 0)) {
+ // do something here when out of things to
+ xHttpInstance.stopInterval();
+ }
+ };
+ this.default_next_url = function () {
+ if (xHttpInstance.links.length > 0) {
+ var link_data = xHttpInstance.links.shift().toString();
+ xHttpInstance.asyncLoad(link_data,
+ xHttpInstance.default_callback_xhtr);
+ }
+ };
+ this.default_callback_xhtr = function (xhtr, strURL, args) {
+ // do something with the result
+ // xhtr is the xmlHttpRequest object
+ // common values are:
+ // xhtr.responseText
+ // xhtr.responseXml
+ // xhtr.readyState
+ // xhtr.status
+
+ if (xhtr.status === 404) {
+ // do something when 404 not found
+ // like:
+ alert("Page wasn't found at: " + strURL + "\n" +
+ xhtr.status + " " + xhtr.statusText);
+ } else if (xhtr.status === 200) {
+ // do something when 200 ok
+ // like:
+ alert(xhtr.responseText);
+ } else {
+ // do other stuff with other codes
+ alert("Site returned: " + xhtr.status + " '" +
+ xhtr.statusText + "' for: \n" + strURL);
+ }
+ };
+ /*
+ * startInterval (heartbeat_function, heartbeat_pulse)
+ * accepts:
+ * heartbeat_function: function reference to call on each heartbeat
+ * pulse
+ * heartbeat_pulse: integer
+ *
+ */
+ this.startInterval = function (heartbeat_function, heartbeat_pulse) {
+ var pulse_rate;
+ var heartbeat_func;
+ // check for and stop existing interval
+ if (xHttpInstance.interval !== null) { xHttpInstance.stopInterval(); }
+
+ if (typeof heartbeat_pulse === 'undefined') {
+ pulse_rate = 100;
+ } else {
+ pulse_rate = heartbeat_pulse;
+ if (isNaN(pulse_rate)) { // validate its an actual number
+ throw "startInterval given invalid pulse rate :" +
+ heartbeat_pulse;
+ }
+ }
+ if (typeof heartbeat_function === 'undefined') {
+ heartbeat_func = xHttpInstance.default_heartbeat;
+ } else {
+ heartbeat_func = heartbeat_function;
+ }
+
+ if (!heartbeat_func instanceof Function) {
+ throw "startInterval given incorrect heartbeat function argument.";
+ }
+ /* end error checking */
+ xHttpInstance.interval = setInterval(heartbeat_func, pulse_rate);
+ };
+
+ /*
+ * stopInterval ()
+ *
+ * stops the xHttp interval loop.
+ */
+ this.stopInterval = function () {
+ clearInterval(xHttpInstance.interval);
+ xHttpInstance.interval = null;
+ };
+
+ /*
+ * load (strURL)
+ *
+ * synchronus XMLHttpRequest load with simple parameter request.
+ * Returns text value of get request or false.
+ */
+ this.load = function (strURL) {
+ //if (debug) { GM_log("Getting url: " + strURL); }
+ var xhtr = new XMLHttpRequest();
+ xhtr.open("GET", strURL, false);
+ xhtr.send();
+ if (xhtr.readyState === 4 && xhtr.status === 200) {
+ return xhtr.responseText;
+ } else {
+ return false;
+ }
+ };
+ /*
+ * asyncLoad(objparams, callback, extra_args)
+ *
+ * multithreaded url fetching routine
+ * gets url contents and sends to callback function
+ *
+ * if objparams is passed as a string function assumes
+ * simple get request with objparams being a url string.
+ *
+ * otherwise:
+ * objparams object properties imitates grease monkey
+ * GM_xmlHttpRequest function.
+ *
+ * method - a string, the HTTP method to use on this request.
+ * Generally GET, but can be any HTTP verb, including POST,
+ * PUT, and DELETE.
+ *
+ * url - a string, the URL to use on this request. Required.
+ *
+ * headers - an associative array of HTTP headers to include on
+ * this request. Optional, defaults to an empty array. Example:
+ * headers: {'User-Agent': 'Mozilla/4.0 (compatible) Greasemonkey',
+ * 'Accept': 'application/atom+xml,application/xml,text/xml'}
+ *
+ * data - a string, the body of the HTTP request. Optional, defaults
+ * to an empty string. If you are simulating posting a form
+ * (method == 'POST'), you must include a Content-type of
+ * 'application/x-www-form-urlencoded' in the headers field,
+ * and include the URL-encoded form data in the data field.
+ *
+ * onreadystatechange - a function object, the callback function to be
+ * called repeatedly while the request is in progress.
+ *
+ * onerror - a function object, the callback function to be called
+ * if an error occurs while processing the request.
+ *
+ * onload - a function object, the callback function to be called when
+ * the request has finished successfully.
+ * ** DO NOT ** specify a callback function if using onload.
+ * onload will take precedence and fire instead of callback.
+ * onload will pass the callback value to its called function
+ * if you want to use the values in some way. See definition
+ * below for the default_onload.
+ */
+
+ this.asyncLoad = function (objparams, callback, extra_args) {
+ //if (debug) GM_log("Async Getting url : " + url);
+
+ // local function Variables
+ var url = "";
+ var method;
+ var default_method = "GET";
+ var send_data = "";
+ var http_req = new XMLHttpRequest();
+ var xHttpPtr = xHttpInstance;
+ var headkey;
+ var useGMxml = false;
+
+ var onerror_wrapper = null;
+ var onload_wrapper = null;
+ var onreadystatechange_wrapper = null;
+ // end local function variables
+
+ var default_onerror = function (args) {
+ /*
+ * do something here when there's errors.
+ */
+ var target;
+ if (args.target) {
+ target = args.target;
+ } else {
+ target = args;
+ }
+ xHttpInstance.runcon -= 1;
+ if (onerror_wrapper !== null) {
+ onerror_wrapper(target, objparams, callback, extra_args);
+ }
+ };
+
+ var default_onreadystatechange = function (args) {
+ var target;
+ if (args.target) {
+ target = args.target;
+ } else {
+ target = args;
+ }
+ if (onreadystatechange_wrapper !== null) {
+ onreadystatechange_wrapper(target, objparams,
+ callback, extra_args);
+ }
+ };
+
+ var default_onload = function (args) {
+// if (debug) {
+// GM_log("xmlHttpRequest response: " +
+// args.readyState + " " + args.status + " " +
+// url);
+// }
+ var target;
+ if (args.target) {
+ target = args.target;
+ } else {
+ target = args;
+ }
+ xHttpPtr.runcon -= 1;
+ if (onload_wrapper !== null) {
+ onload_wrapper(target, objparams, callback, extra_args);
+ } else {
+ callback(target, url, extra_args);
+ }
+ };
+
+ if (typeof objparams !== 'object') {
+ if (typeof objparams === 'string') {
+ url = objparams;
+ method = default_method;
+ http_req.open(method, url, true);
+ } else {
+ throw "asyncLoad error: parameters not object or string";
+ }
+ } else {
+
+ // check url parameter value
+ if (typeof objparams['url'] !== 'string') {
+ throw "asyncLoad error: missing url parameter.";
+ } else {
+ // make sure its not blank
+ url = objparams['url'];
+ if (url === '') {
+ throw "asyncLoad error: url parameter is empty string.";
+ }
+ }
+
+ // check if we specified method
+ if (typeof objparams['method'] === 'string') {
+ method = objparams['method'];
+ } else {
+ method = default_method;
+ }
+
+ // open xmlHttpRequest so we can properly set headers
+ http_req.open(method, url, true);
+
+ // check if we specified any custom headers and have some sort
+ // of validation of the data. Just ignores non strings.
+ if (typeof objparams['headers'] === 'object') {
+ for (headkey in objparams['headers']) {
+ if (objparams['headers'].hasOwnProperty(headkey)) {
+ if (typeof headkey === 'string') {
+ if (typeof objparams['headers'][headkey]
+ === 'string') {
+ http_req.setRequestHeader(headkey,
+ objparams['headers'][headkey]);
+ }
+ }
+ }
+ }
+ }
+
+ if (typeof objparams['data'] === 'string') {
+ send_data = objparams['data'];
+ }
+
+ if (typeof objparams['onreadystatechange'] === 'function') {
+ onreadystatechange_wrapper = objparams['onreadystatechange'];
+ }
+
+ if (typeof objparams['onerror'] === 'function') {
+ onerror_wrapper = objparams['onerror'];
+ }
+
+ if (typeof objparams['onload'] === 'function') {
+ onload_wrapper = objparams['onload'];
+ }
+
+ if (objparams['useGMxml']) {
+ useGMxml = true;
+ }
+
+ }
+
+ if (typeof callback !== 'function' &&
+ typeof onload_wrapper !== 'function') {
+ throw "asyncLoad error: no callback or onload function passed.";
+ }
+
+ xHttpPtr.runcon += 1;
+
+ if (useGMxml) {
+ GM_xmlhttpRequest({
+ method: method,
+ url: url,
+ headers: objparams['headers'],
+ onload: default_onload,
+ onerror: default_onerror,
+ onreadystatechange: default_onreadystatechange
+ });
+
+ } else {
+ http_req.onerror = default_onerror;
+ http_req.onreadystatechange = default_onreadystatechange;
+ http_req.onload = default_onload;
+
+ http_req.send(send_data);
+ }
+ };
+}
+
+var deviantRipper = {
+ isChrome : /chrome/i.test(navigator.userAgent),
+ isFireFox : /firefox/i.test(navigator.userAgent),
+ abort_links : false,
+ useGMxml : false, // flag to use GM_xmlHttpRequest instead of XMLHttpRequst
+ xml_link_data : [], // array holder for xlm page links
+ pages : {
+ //recurse var used for thumbnail pages mainly. if set to 0 and button
+ //clicked on single page it doesn't really do anything useful.
+ recurse: true, // recuse into lower gallery pages
+ current: 0, // current counter reused for image and gallery parsing
+ total: 0, // total counter used for image parsing
+ urls: [], // holder for url html list
+ toparse: [], // list of urls of single image pages that need to be parsed for DDL
+ textbox: null, // textbox holder
+ 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)
+ },
+
+ /*
+ * display_url_list()
+ *
+ * function called when we're all done and we want to
+ * display the list of url's we got.
+ */
+ display_url_list : function () {
+ var docNamespace = 'http://www.w3.org/1999/xhtml';
+ var counter;
+ var tmpStr;
+ if (debug) { GM_log("Call: display_url_list()"); }
+ if (debug) { GM_log(deviantRipper); }
+ if (deviantRipper.pages.fetchStatus > 3) { return; }
+ deviantRipper.pages.textbox =
+ document.createElementNS(docNamespace, "textarea");
+ deviantRipper.pages.textbox.style.width = '100%';
+ for (counter = 0; counter < deviantRipper.pages.urls.length; counter += 1) {
+ if (debug) { GM_log("Fixing " + deviantRipper.pages.urls[counter]); }
+ if (deviantRipper.pages.urls[counter].indexOf('http://th') > -1) {
+ tmpStr = deviantRipper.pages.urls[counter].replace('http://th', 'http://fc').replace('/PRE/', '/');
+ deviantRipper.pages.urls[counter] = tmpStr;
+ }
+ }
+ deviantRipper.pages.textbox.innerHTML =
+ deviantRipper.pages.urls.join('\r\n');
+ document.body.insertBefore(deviantRipper.pages.textbox,
+ document.body.firstChild);
+ deviantRipper.pages.fetchStatus = 4;
+ },
+
+ /*
+ * init()
+ *
+ * Called as first function execution upon script load.
+ * Sets up the xmlHttpRequest helpers and generates click button.
+ */
+ init : function () {
+ // Check whether we're on backend
+ deviantRipper.xml_xHttp = new XHttp();
+
+ if (debug) { GM_log("init() isChrome: " + deviantRipper.isChrome + " isFireFox: " + deviantRipper.isFireFox); }
+ if (deviantRipper.isFireFox === true) {
+ deviantRipper.useGMxml = true;
+ }
+
+ if (/backend/i.test(location.hostname) === true) {
+ if (/rss\.xml/i.test(location.href) === true) {
+ // test if we're in iframe if not then get out
+ if (window === parent) { return; }
+
+ deviantRipper.pages.btnID = deviantRipper.btn.generateXMLButton();
+ deviantRipper.btn.startXML(document.location.href);
+ }
+ } else {
+ deviantRipper.pages.btnID = deviantRipper.btn.generateButton();
+ }
+ },
+
+ checker: {
+ /*
+ * isThumbnailGallery (doc)
+ *
+ * return true if page seems to be a gallery index
+ * or false if it looks like its a single image page
+ * detection is looking for the comments by the artist
+ * usually found on the single image page
+ */
+ isThumbnailGallery : function (doc) {
+ if (debug) { GM_log("Call: isThumbnailGallery()"); }
+ return (doc.getElementById("artist-comments")) ? false : true;
+ },
+
+ /*
+ * isAborted ()
+ *
+ * check if we clicked the button to abort script
+ * if we did it requires a page reload to start again
+ *
+ */
+ isAborted : function () {
+ if (debug) { GM_log("isAborted(): " + deviantRipper.abort_links); }
+ if (deviantRipper.abort_links === true) {
+ deviantRipper.pages.btnID.value = 'Aborted: ' + deviantRipper.pages.btnID.value;
+ if (debug) { GM_log("FetchStatus: " + deviantRipper.pages.fetchStatus); }
+ if (deviantRipper.pages.fetchStatus > 1) { deviantRipper.display_url_list(); }
+ deviantRipper.xml_link_data = [];
+ deviantRipper.pages.toparse = [];
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /*
+ * next_xml ()
+ *
+ * get our next gallery page from our stack,
+ * increment our fetching counter, and fetch page
+ */
+ next_xml : function () {
+ var link_uri;
+
+ if (debug) { GM_log("Call: next_xml()"); }
+ if (deviantRipper.checker.isAborted()) {
+ return false;
+ }
+ if (deviantRipper.xml_link_data.length > 0) {
+ link_uri = deviantRipper.xml_link_data.shift().toString();
+ if (debug) { GM_log("Shifted: " + link_uri + "\ntypeof: " + typeof link_uri); }
+
+ if (deviantRipper.useGMxml) {
+ if (debug) {
+ GM_log("Using GreaseMonkey GM_xmlHttpRequest.");
+ }
+ deviantRipper.xml_xHttp.asyncLoad({
+ url: link_uri,
+ useGMxml: true,
+ onload: deviantRipper.callback.scan_xml_dom
+ });
+ } else {
+ deviantRipper.xml_xHttp.asyncLoad(link_uri, deviantRipper.callback.scan_xml_dom);
+ }
+
+ }
+ }
+ }, // end checker
+
+ parser: {
+ /*
+ * image_links_xml (docbase)
+ *
+ * function called after we load a gallery index page,
+ * "docbase" references the document of the index page
+ * so we can start looking for thumbnails in order to
+ * get the single image page links.
+ */
+ image_links_xml : function (docbase) {
+ if (debug) { GM_log("Call: image_links_xml()"); }
+ var items;
+ var hifi = null;
+ var lofi = null;
+ var thumbnail;
+ var thumbnails;
+ var content;
+ var counter;
+ var locounter;
+
+ items = docbase.getElementsByTagNameNS('*', 'item');
+ if (items.length < 1) {
+ deviantRipper.pages.recurse = false;
+ return;
+ }
+
+ for (counter = 0; counter < items.length; counter += 1) {
+ content = items[counter].getElementsByTagNameNS('*', 'content');
+ thumbnails = items[counter].getElementsByTagNameNS('*', 'thumbnail');
+ thumbnail = null;
+
+ if (thumbnails.length > 0) {
+ // grab last thumbnail item and use it incase we don't find any content lines
+ thumbnail = thumbnails[thumbnails.length - 1].getAttribute('url');
+ }
+
+ for (locounter = 0; locounter < content.length; locounter += 1) {
+ if (content[locounter].getAttribute('medium') === 'image') { lofi = content[locounter].getAttribute('url'); }
+ if (content[locounter].getAttribute('medium') === 'document') { hifi = content[locounter].getAttribute('url'); }
+ }
+ if (hifi !== null) {
+ if (debug) { GM_log("Hifi: " + hifi); }
+ deviantRipper.pages.urls.push(hifi);
+ } else if (lofi !== null) {
+ if (debug) { GM_log("Lofi: " + lofi); }
+ deviantRipper.pages.urls.push(lofi);
+ } else {
+ if (debug) { GM_log("thumbnail: " + thumbnail); }
+ deviantRipper.pages.urls.push(thumbnail);
+ }
+ }
+ if (debug) { GM_log([counter, length, deviantRipper.pages.urls.length]); }
+ },
+
+
+ /*
+ * next_xml_page_link (docbase)
+ *
+ * Function called after loading xml page looking for next
+ */
+ next_xml_page_link : function (docbase) {
+ if (debug) { GM_log("Call: next_xml_page_link()"); }
+ if (debug) { GM_log(docbase); }
+ var rtn_val;
+// var links;
+// var counter, length;
+// links = docbase.getElementsByTagNameNS('http://www.w3.org/2005/Atom', 'link');
+// for (counter = 0, length = links.length;
+// counter < length;
+// counter += 1) {
+// if (links[counter].getAttribute('rel').toString() === "next") {
+// rtn_val = links[counter];
+// break;
+// }
+// }
+ rtn_val = docbase.querySelector('link[rel="next"]');
+ if (rtn_val) {
+ rtn_val = rtn_val.getAttribute('href');
+ if (debug) { GM_log("NextXML page: " + rtn_val); }
+ return rtn_val;
+ } else {
+ return false;
+ }
+ }
+
+
+ }, // end parser
+
+ callback: {
+
+ /*
+ * scan_xml_dom (HTML_Data, url, args)
+ *
+ * called when gallery page html is loaded
+ * so we can parse images out and set next page
+ */
+ scan_xml_dom : function (HTML_Data, url, args) {
+ if (debug) { GM_log("Call: scan_xml_dom()"); }
+ var html_dom;
+ var nextPage;
+ var parser;
+
+ html_dom = HTML_Data.responseXML;
+ if (!html_dom) {
+ if (HTML_Data.responseText !== "") {
+ parser = new DOMParser();
+ html_dom = parser.parseFromString(HTML_Data.responseText, "text/xml");
+ } else {
+ throw "There was an error parsing XML from: " + url;
+ }
+ }
+
+ // parse and add images on page to fetch stack
+ deviantRipper.parser.image_links_xml(html_dom);
+
+ deviantRipper.pages.current += 1;
+ deviantRipper.pages.btnID.value = "Loading xml page " +
+ deviantRipper.pages.current +
+ "(" + deviantRipper.pages.urls.length + ")";
+
+ if (deviantRipper.pages.recurse) {
+ nextPage = deviantRipper.parser.next_xml_page_link(html_dom);
+ if (nextPage) { deviantRipper.xml_link_data.push(nextPage.toString()); }
+ }
+
+ }
+
+ }, // end callback
+
+ btn: {
+ /*
+ * getLinks ()
+ *
+ * onclick function triggered when the
+ * button we injected is clicked to get
+ * our direct links.
+ */
+ getLinks : function () {
+ if (debug) { GM_log("Call: getLinks()"); }
+ var iframeLoader;
+ var feedbutton;
+ var docNamespace = 'http://www.w3.org/1999/xhtml';
+
+ deviantRipper.pages.btnID.removeEventListener("click", deviantRipper.btn.getLinks, false);
+ feedbutton = document.querySelector('link[type="application/rss+xml"]');
+ if (!feedbutton) {
+ throw "No feed button on this page.";
+ }
+
+ if (deviantRipper.isChrome === true) {
+ deviantRipper.pages.btnID.parentNode.removeChild(
+ deviantRipper.pages.btnID
+ );
+
+ iframeLoader = document.createElementNS(
+ docNamespace,
+ 'iframe'
+ );
+ iframeLoader.src = feedbutton.href;
+ iframeLoader.style.width = '100%';
+ iframeLoader.style.height = '100px';
+ document.body.insertBefore(
+ iframeLoader,
+ document.body.firstChild
+ );
+ } else {
+ deviantRipper.btn.startXML(feedbutton.href);
+ }
+
+ },
+
+ /*
+ * startXML ()
+ *
+ * started from init() to start grabbing XML pages
+ * starting with current loaded one. Script assumes
+ * we loaded from an iframe.
+ */
+ startXML : function (galleryLink) {
+ if (debug) { GM_log("Call: startXML(" + arguments[0] + ")"); }
+ deviantRipper.pages.btnID.addEventListener('click', deviantRipper.btn.abortLinkChecking, false);
+ deviantRipper.xml_link_data.push(galleryLink.toString());
+ deviantRipper.pages.fetchStatus = 1;
+ deviantRipper.xml_xHttp.startInterval(deviantRipper.heartbeat.load_xml, 50);
+ },
+
+ /*
+ * abortLinkChecking ()
+ *
+ * onclick triggered when button is clicked
+ * while we're getting links.
+ */
+ abortLinkChecking : function () {
+ deviantRipper.abort_links = true;
+ GM_log("abortLinkChecking()");
+ deviantRipper.pages.btnID.removeEventListener('click', deviantRipper.abortLinkChecking, false);
+ },
+
+ /*
+ * generateButton()
+ *
+ * creates the click button for our page
+ */
+ generateButton : function () {
+ if (debug) { GM_log("Call: generateButton()"); }
+ var new_button;
+ var btnLoc;
+
+ new_button = document.createElement("input");
+ new_button.type = "button";
+ new_button.value = "Get URLs for Gallery";
+ new_button.setAttribute("onsubmit", "return false;");
+
+ // var btnLoc = document.getElementById("gmi-GalleryEditor");
+ btnLoc = document.getElementById("output");
+ if (btnLoc) {
+ btnLoc.insertBefore(new_button, btnLoc.firstChild);
+ new_button.addEventListener("click", deviantRipper.btn.getLinks, false);
+ } else {
+ new_button.value = "Root Thumbnail Page?";
+ document.body.insertBefore(new_button, document.body.firstChild);
+ }
+ return new_button;
+ },
+
+ /*
+ * generateXMLButton()
+ *
+ * creates the click button for our page
+ */
+ generateXMLButton : function () {
+ if (debug) { GM_log("Call: generateXMLButton()"); }
+ var new_button;
+ var docNamespace = 'http://www.w3.org/1999/xhtml';
+ var replacedRootNode = document.createElement('clearinghouse');
+
+ // empty out the current document view.
+ if (deviantRipper.isChrome === true) {
+ while (document.documentElement.firstChild) {
+ replacedRootNode.appendChild(
+ document.documentElement.firstChild
+ );
+ }
+ } else if (deviantRipper.isFireFox === true) {
+ while (document.body.firstChild) {
+ replacedRootNode.appendChild(
+ document.body.firstChild
+ );
+ }
+ }
+
+ if (document.body === null) {
+ document.body = document.createElementNS('http://www.w3.org/1999/xhtml', 'body');
+ document.documentElement.appendChild(document.body);
+ }
+
+ new_button = document.createElementNS(docNamespace, 'input');
+ new_button.type = "button";
+ new_button.value = "Loading...";
+ new_button.setAttribute("onsubmit", "return false;");
+ document.body.appendChild(new_button);
+ new_button.addEventListener('click', deviantRipper.btn.abortLinkChecking, false);
+
+ return new_button;
+ }
+ }, // end btn
+
+ heartbeat: {
+
+ /*
+ * load_xml ()
+ *
+ * heartbeat loop while loading gallerie indices
+ */
+ load_xml : function () {
+ var runcon = deviantRipper.xml_xHttp.runcon;
+ var maxreq = deviantRipper.xml_xHttp.maxreq;
+ var length = deviantRipper.xml_link_data.length;
+ if ((runcon < maxreq) && (length > 0)) {
+ if (debug) { GM_log("heartbeat load_xml()\nrunning connections: (" + runcon + ') max running (' + maxreq + ')'); }
+ deviantRipper.checker.next_xml();
+ }
+ if ((length === 0) && (runcon === 0)) {
+ if (debug) { GM_log("Stopping heartbeat out of xml pages to pull."); }
+ deviantRipper.xml_xHttp.stopInterval();
+
+ deviantRipper.pages.total = deviantRipper.pages.toparse.length;
+ deviantRipper.pages.fetchStatus = 3;
+ deviantRipper.xml_xHttp.startInterval(deviantRipper.heartbeat.xml_finisher, 50);
+ }
+ },
+
+ /*
+ * xml_finisher ()
+ *
+ * watches for xml to finish loading then displays the urls.
+ */
+ xml_finisher : function () {
+ var runcon = deviantRipper.xml_xHttp.runcon;
+ var length = deviantRipper.xml_link_data.length;
+ if ((length === 0) && (runcon === 0)) {
+ if (debug) { GM_log("Stopping heartbeat xml_finisher."); }
+ deviantRipper.xml_xHttp.stopInterval();
+
+ deviantRipper.display_url_list();
+ }
+ }
+ } // end heartbeat
+
+ };
+
+
+
+if (debug) { GM_log("Current URL loaded from: " + document.location.href); }
+//start the dirty stuff
+deviantRipper.init();