Greasy Fork 还支持 简体中文。

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.

目前為 2024-11-25 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Old Reddit with New Reddit Profile Pictures - Universal Version
// @namespace    https://github.com/Nick2bad4u/UserStyles
// @version      4.2
// @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');

  let profilePictureCache = JSON.parse(localStorage.getItem('profilePictureCache') || '{}');
  let cacheTimestamps = JSON.parse(localStorage.getItem('cacheTimestamps') || '{}');
  const CACHE_DURATION = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds
  const MAX_CACHE_SIZE = 25000; // Maximum number of cache entries

  function saveCache() {
    localStorage.setItem('profilePictureCache', JSON.stringify(profilePictureCache));
    localStorage.setItem('cacheTimestamps', JSON.stringify(cacheTimestamps));
  }

  function flushOldCache() {
    console.log('Flushing old cache');
    const now = Date.now();
    for (const username in cacheTimestamps) {
      if (now - cacheTimestamps[username] > CACHE_DURATION) {
        console.log(`Deleting cache for ${username}`);
        delete profilePictureCache[username];
        delete cacheTimestamps[username];
      }
    }
    saveCache();
    console.log('Old cache entries flushed');
  }

  function limitCacheSize() {
    const cacheEntries = Object.keys(profilePictureCache);
    if (cacheEntries.length > MAX_CACHE_SIZE) {
      console.log('Cache size exceeded, removing oldest entries');
      const sortedEntries = cacheEntries.sort((a, b) => cacheTimestamps[a] - cacheTimestamps[b]);
      const entriesToRemove = sortedEntries.slice(0, cacheEntries.length - MAX_CACHE_SIZE);
      entriesToRemove.forEach((username) => {
        delete profilePictureCache[username];
        delete cacheTimestamps[username];
      });
      saveCache();
      console.log('Cache size limited');
    }
  }

  async function fetchProfilePictures(usernames) {
    console.log('Fetching profile pictures');
    const uncachedUsernames = usernames.filter(
      (username) =>
        !profilePictureCache[username] && username !== '[deleted]' && username !== '[removed]'
    );
    if (uncachedUsernames.length === 0) {
      console.log('All usernames are cached');
      return usernames.map((username) => profilePictureCache[username]);
    }

    console.log(`Fetching profile pictures for: ${uncachedUsernames.join(', ')}`);

    const fetchPromises = uncachedUsernames.map(async (username) => {
      try {
        const response = await fetch(`https://www.reddit.com/user/${username}/about.json`);
        if (!response.ok) {
          console.error(`Error fetching profile picture for ${username}: ${response.statusText}`);
          return null;
        }
        const data = await response.json();
        if (data.data && data.data.icon_img) {
          const profilePictureUrl = data.data.icon_img.split('?')[0];
          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;
      }
    });

    const results = await Promise.all(fetchPromises);
    limitCacheSize();
    return usernames.map((username) => profilePictureCache[username]);
  }

  async function injectProfilePictures(comments) {
    console.log(`Comments found: ${comments.length}`);
    const usernames = Array.from(comments)
      .map((comment) => comment.textContent.trim())
      .filter((username) => username !== '[deleted]' && username !== '[removed]');
    const profilePictureUrls = await fetchProfilePictures(usernames);

    comments.forEach((comment, index) => {
      const username = usernames[index];
      const profilePictureUrl = profilePictureUrls[index];
      if (
        profilePictureUrl &&
        !comment.previousElementSibling?.classList.contains('profile-picture')
      ) {
        console.log(`Injecting profile picture: ${username}`);
        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');
        });
        comment.insertAdjacentElement('beforebegin', img);

        const enlargedImg = document.createElement('img');
        enlargedImg.src = profilePictureUrl;
        enlargedImg.classList.add('enlarged-profile-picture');
        document.body.appendChild(enlargedImg);
        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`;
        });
        img.addEventListener('mouseout', () => {
          enlargedImg.style.display = 'none';
        });
      }
    });
    console.log('Profile pictures injected');
  }

  function setupObserver() {
    console.log('Setting up observer');
    const observer = new MutationObserver((mutations) => {
      const comments = document.querySelectorAll('.author, .c-username');
      if (comments.length > 0) {
        console.log('New comments detected');
        observer.disconnect();
        injectProfilePictures(comments);
      }
    });
    observer.observe(document.body, {
      childList: true,
      subtree: true,
    });
    console.log('Observer initialized');
  }

  function runScript() {
    flushOldCache();
    console.log('Cache loaded:', profilePictureCache);
    setupObserver();
  }

  window.addEventListener('load', () => {
    console.log('Page loaded');
    runScript();
    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);
})();