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.

  1. // ==UserScript==
  2. // @name Old Reddit with New Reddit Profile Pictures - Universal Version
  3. // @namespace typpi.online
  4. // @version 7.0.7
  5. // @description Injects new Reddit profile pictures into Old Reddit and Reddit-Stream.com next to the username. Caches in localstorage.
  6. // @author Nick2bad4u
  7. // @match *://*.reddit.com/*
  8. // @match *://reddit-stream.com/*
  9. // @connect reddit.com
  10. // @connect reddit-stream.com
  11. // @grant GM_xmlhttpRequest
  12. // @homepageURL https://github.com/Nick2bad4u/UserStyles
  13. // @license Unlicense
  14. // @resource https://www.google.com/s2/favicons?sz=64&domain=reddit.com
  15. // @icon https://www.google.com/s2/favicons?sz=64&domain=reddit.com
  16. // @icon64 https://www.google.com/s2/favicons?sz=64&domain=reddit.com
  17. // @run-at document-start
  18. // @tag reddit
  19. // ==/UserScript==
  20.  
  21. (function () {
  22. 'use strict';
  23. console.log('Script loaded');
  24.  
  25. // Initialize the profile picture cache from localStorage or as an empty object if not present
  26. let profilePictureCache = JSON.parse(localStorage.getItem('profilePictureCache') || '{}');
  27.  
  28. // Initialize the cache timestamps from localStorage or as an empty object if not present
  29. let cacheTimestamps = JSON.parse(localStorage.getItem('cacheTimestamps') || '{}');
  30.  
  31. // Define the cache duration as 7 days in milliseconds
  32. const CACHE_DURATION = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds
  33.  
  34. // Define the maximum number of cache entries
  35. const MAX_CACHE_SIZE = 25000; // Maximum number of cache entries
  36.  
  37. // Function to save the current state of the cache to localStorage
  38. function saveCache() {
  39. localStorage.setItem('profilePictureCache', JSON.stringify(profilePictureCache));
  40. localStorage.setItem('cacheTimestamps', JSON.stringify(cacheTimestamps));
  41. }
  42.  
  43. // Function to flush old cache entries
  44. function flushOldCache() {
  45. console.log('Flushing old cache');
  46. const now = Date.now();
  47.  
  48. // Iterate over all usernames in the cache timestamps
  49. for (const username in cacheTimestamps) {
  50. // Check if the cache entry is older than the allowed cache duration
  51. if (now - cacheTimestamps[username] > CACHE_DURATION) {
  52. console.log(`Deleting cache for ${username}`);
  53. // Delete the cache entry for this username
  54. delete profilePictureCache[username];
  55. // Delete the timestamp for this username
  56. delete cacheTimestamps[username];
  57. }
  58. }
  59.  
  60. // Save the updated cache state to localStorage
  61. saveCache();
  62. console.log('Old cache entries flushed');
  63. }
  64.  
  65. // Function to limit the size of the profile picture cache
  66. function limitCacheSize() {
  67. // Get an array of all usernames currently in the cache
  68. const cacheEntries = Object.keys(profilePictureCache);
  69.  
  70. // Check if the cache size exceeds the maximum allowed size
  71. if (cacheEntries.length > MAX_CACHE_SIZE) {
  72. console.log('Cache size exceeded, removing oldest entries');
  73.  
  74. // Sort the cache entries by their timestamps in ascending order
  75. const sortedEntries = cacheEntries.sort((a, b) => cacheTimestamps[a] - cacheTimestamps[b]);
  76.  
  77. // Determine the number of entries to remove to fit within the maximum cache size
  78. const entriesToRemove = sortedEntries.slice(0, cacheEntries.length - MAX_CACHE_SIZE);
  79.  
  80. // Remove the oldest entries from the cache and timestamps
  81. entriesToRemove.forEach((username) => {
  82. delete profilePictureCache[username];
  83. delete cacheTimestamps[username];
  84. });
  85.  
  86. // Save the updated cache state
  87. saveCache();
  88. console.log('Cache size limited');
  89. }
  90. }
  91.  
  92. // Asynchronous function to fetch profile pictures for a list of usernames
  93. async function fetchProfilePictures(usernames) {
  94. console.log('Fetching profile pictures');
  95.  
  96. // Filter out usernames that are already in the cache or are marked as deleted or removed
  97. const uncachedUsernames = usernames.filter((username) => !profilePictureCache[username] && username !== '[deleted]' && username !== '[removed]');
  98.  
  99. // If all usernames are cached, return the cached profile pictures
  100. if (uncachedUsernames.length === 0) {
  101. console.log('All usernames are cached');
  102. return usernames.map((username) => profilePictureCache[username]);
  103. }
  104.  
  105. console.log(`Fetching profile pictures for: ${uncachedUsernames.join(', ')}`);
  106.  
  107. // Map over the uncached usernames and fetch their profile pictures
  108. const fetchPromises = uncachedUsernames.map(async (username) => {
  109. try {
  110. // Fetch user data from Reddit
  111. const response = await fetch(`https://www.reddit.com/user/${username}/about.json`);
  112.  
  113. // Check if the response is successful
  114. if (!response.ok) {
  115. console.error(`Error fetching profile picture for ${username}: ${response.statusText}`);
  116. return null;
  117. }
  118.  
  119. // Parse the response JSON data
  120. const data = await response.json();
  121.  
  122. // Check if the data contains a profile picture URL
  123. if (data.data && data.data.icon_img) {
  124. const profilePictureUrl = data.data.icon_img.split('?')[0];
  125.  
  126. // Cache the profile picture URL and timestamp
  127. profilePictureCache[username] = profilePictureUrl;
  128. cacheTimestamps[username] = Date.now();
  129. saveCache();
  130. console.log(`Fetched profile picture: ${username}`);
  131. return profilePictureUrl;
  132. } else {
  133. console.warn(`No profile picture found for: ${username}`);
  134. return null;
  135. }
  136. } catch (error) {
  137. console.error(`Error fetching profile picture for ${username}:`, error);
  138. return null;
  139. }
  140. });
  141.  
  142. // Wait for all fetch promises to resolve
  143. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  144. const results = await Promise.all(fetchPromises);
  145.  
  146. // Limit the cache size if necessary
  147. limitCacheSize();
  148.  
  149. // Return the profile pictures, using the cache for already fetched usernames
  150. return usernames.map((username) => profilePictureCache[username]);
  151. }
  152.  
  153. // Asynchronous function to inject profile pictures into comments
  154. async function injectProfilePictures(comments) {
  155. console.log(`Comments found: ${comments.length}`);
  156.  
  157. // Extract usernames from comments, filtering out deleted or removed comments
  158. const usernames = Array.from(comments)
  159. .map((comment) => comment.textContent.trim())
  160. .filter((username) => username !== '[deleted]' && username !== '[removed]');
  161.  
  162. // Fetch profile pictures for the extracted usernames
  163. const profilePictureUrls = await fetchProfilePictures(usernames);
  164.  
  165. // Iterate over each comment and inject the corresponding profile picture
  166. comments.forEach((comment, index) => {
  167. const username = usernames[index];
  168. const profilePictureUrl = profilePictureUrls[index];
  169.  
  170. // Check if the profile picture URL is valid and the profile picture is not already injected
  171. if (profilePictureUrl && !comment.previousElementSibling?.classList.contains('profile-picture')) {
  172. console.log(`Injecting profile picture: ${username}`);
  173.  
  174. // Create and configure the img element for the profile picture
  175. const img = document.createElement('img');
  176. img.src = profilePictureUrl;
  177. img.classList.add('profile-picture');
  178. img.onerror = () => {
  179. img.style.display = 'none';
  180. };
  181. img.addEventListener('click', () => {
  182. window.open(profilePictureUrl, '_blank');
  183. });
  184.  
  185. // Insert the profile picture before the comment element
  186. comment.insertAdjacentElement('beforebegin', img);
  187.  
  188. // Create an enlarged version of the profile picture for hover effect
  189. const enlargedImg = document.createElement('img');
  190. enlargedImg.src = profilePictureUrl;
  191. enlargedImg.classList.add('enlarged-profile-picture');
  192. document.body.appendChild(enlargedImg);
  193.  
  194. // Show the enlarged profile picture on mouseover
  195. img.addEventListener('mouseover', () => {
  196. enlargedImg.style.display = 'block';
  197. const rect = img.getBoundingClientRect();
  198. enlargedImg.style.top = `${rect.top + window.scrollY + 20}px`;
  199. enlargedImg.style.left = `${rect.left + window.scrollX + 20}px`;
  200. });
  201.  
  202. // Hide the enlarged profile picture on mouseout
  203. img.addEventListener('mouseout', () => {
  204. enlargedImg.style.display = 'none';
  205. });
  206. }
  207. });
  208.  
  209. console.log('Profile pictures injected');
  210. }
  211.  
  212. // Function to set up a MutationObserver to detect new comments
  213. function setupObserver() {
  214. console.log('Setting up observer');
  215.  
  216. // Create a new MutationObserver instance and define the callback function
  217. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  218. const observer = new MutationObserver((mutations) => {
  219. // Select all elements with the classes 'author' or 'c-username'
  220. const comments = document.querySelectorAll('.author, .c-username');
  221.  
  222. // If new comments are detected, disconnect the observer and inject profile pictures
  223. if (comments.length > 0) {
  224. console.log('New comments detected');
  225. observer.disconnect();
  226. injectProfilePictures(comments);
  227. }
  228. });
  229.  
  230. // Start observing the document body for changes in the child elements and subtree
  231. observer.observe(document.body, {
  232. childList: true,
  233. subtree: true,
  234. });
  235.  
  236. console.log('Observer initialized');
  237. }
  238.  
  239. // Function to run the script
  240. function runScript() {
  241. // Flush old cache data
  242. flushOldCache();
  243.  
  244. // Log the current state of the profile picture cache
  245. console.log('Cache loaded:', profilePictureCache);
  246.  
  247. // Set up a MutationObserver to detect new comments
  248. setupObserver();
  249. }
  250.  
  251. // Add an event listener for the 'load' event to ensure the script runs after the page has fully loaded
  252. window.addEventListener('load', () => {
  253. console.log('Page loaded');
  254.  
  255. // Run the script immediately after the page loads
  256. runScript();
  257.  
  258. // Set an interval to run the script every 10 seconds
  259. setInterval(runScript, 10000); // Run every 10 seconds
  260. });
  261. const style = document.createElement('style');
  262. style.textContent = `
  263. .profile-picture {
  264. width: 20px;
  265. height: 20px;
  266. border-radius: 50%;
  267. margin-right: 5px;
  268. transition: transform 0.2s ease-in-out;
  269. position: relative;
  270. z-index: 1;
  271. cursor: pointer;
  272. }
  273. .enlarged-profile-picture {
  274. width: 250px;
  275. height: 250px;
  276. border-radius: 50%;
  277. position: absolute;
  278. display: none;
  279. z-index: 1000;
  280. pointer-events: none;
  281. outline: 3px solid #000;
  282. box-shadow: 0 4px 8px rgba(0, 0, 0, 1);
  283. background-color: rgba(0, 0, 0, 1);
  284. }
  285. `;
  286. document.head.appendChild(style);
  287. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址