// ==UserScript== // @name Kink Image Replacer Everywhere // @namespace https://barefoot-boi.itch.io/ // @version 2.0.0 // @description Replaces all webpage images (Amazon, eBay, Google Images, etc.) with custom API images. // @author @Barefootboi92 // @match *://*/* // @grant GM_xmlhttpRequest // @connect * // @run-at document-idle // ==/UserScript== (function() { 'use strict'; // ===== CONFIG - EDIT THESE ===== // Set this to whichever of your 3 image pool URLs you want to use // https://footfetish.blakesfeet1992.com/random // https://rubber.blakesfeet1992.com/random // https://sneakers.blakesfeet1992.com/random const API_URL = "https://footfetish.blakesfeet1992.com/random"; // How many different images to fetch per page load. // Keep this between 3 and 10 so you don't overwhelm your server API! const POOL_SIZE = 5; // ================================ let imagePool = []; // Fetches a single image URL from the API function fetchImage() { return new Promise((resolve) => { GM_xmlhttpRequest({ method: 'GET', url: API_URL, onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.url) resolve(data.url); else resolve(null); } catch (e) { resolve(null); } }, onerror: () => resolve(null) }); }); } // Fills our pool of images so we have a few varieties to cycle through async function initPool() { let promises = []; for(let i = 0; i < POOL_SIZE; i++) { promises.push(fetchImage()); } const results = await Promise.all(promises); // Filter out any failed requests imagePool = results.filter(url => url !== null); if (imagePool.length > 0) { console.log(`[ImageReplacer] Successfully loaded ${imagePool.length} images.`); replaceExistingImages(); observeNewImages(); } else { console.error('[ImageReplacer] Failed to load any images from the API.'); } } // Swaps a specific image element with a random one from our pool function swapImage(img) { if (img.dataset.kinkified) return; if (imagePool.length === 0) return; const randomImage = imagePool[Math.floor(Math.random() * imagePool.length)]; // Standard image tags if (img.tagName.toLowerCase() === 'img') { img.src = randomImage; if (img.hasAttribute('srcset')) img.setAttribute('srcset', randomImage); // Defeat lazy-loading attributes that modern sites use to override images ['data-src', 'data-srcset', 'data-zoom-image', 'data-old-hires', 'data-original'].forEach(attr => { if (img.hasAttribute(attr)) img.setAttribute(attr, randomImage); }); } // Picture source tags (used for responsive mobile/desktop layouts) else if (img.tagName.toLowerCase() === 'source') { if (img.hasAttribute('srcset')) img.setAttribute('srcset', randomImage); } // Mark it so we don't process it endlessly img.dataset.kinkified = 'true'; } // Scans the page for images currently loaded function replaceExistingImages() { const images = document.querySelectorAll('img, picture source'); images.forEach(swapImage); } // Watches the page for new images being loaded in (e.g., infinite scrolling) function observeNewImages() { const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { // 1. Check for newly added image nodes (scrolling down Google Images) for (const node of mutation.addedNodes) { if (node.nodeType === 1) { // ELEMENT_NODE if (['img', 'source'].includes(node.tagName.toLowerCase())) { swapImage(node); } // Also check inside new containers const childImages = node.querySelectorAll('img, picture source'); childImages.forEach(swapImage); } } // 2. Prevent the website from changing the image back to the original if (mutation.type === 'attributes' && (mutation.attributeName === 'src' || mutation.attributeName === 'srcset')) { const target = mutation.target; if (target.dataset.kinkified) { const currentSrc = target.src || target.srcset || ''; // If the site updated the src, and it's NOT one of our pool images, re-replace it const isOurs = imagePool.some(url => currentSrc.includes(url)); if (!isOurs) { target.dataset.kinkified = ''; // Reset flag swapImage(target); } } } } }); // Start watching the whole body for changes observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['src', 'srcset'] // Only care if an image src changes }); } // Start the script initPool(); })();