🏠 Home 

Greasy Fork is available in English.

Old Reddit with New Reddit Profile Pictures - Universal Version

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 present
let profilePictureCache = JSON.parse(localStorage.getItem('profilePictureCache') || '{}');
// Initialize the cache timestamps from localStorage or as an empty object if not present
let cacheTimestamps = JSON.parse(localStorage.getItem('cacheTimestamps') || '{}');
// Define the cache duration as 7 days in milliseconds
const CACHE_DURATION = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds
// Define the maximum number of cache entries
const MAX_CACHE_SIZE = 25000; // Maximum number of cache entries
// Function to save the current state of the cache to localStorage
function saveCache() {
localStorage.setItem('profilePictureCache', JSON.stringify(profilePictureCache));
localStorage.setItem('cacheTimestamps', JSON.stringify(cacheTimestamps));
}
// Function to flush old cache entries
function flushOldCache() {
console.log('Flushing old cache');
const now = Date.now();
// Iterate over all usernames in the cache timestamps
for (const username in cacheTimestamps) {
// Check if the cache entry is older than the allowed cache duration
if (now - cacheTimestamps[username] > CACHE_DURATION) {
console.log(`Deleting cache for ${username}`);
// Delete the cache entry for this username
delete profilePictureCache[username];
// Delete the timestamp for this username
delete cacheTimestamps[username];
}
}
// Save the updated cache state to localStorage
saveCache();
console.log('Old cache entries flushed');
}
// Function to limit the size of the profile picture cache
function limitCacheSize() {
// Get an array of all usernames currently in the cache
const cacheEntries = Object.keys(profilePictureCache);
// Check if the cache size exceeds the maximum allowed size
if (cacheEntries.length > MAX_CACHE_SIZE) {
console.log('Cache size exceeded, removing oldest entries');
// Sort the cache entries by their timestamps in ascending order
const sortedEntries = cacheEntries.sort((a, b) => cacheTimestamps[a] - cacheTimestamps[b]);
// Determine the number of entries to remove to fit within the maximum cache size
const entriesToRemove = sortedEntries.slice(0, cacheEntries.length - MAX_CACHE_SIZE);
// Remove the oldest entries from the cache and timestamps
entriesToRemove.forEach((username) => {
delete profilePictureCache[username];
delete cacheTimestamps[username];
});
// Save the updated cache state
saveCache();
console.log('Cache size limited');
}
}
// Asynchronous function to fetch profile pictures for a list of usernames
async function fetchProfilePictures(usernames) {
console.log('Fetching profile pictures');
// Filter out usernames that are already in the cache or are marked as deleted or removed
const uncachedUsernames = usernames.filter((username) => !profilePictureCache[username] && username !== '[deleted]' && username !== '[removed]');
// If all usernames are cached, return the cached profile pictures
if (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 pictures
const fetchPromises = uncachedUsernames.map(async (username) => {
try {
// Fetch user data from Reddit
const response = await fetch(`https://www.reddit.com/user/${username}/about.json`);
// Check if the response is successful
if (!response.ok) {
console.error(`Error fetching profile picture for ${username}: ${response.statusText}`);
return null;
}
// Parse the response JSON data
const data = await response.json();
// Check if the data contains a profile picture URL
if (data.data && data.data.icon_img) {
const profilePictureUrl = data.data.icon_img.split('?')[0];
// Cache the profile picture URL and timestamp
profilePictureCache[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-vars
const r###lts = await Promise.all(fetchPromises);
// Limit the cache size if necessary
limitCacheSize();
// Return the profile pictures, using the cache for already fetched usernames
return usernames.map((username) => profilePictureCache[username]);
}
// Asynchronous function to inject profile pictures into comments
async function injectProfilePictures(comments) {
console.log(`Comments found: ${comments.length}`);
// Extract usernames from comments, filtering out deleted or removed comments
const usernames = Array.from(comments)
.map((comment) => comment.textContent.trim())
.filter((username) => username !== '[deleted]' && username !== '[removed]');
// Fetch profile pictures for the extracted usernames
const profilePictureUrls = await fetchProfilePictures(usernames);
// Iterate over each comment and inject the corresponding profile picture
comments.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 injected
if (profilePictureUrl && !comment.previousElementSibling?.classList.contains('profile-picture')) {
console.log(`Injecting profile picture: ${username}`);
// Create and configure the img element for the profile picture
const 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 element
comment.insertAdjacentElement('beforebegin', img);
// Create an enlarged version of the profile picture for hover effect
const enlargedImg = document.createElement('img');
enlargedImg.src = profilePictureUrl;
enlargedImg.classList.add('enlarged-profile-picture');
document.body.appendChild(enlargedImg);
// Show the enlarged profile picture on mouseover
img.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 mouseout
img.addEventListener('mouseout', () => {
enlargedImg.style.display = 'none';
});
}
});
console.log('Profile pictures injected');
}
// Function to set up a MutationObserver to detect new comments
function setupObserver() {
console.log('Setting up observer');
// Create a new MutationObserver instance and define the callback function
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const 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 pictures
if (comments.length > 0) {
console.log('New comments detected');
observer.disconnect();
injectProfilePictures(comments);
}
});
// Start observing the document body for changes in the child elements and subtree
observer.observe(document.body, {
childList: true,
subtree: true,
});
console.log('Observer initialized');
}
// Function to run the script
function runScript() {
// Flush old cache data
flushOldCache();
// Log the current state of the profile picture cache
console.log('Cache loaded:', profilePictureCache);
// Set up a MutationObserver to detect new comments
setupObserver();
}
// Add an event listener for the 'load' event to ensure the script runs after the page has fully loaded
window.addEventListener('load', () => {
console.log('Page loaded');
// Run the script immediately after the page loads
runScript();
// Set an interval to run the script every 10 seconds
setInterval(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);
})();