├── scripts ├── CompleteROMs.com.js ├── NewGrounds.com - My Friend Pedro.js ├── Yuzu Game Compatibility Sorter.js ├── WebHackery - MutationObserver.js ├── RPS Adblock Blocker Blocker.js ├── Reddit NSFW Saved Post Scrubber.js ├── Fast GitHub Repo Delete.js └── Napalm FTP Indexer.js └── README.md /scripts/CompleteROMs.com.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name completeroms.com 3 | // @namespace http://tampermonkey.net/ 4 | // @version 0.1 5 | // @description try to take over the world! 6 | // @author You 7 | // @match *://*.completeroms.com/* 8 | // @run-at document-end 9 | // @grant none 10 | // ==/UserScript== 11 | 12 | var jQuery = window.jQuery 13 | 14 | //-- Always show download button 15 | jQuery('.download-button').removeAttr('style') 16 | 17 | //-- Remove countdown text and bottom alert 18 | jQuery('.margin-bottom-30, .alert-danger').remove() 19 | 20 | //-- Clean top spacing 21 | jQuery('.text-center').toggleClass('margin-top-50 padding-top-30') 22 | 23 | //-- Removes iframes 24 | setInterval(function(){ jQuery('iframe').remove() }, 100); 25 | 26 | //-- Remove pop-up behavior 27 | window.K200 = undefined 28 | -------------------------------------------------------------------------------- /scripts/NewGrounds.com - My Friend Pedro.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name NewGrounds - Pedro 3 | // @namespace http://tampermonkey.net/ 4 | // @version 0.1 5 | // @description Custom "My Friend Pedro" page on NewGrounds. 6 | // @author You 7 | // @match https://www.newgrounds.com/portal/view/641215 8 | // @run-at document-end 9 | // @grant none 10 | // ==/UserScript== 11 | 12 | //-- Get rid of jQuery warnings in Tampermonkey (Thanks for the idea, FreeER!) 13 | var jQuery = window.jQuery 14 | 15 | //-- Modify header of game container: 16 | //-- Change rating to 'M', center it with game title, and remove right-hand div 17 | var podHead = jQuery('.pod-head') 18 | podHead.children('.rated-t').attr('class', 'rated-m').css('text-align','center') 19 | podHead.children('div').remove() 20 | 21 | //-- Grab main container that game is in, set new customized CSS for it, then store in variable myFriendPedro via clone() 22 | var myFriendPedro = jQuery('.maincontent-wide').css({ 23 | 'width': '635px', 24 | 'margin': '0', 25 | 'position': 'absolute', 26 | 'top': '50%', 27 | 'left': '50%', 28 | 'transform': 'translate(-50%, -50%)' 29 | }).clone() 30 | 31 | //-- Apply background image to body, delete all elements within body, then append custom cloned game container 32 | jQuery('body').css({ 33 | 'background-image': 'url("https://cdn3.dualshockers.com/wp-content/uploads/2018/06/My-Friend-Pedro-Key-Art.png")', 34 | 'background-size': 'cover' 35 | }).empty().append(myFriendPedro) 36 | -------------------------------------------------------------------------------- /scripts/Yuzu Game Compatibility Sorter.js: -------------------------------------------------------------------------------- 1 | // Webpage this script is for: https://yuzu-emu.org/game/ 2 | // 3 | // Description: This script allows you to sort games by compatibility type. 4 | // 5 | // Use: Click on the page header ("Game Compatibility List") to reset the games list, 6 | // or click on one of the 7 compatiblity types (Perfect, Great, Okay, etc.) in the list 7 | // up top to show only games of that compatibility type. 8 | // 9 | // Note: Either toss this script in a Tampermonkey script so it'll automatically run every 10 | // time you load the page, or paste it all into your browser's DevTools console and then 11 | // press Enter to execute the code! 12 | 13 | // Get all rows of games from the game table 14 | const allGames = [...document.querySelectorAll('[data-key]')]; 15 | 16 | // Get all rows from the "Game Compatibility List" table 17 | const gameCompatList = [...document.querySelector('table > tbody').children]; 18 | 19 | // Resets games list or sorts games list by chosen compatibility type 20 | function sort(data) { 21 | for (game of allGames) { 22 | const compatibility = parseInt(game.childNodes[3].getAttribute('data-compatibility')); 23 | 24 | if (data === 'reset' || data === compatibility) { 25 | game.style.display = 'table-row'; 26 | } else { 27 | game.style.display = 'none'; 28 | } 29 | } 30 | } 31 | 32 | // IIFE to nix ads 33 | (function noAds() { 34 | // Get rid of ads column 35 | document.querySelector('.columns').lastElementChild.remove(); 36 | 37 | // Correct games column width 38 | document.querySelector('.columns > .column').classList.remove('is-two-thirds'); 39 | })(); 40 | 41 | // When clicking on a compatibility list row, sort games by that compatibility type 42 | gameCompatList.forEach((compat, idx) => { 43 | compat.addEventListener('click', function() { 44 | idx === gameCompatList.length - 1 ? sort(99) : sort(idx); 45 | }); 46 | }); 47 | 48 | // Clicking on page title resets game list to original, unsorted state 49 | document.querySelector('.title').addEventListener('click', () => { 50 | sort('reset'); 51 | }); 52 | -------------------------------------------------------------------------------- /scripts/WebHackery - MutationObserver.js: -------------------------------------------------------------------------------- 1 | // Instagram Signup/Login and Image Re-Linker via the MutationObserver API 2 | // Video Tutorial URL: https://youtu.be/KWWGC2TbT70 3 | 4 | // FUNCTION: Checks for presence of signup footer and modal, then removes them if they're on the page. 5 | function loginCheck() { 6 | const body = document.querySelector('body'); 7 | const isHidden = body.style.overflow === 'hidden'; 8 | const hasModal = body.lastElementChild.getAttribute('role') === 'presentation' ? true : false; 9 | const footer = [...document.querySelectorAll('nav > div')].pop().firstElementChild.firstElementChild.lastElementChild.firstElementChild.firstElementChild.firstElementChild; 10 | 11 | if (footer) { 12 | footer.remove(); 13 | } 14 | 15 | if (isHidden && hasModal) { 16 | body.lastElementChild.remove(); 17 | body.style.overflow = 'visible'; 18 | 19 | loginObserver.disconnect(); 20 | } 21 | } 22 | 23 | // FUNCTION: Adds full-sized image link to each thumbnail's hyperlink. 24 | function reLink() { 25 | const linkList = document.querySelectorAll('a'); 26 | 27 | linkList.forEach((link) => { 28 | const linkAttribute = link.getAttribute('href'); 29 | 30 | if (linkAttribute.startsWith('/p/')) { 31 | const linkFormatted = 'https://instagram.com' + linkAttribute + 'media/?size=l' 32 | 33 | link.setAttribute('href', linkFormatted); 34 | 35 | // Log to the console the thumbnail and respective link for hover/click. 36 | console.log('-=-=-=-=-=-=-'); 37 | console.log('Image: ', link.firstElementChild); 38 | console.log('Direct Link: ', linkFormatted); 39 | }; 40 | }); 41 | } 42 | 43 | // FUNCTION: Callback function for MutationObserver which runs when changes are observed. 44 | // Note: Beware of lexical scope with arrow functions, like in the forEach below. 45 | // For example, if you need 'this' to reference the local scope of the loop, then use 46 | // a traditional 'for', 'for...of', or 'for...in' loop. 47 | function mutationsCallback(mutations) { 48 | mutations.forEach((mutation) => { 49 | if (mutation.type === 'childList') { 50 | reLink(); 51 | } else if (mutation.type === 'attributes') { 52 | loginCheck(); 53 | } 54 | }); 55 | } 56 | 57 | // Node(s)/element(s) to be observed. 58 | const imgNode = [...document.querySelectorAll('main > div > div')].pop().lastElementChild.firstElementChild.firstElementChild; 59 | const body = document.querySelector('body'); 60 | 61 | // Create a new MutationObserver instance and pass the callback function to it. 62 | // Then call the observe() method and pass it the node to be observed, as well as 63 | // the type(s) of mutation(s) to watch for. 64 | const imgObserver = new MutationObserver(mutationsCallback); 65 | imgObserver.observe(imgNode, { childList: true }); 66 | 67 | // Same stuff as above, but for our login observer. 68 | const loginObserver = new MutationObserver(mutationsCallback); 69 | loginObserver.observe(body, { attributes: true }); -------------------------------------------------------------------------------- /scripts/RPS Adblock Blocker Blocker.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name RockPaperShotgun Adblock Blocker Blocker 3 | // @namespace http://rockpapershotgun.com/ 4 | // @version 0.1 5 | // @description Remove ad-block roadblock from rockpapershotgun.com 6 | // @author You 7 | // @match *://*.rockpapershotgun.com/* 8 | // @grant none 9 | // ==/UserScript== 10 | 11 | //----------------------------- 12 | //-- jQuery version of the hack 13 | //----------------------------- 14 | 15 | //-- Remove Tampermonkey warnings about jQuery 16 | /* let $ = window.$; 17 | 18 | //-- Function to run after setTimeout time completes 19 | function doStuffs() { 20 | //-- Remove roadblock divs 21 | $('body > div').eq(-1).remove(); 22 | $('body > div').eq(-1).remove(); 23 | 24 | //-- Remove overflow-y hidden on body and html 25 | $('html, body').css('overflow-y', 'visible'); 26 | 27 | //-- Remove .leaderboards div 28 | $('.leaderboards').remove(); 29 | 30 | //-- Remove padding-right used for sidebar ads 31 | $('.ads-enabled .page>main .above').css('padding-right', '0'); 32 | } 33 | 34 | //-- Run function 1 second after page loads 35 | setTimeout(() => { doStuffs() }, 1000); */ 36 | 37 | //--------------------------------- 38 | //-- JavaScript version of the hack 39 | //--------------------------------- 40 | 41 | //-- Function to run after setTimeout time completes 42 | function doStuffs() { 43 | //-- NICE-TO-KNOW: This will hide the elements vis CSS modifications instead of removing them 44 | //-- NOTE: This doesn't guarantee to always get the last 2 divs in our scenario 45 | // document.querySelectorAll('body > div')[3].style.display = 'none'; 46 | // document.querySelectorAll('body > div')[4].style.display = 'none'; 47 | 48 | //-- NICE-TO-KNOW: Chrome's mnemonic for querySelectorAll() (returns array, not nodelist!) 49 | //-- NOTE: Works within Chrome console only, not from within a script like this 50 | // $$('body > div').slice(-1)[0].remove(); 51 | // $$('home > div').slice(-1)[0].remove(); 52 | 53 | //-- NICE-TO-KNOW: Pre-ES6 way to convert nodelist to array (can then use array methods) 54 | // [].slice.call(document.querySelectorAll('body > div')).slice(-1)[0].remove(); 55 | // [].slice.call(document.querySelectorAll('body > div')).slice(-1)[0].remove(); 56 | 57 | //-- ES6 spread operator to convert nodelist to array 58 | [...document.querySelectorAll('body > div')].slice(-1)[0].remove(); 59 | [...document.querySelectorAll('body > div')].slice(-1)[0].remove(); 60 | 61 | //-- Set overflow-y to visible on html and body 62 | document.querySelector('html').style.overflowY = "visible"; 63 | document.querySelector('body').style.overflowY = "visible"; 64 | 65 | //-- Remove padding-right used for sidebar ads 66 | document.querySelector('.ads-enabled .page > main .above').style.paddingRight = "0"; 67 | 68 | //-- Remove .leaderboards div 69 | document.querySelector('.leaderboards').remove(); 70 | } 71 | 72 | //-- Run function 1 second after page loads 73 | setTimeout(() => { doStuffs() }, 1000); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript, jQuery, and Tampermonkey Scripts 2 | These are various scripts I've written for personal use, demonstration purposes (i.e. via my YouTube channel), friends, etc. Brief descriptions for each can be found below. 3 | 4 | **[RPS Adblock Blocker Blocker.js](https://github.com/dsasmblr/JavaScript-jQuery-and-Tampermonkey/blob/master/scripts/RPS%20Adblock%20Blocker%20Blocker.js)** - ***(JavaScript / jQuery / Tampermonkey)*** | This script is from my video tutorial, "[WebHackery Ep. 1: Let's Defeat an Adblock Blocker!](https://youtu.be/gCV8INwY-sc)", where I demonstrate how to defeat an ad-blocking roadblock from a website! I first prototype the hack in jQuery, then convert it to vanilla JavaScript, and finally set everything up in a Tampermonkey script. 5 | 6 | **[Fast GitHub Repo Delete.js](https://github.com/dsasmblr/JavaScript-jQuery-and-Tampermonkey/blob/master/scripts/Fast%20GitHub%20Repo%20Delete.js)** - ***(JavaScript)*** | A work-in-progress script that currently adds a delete button to a user's GitHub repositories main page, then fast-deletes any given repo after its delete button has been clicked 5 times! Great for use via Tampermonkey, or can be ran from the console while on the repositories page. 7 | 8 | **[Napalm FTP Indexer.js](https://github.com/dsasmblr/JavaScript-jQuery-and-Tampermonkey/blob/master/scripts/Napalm%20FTP%20Indexer.js)** - ***(jQuery / Tampermonkey)*** | This is an extensive script that modifies searchftps.net to do a number of things, including remove ads, make requests to the server with hash information to obtain links with which I repurpose download buttons on search results pages, and more! 9 | 10 | **[CompleteROMs.com.js](https://github.com/dsasmblr/JavaScript-jQuery-and-Tampermonkey/blob/master/scripts/CompleteROMs.com.js)** - ***(jQuery / Tampermonkey)*** | Modifies completeroms.com to remove tricky iframes and ads. Also finds download functionality and overrides time-related enabled/disabled status. This script was used in [a YouTube video of mine](https://www.youtube.com/watch?v=Vxpm_wrCm7M) to teach viewers about fun ways to use Chrome DevTools, jQuery, and Tampermonkey! 11 | 12 | **[NewGrounds.com - My Friend Pedro.js](https://github.com/dsasmblr/JavaScript-jQuery-and-Tampermonkey/blob/master/scripts/NewGrounds.com%20-%20My%20Friend%20Pedro.js)** - ***(jQuery / Tampermonkey)*** | Modifies the "My Friend Pedro" game page on NewGrounds. This script was used in [a YouTube video of mine](https://www.youtube.com/watch?v=S18ciaTi4oA) to teach viewers more about using Chrome DevTools, jQuery, and Tampermonkey to hack around with a site! 13 | 14 | **[Reddit NSFW Saved Post Scrubber](https://github.com/dsasmblr/JavaScript-jQuery-and-Tampermonkey/blob/master/scripts/Reddit%20NSFW%20Saved%20Post%20Scrubber.js)** - ***(jQuery)*** | Gets all saved posts and populates them on one page, then unsaves all saved NSFW posts. One thing to be fixed is that the script currently runs synchronously, so the browser will hang until the script is completed. 15 | 16 | **[WebHackery Ep. 3 - Instagram Hacks via the MutationObserver API](https://github.com/dsasmblr/JavaScript-jQuery-and-Tampermonkey/blob/master/scripts/WebHackery%20-%20MutationObserver.js)** - ***(JavaScript)*** | Gets rid of Instagram's forced login/signup roadblocks, and hyperlinks every thumbnail image to the largest available resolution of the image. Finally, code persistence is achieved via using the MutationObserver API. This script was created/used [in episode 3 of WebHackery](https://youtu.be/KWWGC2TbT70), my web development/hacking series on YouTube! 17 | 18 | 19 | -------------------------------------------------------------------------------- /scripts/Reddit NSFW Saved Post Scrubber.js: -------------------------------------------------------------------------------- 1 | /* Reddit NSFW Saved Post Scrubber - Instructions 2 | 3 | Note: Before running this script, you should know two particular side effects. First, 4 | this script unsaves ALL saves marked with the NSFW stamp. That includes subs like 5 | /r/wtf of which you may not want to unsave posts from. In that regard, this script 6 | is an all-or-none thing. 7 | 8 | Second, there are plenty of risque subreddits that aren't stamped NSFW or 18+. As 9 | such, those saves will remain; however, after a first run of the script, you should 10 | be able to more quickly scan through your remaining saved posts and manually 11 | finish the job. 12 | 13 | Now onward to the instructions! 14 | 15 | 1. While logged into your account, go to the following URL after you first replace 16 | YourUsernameHere with your actual username: 17 | 18 | https://www.reddit.com/user/YourUsernameHere/saved/ 19 | 20 | 2. Click anywhere in this script now and press CTRL + A, which will highlight 21 | everything. Then, press CTRL + C to copy it all. 22 | 23 | 3. Click on the tab you have Reddit opened in from step one. Press F12 and you 24 | will see the DevTools section appear. In its bottom pane, you will see a > 25 | character. Click it and you should now see a cursor slowly flashing beside it. 26 | 27 | 4. Press CTRL + V, which will paste what you copied of this script. Now press Enter. 28 | The script will run and you can watch everything happen in the console as URLs 29 | are logged. Once it says, "That's all, folks!", everything will be completed. 30 | Simply refresh the page and all the NSFW-tagged saved posts will be removed. 31 | 32 | */ 33 | 34 | //-- fullUrl keeps track of the URLs scraped from the "Next" button on the page. 35 | let fullUrl = document.location.href; 36 | 37 | //-- This function scrapes data from AJAX results and places them on the page. 38 | function modifyPage(data, status) { 39 | 40 | //-- Find the main container of saved posts, then strip its id so there 41 | //-- aren't duplicates of it when appending to main #siteTable container 42 | let container = jQuery(data).find('#siteTable').attr('id', ''); 43 | 44 | //-- If true is passed, append container. Else, append final container, clean 45 | //-- up nav buttons, then simulate click on "unsave" of each saved NSFW post. 46 | if (status) { 47 | jQuery("#siteTable").append(container); 48 | } else { 49 | jQuery("#siteTable").append(container); 50 | jQuery('.nav-buttons').hide(); 51 | jQuery('.nsfw-stamp').parent().siblings('.save-button').find('a').click(); 52 | console.log("That's all, folks!"); 53 | } 54 | } 55 | 56 | //-- While fullUrl isn't false, run AJAX calls synchronously. 57 | while (fullUrl) { 58 | jQuery.ajax(fullUrl, { 59 | type: 'GET', 60 | dataType: 'html', 61 | async: false, 62 | success: function(data) { 63 | try { 64 | //-- Set fullUrl to the href value of the .next-button element 65 | fullUrl = jQuery(data).find('.next-button')[0].childNodes[0].href; 66 | } 67 | catch { 68 | //-- Set fullUrl to false; finalizes AJAX calls and ends while-loop 69 | fullUrl = false; 70 | } 71 | 72 | //-- If fullUrl is set to false, wrap things up with a final call to modifyPage(). 73 | //-- Else, keep adding AJAX data containers to main container. 74 | //-- The 'count=25' check keeps the first page from being duplicated to itself. 75 | if (!fullUrl) { 76 | modifyPage(data, false); 77 | } else { 78 | if (!fullUrl.includes('count=25')) { 79 | modifyPage(data, true); 80 | } 81 | console.log(fullUrl); 82 | } 83 | } 84 | }); 85 | } -------------------------------------------------------------------------------- /scripts/Fast GitHub Repo Delete.js: -------------------------------------------------------------------------------- 1 | /*-- 2 | Set up global variables ------ 3 | --*/ 4 | 5 | //-- Used for button creation 6 | let repoNames = document.querySelectorAll("[itemprop='name codeRepository']"); 7 | let starBtns = document.querySelectorAll("[aria-label='Star this repository']"); 8 | 9 | //-- Used for multiple user/repo references throughout 10 | let userName = document.querySelector(".user-profile-link strong").innerText; 11 | 12 | //-- Used to keep track of loop iterations, and for index referencing 13 | let arrayCount = 0; 14 | 15 | //-- Keeps track of total consecutive mouse clicks needed on repo delete buttons 16 | let clickObj = { 17 | "repoName": "", 18 | "clicks": 0 19 | }; 20 | 21 | /*-- 22 | Set up functions ------ 23 | --*/ 24 | 25 | //-- FUNC: Deletes the repo 26 | function deleteRepo(text) { 27 | //-- Build form from settings page 28 | let formStart = text.indexOf(`
`); 30 | let formFinal = text.slice(formStart, formEnd + 7); 31 | formFinal = formFinal.replace("form class", "form hidden class"); 32 | 33 | //-- Quietly add the form to the bottom of the body 34 | let docBody = document.querySelector("body"); 35 | docBody.insertAdjacentHTML('beforeend', formFinal); 36 | 37 | //-- Automate steps necessary to delete a repo 38 | let button = document.querySelectorAll(".btn.btn-block.btn-danger")[0]; 39 | document.querySelectorAll("[name='verify']")[0].value = clickObj.repoName; 40 | button.removeAttribute("disabled"); 41 | button.click(); 42 | } 43 | 44 | //-- FUNC: Fetches the page needed to automate deletion of repo, then passes to deleteRepo() 45 | function repoFormFetch() { 46 | fetch(`https://github.com/${userName}/${clickObj.repoName}/settings?_pjax=%23js-repo-pjax-container`, 47 | { 48 | "credentials": "include", 49 | "headers": { 50 | "accept": "text/html", 51 | "accept-language": "en-US,en;q=0.9", 52 | "if-none-match": "W/\"9e3c2df456d87563d30ee986769b2259\"", 53 | "x-pjax": "true", 54 | "x-pjax-container": "#js-repo-pjax-container", 55 | "x-requested-with": "XMLHttpRequest" 56 | }, 57 | "referrer": `https://github.com/${userName}/${clickObj.repoName}`, 58 | "referrerPolicy": "strict-origin-when-cross-origin", 59 | "body": null, 60 | "method": "GET", 61 | "mode": "cors" 62 | }) 63 | .then(res => res.text()) 64 | .then(text => deleteRepo(text)); 65 | } 66 | 67 | //-- FUNC: Checks clicks to follow through with repo deletion 68 | function clickCheck(ctx) { 69 | //-- Get repo name from button 70 | let btnName = ctx.target.getAttribute("repo-name"); 71 | 72 | //-- If the button's repo name is the same as the clickObj "repoName" value, 73 | //-- increment clicks. Otherwise, set clicks to 0 and set the click object 74 | //-- name to the button that was just clicked 75 | if (clickObj.repoName === btnName) { 76 | clickObj.clicks++; 77 | } else { 78 | clickObj.clicks = 0; 79 | clickObj.repoName = btnName; 80 | } 81 | 82 | //-- If clickObj.clicks contains 5 consecutive clicks from the same button, 83 | //-- then delete the repo and reset clickObj 84 | if (clickObj.clicks + 1 === 5) { 85 | clickObj.clicks = 0; 86 | repoFormFetch(); 87 | } 88 | } 89 | 90 | //-- FUNC: Creates delete buttons, click events, etc. and renders the buttons onto the page 91 | function createDeleteButtons() { 92 | for (starBtn of starBtns) { 93 | //-- Create delete button 94 | let delBtn = ``; 95 | 96 | //-- Add delete button to the page before each Star button 97 | starBtn.parentNode.parentNode.parentNode.insertAdjacentHTML('afterbegin', delBtn); 98 | 99 | arrayCount++; 100 | } 101 | 102 | //-- Add event listener to each delete button 103 | allDelBtns = document.querySelectorAll("[repo-name]"); 104 | for (btn of allDelBtns) { 105 | btn.addEventListener("click", (e) => { 106 | e.preventDefault(); 107 | clickCheck(e); 108 | }); 109 | } 110 | 111 | arrayCount = 0; 112 | } 113 | 114 | //-- FUNC: Shows delete buttons only on logged-in user's repositories tab page; otherwise, does nothing 115 | function pageCheck() { 116 | let urlCheck1 = document.URL.includes(userName); 117 | let urlCheck2 = document.URL.includes("tab=repositories"); 118 | 119 | if (urlCheck1 && urlCheck2) createDeleteButtons(); 120 | } 121 | 122 | //-- Script begins here 123 | pageCheck(); 124 | 125 | //-- TODO: Prevent the redirect and instead animate-out a deleted repo. Use preventDefault() and...? 126 | //-- TODO: Make delete buttons "persist" across filter results. Maybe use MutationObserver() and...? 127 | //-- TODO: Error handling. 128 | -------------------------------------------------------------------------------- /scripts/Napalm FTP Indexer.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Napalm FTP Indexer 3 | // @namespace https://www.searchftps.net/ 4 | // @version 1 5 | // @description Kills ads, makes buttons function as expected, etc. 6 | // @author Stephen Chapman 7 | // @match *://*.searchftps.net/* 8 | // @run-at document-idle 9 | // @grant none 10 | // ==/UserScript== 11 | 12 | //-- Eliminate Tampermonkey warnings about not recognizing jQuery 13 | var jQuery = window.jQuery; 14 | 15 | //-- Get rid of ad containers for each page as appropriately passed 16 | function noAds(page) { 17 | switch(page) { 18 | case 'single': 19 | jQuery('.well').eq(0).find('table > tbody > tr:last').remove(); 20 | jQuery('ins').remove(); 21 | break; 22 | case 'search': 23 | jQuery('#result-main-right, ins').remove(); 24 | break; 25 | case 'home': 26 | jQuery('ins').remove(); 27 | break; 28 | default: 29 | break; 30 | } 31 | } 32 | 33 | //-- Decode URL 34 | function urlRevealer(data) { 35 | //-- Find beginning of first instance of decode() 36 | const start = jQuery(data).text().indexOf("decode('"); 37 | 38 | //-- Find end of first instance of decode() 39 | const end = jQuery(data).text().indexOf(")));"); 40 | 41 | //-- Build the string to be decoded; clean up with trim() 42 | const encodedStr = jQuery(data).text().substr(start + 8, (end - start)).replace("')));", "").trim(); 43 | 44 | //-- Decode and return the string (should be the URL) 45 | //-- Using native JS base64 decode function atob() instead of site's custom decode() 46 | return atob(encodedStr); 47 | } 48 | 49 | //-- AJAX call to get file download URLs from individual results pages 50 | function ajaxCall(obj) { 51 | //-- Prepare serialized data to be sent 52 | let str = JSON.stringify(obj.hash).replace(/"/g, '') 53 | str = `action=content&args=type%3Df%26hash%3D${str}` 54 | 55 | //-- Perform AJAX post call, and when finished, update download buttons with direct file URL 56 | jQuery.ajax({ 57 | type: 'post', 58 | url: '/', 59 | data: str, 60 | }).done(function(data) { 61 | //-- Decode and store URL 62 | const url = urlRevealer(data) 63 | 64 | //-- Add URL to hash object for use now, and possibly later if we want 65 | Object.assign(obj, {url: `${url}`}); 66 | 67 | //-- Get the correct download button to replace href for 68 | const dlBtn = jQuery('.dn-btn > a').eq([obj.btnNum] - 1); 69 | 70 | //-- Replace download button href with correct URL from hash object 71 | dlBtn.attr('href', obj.url); 72 | }).fail(function(data) { 73 | //-- Log to the console if there was a failure 74 | console.log("Failed: " + obj); 75 | }); 76 | } 77 | 78 | //-- Single file(s) page 79 | function singleResult() { 80 | //-- Kill ads 81 | noAds('single'); 82 | 83 | //-- Get full contents of