Advertisement
Guest User

FA Conglomerator Alt.

a guest
Mar 2nd, 2019
216
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name           FA Conglomerator (same page alternate version)
  3. // @namespace      http://lulz.net/
  4. // @version        3.2.1b
  5. // @description    Asynchronously extracts all submissions from a FurAffinity gallery or scraps collection and displays them as a list of links replacing the gallery content. A button "Conglomerate" is inserted into the gallery/scraps collection page which activates the script. Additional input boxes allow the specification of the maximum number of concurrent requests used, a request delay time and the number of retries. Install guide, updates, changelog and other scripts @ http://pastebin.com/u/lulzScript
  6. // @include        http*://*.furaffinity.net/gallery/*
  7. // @include        http*://*.furaffinity.net/scraps/*
  8. //
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. //
  12. // ==/UserScript==
  13.  
  14.  
  15. function _GM_getValue(key) {
  16.   return (typeof(GM_getValue) == 'function') ? GM_getValue(key) : undefined;
  17. }
  18.  
  19. //Some constants
  20. //Default is 48 submissions per page. Valid values can be taken from the drop-down box on the gallery page.
  21. var congPerPage = 48;
  22. //Default is up to 10 retries for a page/submission to load.
  23. var congRetries = 10;
  24.  
  25. //Maximum number of concurrent requests. Default is 4.
  26. //Note: Your browser may still generally limit the maximum concurrent requests per server according to its system settings.
  27. var maxConcReq = 4;
  28.  
  29. //The polling times in miliseconds for submission requests and submission list requests.
  30. //Each request will check for an available request slot using this interval.
  31. //This is needed to limit concurrent request to maxConcReq.
  32. //The value of submListPollTime should be lower than submPollTime, to give page loading priority over submission loading.
  33. var submPollTime = 40;
  34. var submListPollTime = 20;
  35.  
  36. //Nummber of miliseconds to wait before a request slot is freed for further request
  37. //Can be used to reduce the stress on the server but links will take longer to conglomerate
  38. var congFreeSlotDelay = 500;
  39.  
  40.  
  41. // Strip 'href' attribute from all links on page, so will not be picked up by batch downloader;
  42. // replicates link functionality via inserting 'onclick' event.
  43. function disableLinks() {
  44.  
  45.   function clickLink(e) {
  46.     var elt = this;
  47.     e.preventDefault();
  48.     elt.href = elt.getAttribute('data-href');
  49.     elt.removeEventListener('click', clickLink, true);
  50.     elt.click();
  51.     setTimeout(function() {
  52.       elt.href = '#'
  53.       elt.addEventListener('click', clickLink, true);
  54.     }, 0);
  55.   }
  56.  
  57.   var links = document.body.getElementsByTagName('a');
  58.   for (var i = 0, j = links.length; i < j; i++) {
  59.     var elt = links[i];
  60.     if (elt.getAttribute('data-href') === null) {
  61.       elt.setAttribute('data-href', elt.href);
  62.       elt.href = '#'
  63.       elt.addEventListener('click', clickLink, true);
  64.     }
  65.   }
  66. }
  67.  
  68.  
  69. //When each request either loaded or gave an error, the rCount should be back at 0.
  70. //Pending downlaods lead to rCount > 0
  71. function checkIfDone()
  72. {
  73.   if (rCount == 0)
  74.     lStatus.innerHTML = '<b> Done!</b>';
  75. }
  76.  
  77. //Frees a used request slot
  78. function freeRequestSlot()
  79. {
  80.   concReq--;
  81. }
  82.  
  83. //Function called when a submission page was successfully loaded.
  84. //req = XMLHttpRequest object of the loaded page
  85. //linkObj = link object within the new link window
  86. function onSubm (req, linkObj)
  87. {
  88.   //Extract download link from submission page
  89.   var submFile_ = req.responseText.match(/< *a *href=\"([^>\"]*)\" *> *Download *< *\/a *>/i);
  90.   if ( submFile_ )
  91.   {
  92.     var wProto = submFile_[1].replace(/^\/\//, faProto[0]);
  93.     //Insert extracted download link into link list
  94.     //Link displayed as text
  95.     linkObj.innerHTML = wProto;
  96.     //Also set the href attribut, to make the link clickable
  97.     linkObj.setAttribute('href', wProto);
  98.   }
  99.  
  100.   //Request finished successfully. Decrease request counter and check if rCount = 0.
  101.   rCount--;
  102.   checkIfDone();
  103.  
  104. }
  105.  
  106. //Function called when a submission page failed to load.
  107. //req = XMLHttpRequest object of the failed page
  108. //linkObj = link object within the new link window
  109. //retry = counter that indicates the current number of retries.
  110. function onSubmErr (req, linkObj, retry)
  111. {
  112.   //Display "Error" in the linklist if the link couldn't be extracted.
  113.   linkObj.innerHTML = '<b>Error (retry = ' + retry + '/' + congRetries + ')</b>';
  114.  
  115.   //Request finished with error. Decrease request counter and check if rCount = 0.
  116.   rCount--;
  117.   checkIfDone();
  118. }
  119.  
  120. //Loads a submission page asynchronously.
  121. //submURL = Full URL of submission.
  122. //linkObj = link object within the new link window
  123. //retry = counter that indicates the current number of retries.
  124. //After [congRetries] failed retries, the function fails.
  125. function getSubm (submURL, linkObj, retry)
  126. {
  127.   //Check for free request slot
  128.   if (concReq < maxConcReq)
  129.   {
  130.     //Reserve request slot
  131.     concReq++;
  132.  
  133.     //Display loading message and retries needed if any.
  134.     if (retry == 0)
  135.       linkObj.innerHTML = '<b>loading...</b>';
  136.     else
  137.       linkObj.innerHTML = '<b>loading...(retry = ' + retry + '/' + congRetries + ')</b>';
  138.  
  139.     var req = new XMLHttpRequest();
  140.     //Prepare request with full URL, containing the desired page number
  141.     req.open('GET', submURL, true);
  142.     //Function to call when loading state of page changes
  143.     req.onreadystatechange = function (evt)
  144.     {
  145.  
  146.       //Check if page was loaded (or not)
  147.       if (req.readyState == 4)
  148.       {
  149.         //Free reserved request slot
  150.         if (congFreeSlotDelay > 0)
  151.           setTimeout (freeRequestSlot, congFreeSlotDelay);
  152.         else
  153.           freeRequestSlot();
  154.  
  155.         if (req.status == 200)
  156.         {
  157.           //Page was sucessfully loaded
  158.           //Call handler for successful requests.
  159.           onSubm (req, linkObj);
  160.         }
  161.         else
  162.         {
  163.           //If page failed to load, retry up to [congRetries] times.
  164.           if ( retry < congRetries )
  165.           {
  166.             linkObj.innerHTML = 'waiting...[queued for retry ' + (retry+1) + '/' + congRetries + ']'
  167.             //The retry is done with setTimeout, instead of calling getSubm directly.
  168.             //This will prevent multiple recursive calls for each retry
  169.             setTimeout (getSubm, 0, submURL, linkObj, retry+1);
  170.           }
  171.           else
  172.           {
  173.             //Page could not be loaded even after retrying
  174.             //Call error handler.
  175.             onSubmErr (req, linkObj, retry);
  176.           }
  177.         }
  178.       }
  179.     };
  180.  
  181.     //Sends request and starts loading of the page.
  182.     req.send(null);
  183.   }
  184.   else
  185.     //If no request slot is available, poll for free request slot by calling this request again after a delay of [submPollTime] miliseconds.
  186.     setTimeout (getSubm, submPollTime, submURL, linkObj, retry)
  187. }
  188.  
  189. //Function called when a submission list page was successfully loaded.
  190. //req = XMLHttpRequest object of the loaded page
  191. //no = number of loaded page
  192. //retry = counter that indicates the current number of retries.
  193. function onSubmList (req, no, retry)
  194. {
  195.   //Extract all submission links
  196.   var rawSubmLinks = req.responseText.match(/<a href=\"\/view\/\d+\/\">/gi);
  197.  
  198.   //Continue as long as there are submission links found
  199.   //An empty page indicates the end of a gallery/scraps collection
  200.   if ( rawSubmLinks )
  201.   {
  202.     //Display page number
  203.     var div = document.createElement('div');
  204.  
  205.     //Display page number an retries needed if any.
  206.     if (retry == 0)
  207.       div.innerHTML = '</br></br><b>Page ' + no + '</b></br></br>';
  208.     else
  209.       div.innerHTML = '</br></br><b>Page ' + no + ' (retry = ' + retry + '/' + congRetries + ')</b></br></br>';
  210.  
  211.     congLinkDiv.insertBefore(div, null);
  212.  
  213.     //Iterate through all found links.
  214.     for (var i=0; i < rawSubmLinks.length; i++)
  215.     {
  216.       //Extract actual link from list of matched links
  217.       var submLink = rawSubmLinks[i].match(/<a href=\"\/view\/(\d+)\/\">/);
  218.       if ( submLink )
  219.       {
  220.         //Make full URL of submission page from link.
  221.         var submURL = faBaseURL + '/view/' + submLink[1] + '/';
  222.  
  223.         //Add new line for submission in link window and set status to "loading..."
  224.         var div = document.createElement('div');
  225.         div.innerHTML = '<b>' + sCount + ': </b>';
  226.         var linkObj = document.createElement('a');
  227.         linkObj.innerHTML = 'waiting...';
  228.         linkObj.setAttribute('target', '_blank');
  229.         div.insertBefore(linkObj, null);
  230.         congLinkDiv.insertBefore(div, null);
  231.  
  232.         //New request. Increase request counter.
  233.         rCount++;
  234.         getSubm (submURL, linkObj, 0);
  235.  
  236.         //Increase submission counter
  237.         sCount++;
  238.  
  239.       }
  240.     }
  241.  
  242.     //New request. Increase request counter.
  243.     rCount++;
  244.     //Load next submission list page
  245.     getSubmList (submBaseURL, no+1, congPerPage, 0);
  246.   }
  247.   else
  248.   {
  249.     //There are no more pages. Display message.
  250.     var div = document.createElement('div');
  251.     div.innerHTML = '</br></br><b>No more pages!</b>';
  252.     congLinkDiv.insertBefore(div, null);
  253.   }
  254.  
  255.   //Request finished successfully. Decrease request counter and check if rCount = 0.
  256.   rCount--;
  257.   checkIfDone();
  258.  
  259. }
  260.  
  261. //Function called when a submission list page failed to load.
  262. //req = XMLHttpRequest object of the failed page
  263. //no = number of loaded page
  264. //retry = counter that indicates the current number of retries.
  265. function onSubmListErr (req, no, retry)
  266. {
  267.   //Page could not be loaded. Display Error message.
  268.   var div = document.createElement('div');
  269.   div.innerHTML = '</br></br><b>Error - cannot load page ' + no + ' (retry = ' + retry + '/' + congRetries + ')!<br/>Further conglomeration stoppped.</b><br/>';
  270.   congLinkDiv.insertBefore(div, null);
  271.  
  272.   //Request finished with error. Decrease request counter and check if rCount = 0.
  273.   rCount--;
  274.   checkIfDone();
  275. }
  276.  
  277. //Loads a submission list page from the gallery/scraps collection asynchronously.
  278. //baseURL = Base URL of gallery/scraps collection page.
  279. //no = Nummer of list page to load
  280. //perpage = submissions per page {24, 36, 48, 60}
  281. //retry = counter that indicates the current number of retries.
  282. //After [congRetries] failed retries, the function fails.
  283. function getSubmList (baseURL, no, perpage, retry)
  284. {
  285.   //Check for free request slot
  286.   if (concReq < maxConcReq)
  287.   {
  288.     //Reserve request slot
  289.     concReq++;
  290.  
  291.     var req = new XMLHttpRequest();
  292.     //Prepare request with full URL, containing the desired page number
  293.     req.open('POST', baseURL + no, true);
  294.     //Set additional request header
  295.     req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  296.  
  297.     //Function to call when loading state of page changes
  298.     req.onreadystatechange = function (evt)
  299.     {
  300.       //Check if page was loaded (or not)
  301.       if (req.readyState == 4)
  302.       {
  303.         //Free reserved request slot
  304.         if (congFreeSlotDelay > 0)
  305.           setTimeout (freeRequestSlot, congFreeSlotDelay);
  306.         else
  307.           freeRequestSlot();
  308.  
  309.         if (req.status == 200)
  310.         {
  311.           //Page was sucessfully loaded
  312.           //Call handler for successful requests.
  313.           onSubmList (req, no, retry);
  314.         }
  315.         else
  316.         {
  317.           //If page failed to load, retry up to [congRetries] times.
  318.           if ( retry < congRetries )
  319.             //The retry is done with setTimeout, instead of calling getSubmList directly.
  320.             //This will prevent multiple recursive calls for each retry and therefore save stack resources.
  321.             setTimeout (getSubmList, 0, baseURL, no, perpage, retry+1);
  322.           else
  323.           {
  324.             //Page could not be loaded even after retrying
  325.             //Call error handler.
  326.             onSubmListErr (req, no, retry);
  327.           }
  328.         }
  329.       }
  330.     };
  331.  
  332.     //Sends request and starts loading of the page.
  333.     req.send('perpage=' + perpage);
  334.   }
  335.   else
  336.     //If no request slot is available, poll for free request slot by calling this request again after a delay of [submListPollTime] miliseconds.
  337.     setTimeout(getSubmList, submListPollTime, baseURL, no, perpage, retry);
  338. }
  339.  
  340. //Global variables:
  341.  
  342. //Holds the object of the drop-down box containing the selected number of submissions per page
  343. var submPerPage;
  344. //Base URL of FA, including protocol and subdomain
  345. var faBaseURL;
  346. //Base URL of gallery/scraps
  347. var submBaseURL;
  348. //Reference to new link window
  349. var congLinkDiv;
  350. //Counter for submission index
  351. var sCount;
  352. //Name of FA user whose galery/scraps are being conglomerated
  353. var faUsername;
  354. //Source of submission ("gallery" or "scraps")
  355. var submSrc;
  356. //Reference to status message in link window
  357. var lStatus;
  358. //Request counter. Used to determine when all submissions are loaded.
  359. var rCount;
  360. //Protocol http or https
  361. var faProto;
  362. //Counter for concurrent connections
  363. var concReq;
  364.  
  365. //Main script function. Called when conglomerator button is clicked
  366. function congStart(e)
  367. {
  368.   //Prevents the form's default action for a button
  369.   e.preventDefault();
  370.  
  371.   //Get FA base URL (including protocol and subdomain); gallery/scraps base URL; submission source and username
  372.   var baseURLs = document.location.href.match(/((.*furaffinity\.net)\/(gallery|scraps)\/([^\/]*))(?:\/|)/);
  373.   if ( baseURLs )
  374.   {
  375.     submBaseURL = baseURLs[1] + '/'; //Submission base URL
  376.     faBaseURL = baseURLs[2];         //FA base URL
  377.     submSrc = baseURLs[3];           //submission source
  378.     faUsername = baseURLs[4];        //FA username
  379.  
  380.     faProto = document.location.href.match(/(.*):\/\//);
  381.  
  382.     disableLinks();
  383.    
  384.     // Clear submission space to replace with conglomerated links:
  385.     var subList = document.body.getElementsByClassName('submission-list');
  386.     subList = subList.length && subList[0];
  387.    
  388.     if ( subList )
  389.     {
  390.       var subGallery = subList.getElementsByTagName('section');
  391.       subGallery = subGallery.length && subGallery[0];
  392.      
  393.       if (subGallery) {
  394.         subGallery.style.display = 'none';
  395.        
  396.         congLinkDiv = document.createElement('div');
  397.         subList.insertBefore(congLinkDiv, subGallery);
  398.  
  399.         //Set title of new window
  400.         var span = document.createElement('span');
  401.         span.innerHTML = '<b>List of ' + faUsername + '\'s ' + submSrc + ':</b>';
  402.         congLinkDiv.insertBefore(span, null);
  403.  
  404.         //Add status message to link Window
  405.         lStatus = document.createElement('span');
  406.         lStatus.innerHTML = '<b> Loading...</b>';
  407.         congLinkDiv.insertBefore(lStatus, null);
  408.  
  409.         //Reset submission-, request and connection counter
  410.         sCount = 1;
  411.         rCount = 1;
  412.         concReq = 0;
  413.  
  414.         //Set maximum concurrent requests according to the input field
  415.         maxConcReq = parseInt(congMCRInput.value, 10);
  416.  
  417.         //Set free slot delay according to the input field
  418.         congFreeSlotDelay = parseInt(congFSDInput.value, 10);
  419.  
  420.         //Set retries according to the input field
  421.         congRetries = parseInt(congRetInput.value, 10);
  422.  
  423.         //Set number sumbissions per page using the selected value of the drop-down box if it is valid, otherwise uses default value
  424.         if (submPerPage)
  425.         {
  426.           var congPerPage_ = parseInt(submPerPage.value, 10);
  427.           if (!isNaN(congPerPage))
  428.             congPerPage = congPerPage_;
  429.         }
  430.  
  431.         //At this point faBaseURL and submBaseURL both matched correctly.
  432.         //Load first submission list page
  433.         getSubmList (submBaseURL, 1, congPerPage, 0, 0);
  434.       }
  435.     }
  436.   }
  437. }
  438.  
  439. function insertButton()
  440. {  
  441.   //Find location of gallery control buttons
  442.   var browsebar = document.getElementsByClassName('page-options');
  443.  
  444.   //If found, insert conglomerator button
  445.   if ( browsebar.length >= 1 )
  446.   {
  447.     var bParent = browsebar[0];
  448.     bParent.insertBefore(congButton, bParent.firstChild);
  449.     bParent.insertBefore(congInputSpan, congButton.nextSibling);
  450.     bParent.insertBefore(document.createElement('br'), congInputSpan.nextSibling);
  451.     bParent.insertBefore(document.createElement('br'), congInputSpan.nextSibling);
  452.  
  453.     //Find drop-down box containing the valid values for the number of submissions per page
  454.     var submPerPage_ = document.getElementsByName('perpage');
  455.     if (submPerPage_.length > 0)
  456.     {
  457.       submPerPage = submPerPage_[0];
  458.       var submPerPageTitle = 'NOTE: This input box is part of the original webpage and lets you set the number of submissions you can view per page.\nThe conglomerator will also use this value for the number of submissions per page it will request from the server.';
  459.  
  460.       //Insert oder add tooltip text to original input field
  461.       if (!submPerPage.title)
  462.         submPerPage.title = submPerPageTitle;
  463.       else
  464.         submPerPage.title = submPerPage.title + '\n\n' + submPerPageTitle;
  465.     }
  466.  
  467.     buttonInserted = true;
  468.   }
  469. }
  470.  
  471. //Flag that is later set to true if button was inserted.
  472. var buttonInserted = false;
  473.  
  474. function onCongMCRInputChange(e)
  475. {
  476.   GM_setValue ('maxConcReq', congMCRInput.value);
  477. }
  478.  
  479. //Create button for conglomerator script
  480. var congButton = document.createElement('button');
  481. //Set button styles/attributes
  482. congButton.setAttribute('class', 'button');
  483. congButton.innerHTML = 'Conglomerate';
  484. congButton.style.marginRight = '10px';
  485. congButton.title = 'Start conglomeration';
  486.  
  487. //Set function to call on a click
  488. congButton.addEventListener ('click', congStart, false);
  489.  
  490. //Create span to group all conglomerator input fields
  491. var congInputSpan = document.createElement('span');
  492.  
  493. //Create and insert text label
  494. var congInputLabel = document.createElement('span');
  495. congInputLabel.innerHTML = 'Concurrent requests:';
  496. congInputSpan.insertBefore (congInputLabel, null);
  497.  
  498. //Create input field for maximum number of concurrent requests
  499. var congMCRInput = document.createElement('input');
  500. congInputSpan.insertBefore (congMCRInput, null);
  501.  
  502. //Create and insert text label
  503. congInputLabel = document.createElement('span');
  504. congInputLabel.innerHTML = 'Request delay [ms]:';
  505. congInputSpan.insertBefore (congInputLabel, null);
  506.  
  507. //Create input field for request delay
  508. var congFSDInput = document.createElement('input');
  509. congInputSpan.insertBefore (congFSDInput, null);
  510.  
  511. congInputLabel = document.createElement('span');
  512. congInputLabel.innerHTML = 'Retries:';
  513. congInputSpan.insertBefore (congInputLabel, null);
  514.  
  515. //Create input field for number of retries
  516. var congRetInput = document.createElement('input');
  517. congInputSpan.insertBefore (congRetInput, null);
  518.  
  519.  
  520.  
  521. //Set input styles/attributes
  522. congMCRInput.setAttribute('class', 'button');
  523. congMCRInput.setAttribute ('type', 'number');
  524. congMCRInput.setAttribute ('size', '3');
  525. congMCRInput.setAttribute ('min', '1');
  526. congMCRInput.style.width = '30px';
  527. congMCRInput.style.marginLeft = '5px';
  528. congMCRInput.style.marginRight = '10px';
  529. congMCRInput.title = 'Maximum number of concurrent requests made to the server.\nIf this number is too high it might trigger request restrictions and timeouts on the server side, leading to high numbers of retries and possibly incomplete conglomeration.';
  530.  
  531. //Load last used value from storage
  532. var maxConcReq_ = _GM_getValue('maxConcReq');
  533. //If value is valid, set as initial value
  534. if (maxConcReq_)
  535.   congMCRInput.value = maxConcReq_;
  536. else
  537.   congMCRInput.value = maxConcReq;
  538.  
  539. congMCRInput.addEventListener ('change', onCongMCRInputChange, false);
  540.  
  541.  
  542. function onCongFSDInputChange(e)
  543. {
  544.   GM_setValue ('congFreeSlotDelay', congFSDInput.value);
  545. }
  546.  
  547. //Set input styles/attributes
  548. congFSDInput.setAttribute('class', 'button');
  549. congFSDInput.setAttribute ('type', 'number');
  550. congFSDInput.setAttribute ('size', '3');
  551. congFSDInput.setAttribute ('min', '0');
  552. congFSDInput.style.width = '50px';
  553. congFSDInput.style.marginLeft = '5px';
  554. congFSDInput.style.marginRight = '10px';
  555. congFSDInput.title = 'Number of milliseconds [ms] to wait before a request slot is freed for further requests.\nIncreasing this number can reduce the stress on the server but conglomeration will take longer';
  556.  
  557.  
  558. //Load last used value from storage
  559. var congFreeSlotDelay_ = _GM_getValue ('congFreeSlotDelay');
  560. //If value is valid, set as initial value
  561. if (congFreeSlotDelay_)
  562.   congFSDInput.value = congFreeSlotDelay_;
  563. else
  564.   congFSDInput.value = congFreeSlotDelay;
  565.  
  566. congFSDInput.addEventListener ('change', onCongFSDInputChange, false);
  567.  
  568.  
  569. function onCongRetInputChange(e)
  570. {
  571.   GM_setValue ('congRetries', congRetInput.value);
  572. }
  573.  
  574. //Set input styles/attributes
  575. congRetInput.setAttribute('class', 'button');
  576. congRetInput.setAttribute ('type', 'number');
  577. congRetInput.setAttribute ('size', '3');
  578. congRetInput.setAttribute ('min', '0');
  579. congRetInput.style.width = '30px';
  580. congRetInput.style.marginLeft = '5px';
  581. congRetInput.title = 'The number of retries for a single request if an error occurs.\nIf there are many requests with a high number of retries in the conglomerator window, try to decreas the number of concurrent requests and/or increase the request delay.';
  582.  
  583. //Load last used value from storage
  584. var congRetries_ = _GM_getValue ('congRetries');
  585. //If value is valid, set as initial value
  586. if (congRetries_)
  587.   congRetInput.value = congRetries_;
  588. else
  589.   congRetInput.value = congRetries;
  590.  
  591. congRetInput.addEventListener ('change', onCongRetInputChange, false);
  592.  
  593.  
  594. //Try to insert button into page if page is already available at this time
  595. insertButton();
  596.  
  597. //Function onDCL is called when DOM tree has completely been loaded.
  598. //This is the safest way to start a script.
  599. document.addEventListener ('DOMContentLoaded',
  600.   function onDCL(e)
  601.   {
  602.     //If the button wasn't inserted before because the page wasn't
  603.     //available at that time, it will be inserted now.
  604.     if ( !buttonInserted )
  605.       insertButton();
  606.  
  607.   }, false
  608. );
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement