Greasy Fork is available in English.
Injects new Reddit profile pictures into Old Reddit and Reddit-Stream.com next to the username. Caches in localstorage.
// ==UserScript==// @name Old Reddit with New Reddit Profile Pictures - Universal Version// @namespace typpi.online// @version 7.0.7// @description Injects new Reddit profile pictures into Old Reddit and Reddit-Stream.com next to the username. Caches in localstorage.// @author Nick2bad4u// @match *://*.reddit.com/*// @match *://reddit-stream.com/*// @connect reddit.com// @connect reddit-stream.com// @grant GM_xmlhttpRequest// @homepageURL https://github.com/Nick2bad4u/UserStyles// @license Unlicense// @resource https://www.google.com/s2/favicons?sz=64&domain=reddit.com// @icon https://www.google.com/s2/favicons?sz=64&domain=reddit.com// @icon64 https://www.google.com/s2/favicons?sz=64&domain=reddit.com// @run-at document-start// @tag reddit// ==/UserScript==(function () {'use strict';console.log('Script loaded');// Initialize the profile picture cache from localStorage or as an empty object if not presentlet profilePictureCache = JSON.parse(localStorage.getItem('profilePictureCache') || '{}');// Initialize the cache timestamps from localStorage or as an empty object if not presentlet cacheTimestamps = JSON.parse(localStorage.getItem('cacheTimestamps') || '{}');// Define the cache duration as 7 days in millisecondsconst CACHE_DURATION = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds// Define the maximum number of cache entriesconst MAX_CACHE_SIZE = 25000; // Maximum number of cache entries// Function to save the current state of the cache to localStoragefunction saveCache() {localStorage.setItem('profilePictureCache', JSON.stringify(profilePictureCache));localStorage.setItem('cacheTimestamps', JSON.stringify(cacheTimestamps));}// Function to flush old cache entriesfunction flushOldCache() {console.log('Flushing old cache');const now = Date.now();// Iterate over all usernames in the cache timestampsfor (const username in cacheTimestamps) {// Check if the cache entry is older than the allowed cache durationif (now - cacheTimestamps[username] > CACHE_DURATION) {console.log(`Deleting cache for ${username}`);// Delete the cache entry for this usernamedelete profilePictureCache[username];// Delete the timestamp for this usernamedelete cacheTimestamps[username];}}// Save the updated cache state to localStoragesaveCache();console.log('Old cache entries flushed');}// Function to limit the size of the profile picture cachefunction limitCacheSize() {// Get an array of all usernames currently in the cacheconst cacheEntries = Object.keys(profilePictureCache);// Check if the cache size exceeds the maximum allowed sizeif (cacheEntries.length > MAX_CACHE_SIZE) {console.log('Cache size exceeded, removing oldest entries');// Sort the cache entries by their timestamps in ascending orderconst sortedEntries = cacheEntries.sort((a, b) => cacheTimestamps[a] - cacheTimestamps[b]);// Determine the number of entries to remove to fit within the maximum cache sizeconst entriesToRemove = sortedEntries.slice(0, cacheEntries.length - MAX_CACHE_SIZE);// Remove the oldest entries from the cache and timestampsentriesToRemove.forEach((username) => {delete profilePictureCache[username];delete cacheTimestamps[username];});// Save the updated cache statesaveCache();console.log('Cache size limited');}}// Asynchronous function to fetch profile pictures for a list of usernamesasync function fetchProfilePictures(usernames) {console.log('Fetching profile pictures');// Filter out usernames that are already in the cache or are marked as deleted or removedconst uncachedUsernames = usernames.filter((username) => !profilePictureCache[username] && username !== '[deleted]' && username !== '[removed]');// If all usernames are cached, return the cached profile picturesif (uncachedUsernames.length === 0) {console.log('All usernames are cached');return usernames.map((username) => profilePictureCache[username]);}console.log(`Fetching profile pictures for: ${uncachedUsernames.join(', ')}`);// Map over the uncached usernames and fetch their profile picturesconst fetchPromises = uncachedUsernames.map(async (username) => {try {// Fetch user data from Redditconst response = await fetch(`https://www.reddit.com/user/${username}/about.json`);// Check if the response is successfulif (!response.ok) {console.error(`Error fetching profile picture for ${username}: ${response.statusText}`);return null;}// Parse the response JSON dataconst data = await response.json();// Check if the data contains a profile picture URLif (data.data && data.data.icon_img) {const profilePictureUrl = data.data.icon_img.split('?')[0];// Cache the profile picture URL and timestampprofilePictureCache[username] = profilePictureUrl;cacheTimestamps[username] = Date.now();saveCache();console.log(`Fetched profile picture: ${username}`);return profilePictureUrl;} else {console.warn(`No profile picture found for: ${username}`);return null;}} catch (error) {console.error(`Error fetching profile picture for ${username}:`, error);return null;}});// Wait for all fetch promises to resolve// eslint-disable-next-line @typescript-eslint/no-unused-varsconst r###lts = await Promise.all(fetchPromises);// Limit the cache size if necessarylimitCacheSize();// Return the profile pictures, using the cache for already fetched usernamesreturn usernames.map((username) => profilePictureCache[username]);}// Asynchronous function to inject profile pictures into commentsasync function injectProfilePictures(comments) {console.log(`Comments found: ${comments.length}`);// Extract usernames from comments, filtering out deleted or removed commentsconst usernames = Array.from(comments).map((comment) => comment.textContent.trim()).filter((username) => username !== '[deleted]' && username !== '[removed]');// Fetch profile pictures for the extracted usernamesconst profilePictureUrls = await fetchProfilePictures(usernames);// Iterate over each comment and inject the corresponding profile picturecomments.forEach((comment, index) => {const username = usernames[index];const profilePictureUrl = profilePictureUrls[index];// Check if the profile picture URL is valid and the profile picture is not already injectedif (profilePictureUrl && !comment.previousElementSibling?.classList.contains('profile-picture')) {console.log(`Injecting profile picture: ${username}`);// Create and configure the img element for the profile pictureconst img = document.createElement('img');img.src = profilePictureUrl;img.classList.add('profile-picture');img.onerror = () => {img.style.display = 'none';};img.addEventListener('click', () => {window.open(profilePictureUrl, '_blank');});// Insert the profile picture before the comment elementcomment.insertAdjacentElement('beforebegin', img);// Create an enlarged version of the profile picture for hover effectconst enlargedImg = document.createElement('img');enlargedImg.src = profilePictureUrl;enlargedImg.classList.add('enlarged-profile-picture');document.body.appendChild(enlargedImg);// Show the enlarged profile picture on mouseoverimg.addEventListener('mouseover', () => {enlargedImg.style.display = 'block';const rect = img.getBoundingClientRect();enlargedImg.style.top = `${rect.top + window.scrollY + 20}px`;enlargedImg.style.left = `${rect.left + window.scrollX + 20}px`;});// Hide the enlarged profile picture on mouseoutimg.addEventListener('mouseout', () => {enlargedImg.style.display = 'none';});}});console.log('Profile pictures injected');}// Function to set up a MutationObserver to detect new commentsfunction setupObserver() {console.log('Setting up observer');// Create a new MutationObserver instance and define the callback function// eslint-disable-next-line @typescript-eslint/no-unused-varsconst observer = new MutationObserver((mutations) => {// Select all elements with the classes 'author' or 'c-username'const comments = document.querySelectorAll('.author, .c-username');// If new comments are detected, disconnect the observer and inject profile picturesif (comments.length > 0) {console.log('New comments detected');observer.disconnect();injectProfilePictures(comments);}});// Start observing the document body for changes in the child elements and subtreeobserver.observe(document.body, {childList: true,subtree: true,});console.log('Observer initialized');}// Function to run the scriptfunction runScript() {// Flush old cache dataflushOldCache();// Log the current state of the profile picture cacheconsole.log('Cache loaded:', profilePictureCache);// Set up a MutationObserver to detect new commentssetupObserver();}// Add an event listener for the 'load' event to ensure the script runs after the page has fully loadedwindow.addEventListener('load', () => {console.log('Page loaded');// Run the script immediately after the page loadsrunScript();// Set an interval to run the script every 10 secondssetInterval(runScript, 10000); // Run every 10 seconds});const style = document.createElement('style');style.textContent = `.profile-picture {width: 20px;height: 20px;border-radius: 50%;margin-right: 5px;transition: transform 0.2s ease-in-out;position: relative;z-index: 1;cursor: pointer;}.enlarged-profile-picture {width: 250px;height: 250px;border-radius: 50%;position: absolute;display: none;z-index: 1000;pointer-events: none;outline: 3px solid #000;box-shadow: 0 4px 8px rgba(0, 0, 0, 1);background-color: rgba(0, 0, 0, 1);}`;document.head.appendChild(style);})();