Guest User

Untitled

a guest
Jun 6th, 2025
34
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name         Archive Links for Images (Throttled, Non-duplicating)
  3. // @namespace    https://perplexity.ai
  4. // @version      1.0.0
  5. // @description  Adds archive/wayback links under valid images in posting areas, throttled and deduplicated.
  6. // @author       Perplexity QA AI
  7. // @include      */threads/*
  8. // @grant        none
  9. // @run-at       document-end
  10. // ==/UserScript==
  11.  
  12. (function() {
  13.   'use strict';
  14.  
  15.   // ---- Configurable: posting area selector ----
  16.   const POSTING_AREA_SELECTOR = 'body'; // Change e.g. '.message-body' as needed
  17.  
  18.   // ---- Exclusion logic, per spec ----
  19.   function isExcluded(src) {
  20.     if (!src) return true;
  21.     return (
  22.       src.includes('https://static.cdninstagram.com') ||
  23.       src.includes('/data/avatars/') ||
  24.       src.includes('twimg.com/profile_images') ||
  25.       src.includes('twitter.com/profile_images') ||
  26.       src.startsWith('https://www.thecoli.com/styles/') ||
  27.       src.startsWith('data:image/') ||
  28.       src.startsWith('https://cdn.jsdelivr.net') ||
  29.       src.toLowerCase().includes('unicode') ||
  30.       src.toLowerCase().includes('favicon')
  31.     );
  32.   }
  33.  
  34.   // ---- Marking processed images for deduplication ----
  35.   const ARCHIVE_MARK = 'data-archive-links';
  36.  
  37.   // ---- Archive Link Generation ----
  38.   function createArchiveLinks(url) {
  39.     const archiveUrl = 'https://web.archive.org/save/' + encodeURIComponent(url);
  40.     const archiveIsUrl = 'https://archive.is/?run=1&url=' + encodeURIComponent(url);
  41.     const viewArchiveUrl = 'https://web.archive.org/web/*/' + encodeURIComponent(url);
  42.  
  43.     // Container
  44.     const div = document.createElement('div');
  45.     div.style.textAlign = 'center';
  46.     div.style.fontSize = '12px';
  47.     div.style.marginTop = '2px';
  48.  
  49.     // Archive (Wayback Save) link
  50.     const aArchive = document.createElement('a');
  51.     aArchive.href = archiveUrl;
  52.     aArchive.textContent = 'archive';
  53.     aArchive.target = '_blank';
  54.     aArchive.rel = 'noopener noreferrer';
  55.     aArchive.style.textDecoration = 'underline';
  56.  
  57.     // Archive.is link
  58.     const aArchiveIs = document.createElement('a');
  59.     aArchiveIs.href = archiveIsUrl;
  60.     aArchiveIs.textContent = 'archive.is';
  61.     aArchiveIs.target = '_blank';
  62.     aArchiveIs.rel = 'noopener noreferrer';
  63.     aArchiveIs.style.textDecoration = 'underline';
  64.     aArchiveIs.style.margin = '0 5px';
  65.  
  66.     // View archive (Wayback Index)
  67.     const aView = document.createElement('a');
  68.     aView.href = viewArchiveUrl;
  69.     aView.textContent = 'view archive';
  70.     aView.target = '_blank';
  71.     aView.rel = 'noopener noreferrer';
  72.     aView.style.textDecoration = 'underline';
  73.  
  74.     div.appendChild(aArchive);
  75.     div.appendChild(document.createTextNode(' | '));
  76.     div.appendChild(aArchiveIs);
  77.     div.appendChild(document.createTextNode(' | '));
  78.     div.appendChild(aView);
  79.  
  80.     return div;
  81.   }
  82.  
  83.   // ---- Main processing logic ----
  84.   function processImages() {
  85.     const area = document.querySelector(POSTING_AREA_SELECTOR);
  86.     if (!area) return;
  87.  
  88.     area.querySelectorAll('img').forEach(img => {
  89.       if (img.hasAttribute(ARCHIVE_MARK)) return; // Already processed
  90.       const src = img.getAttribute('src') || '';
  91.       if (isExcluded(src)) return;
  92.       img.setAttribute(ARCHIVE_MARK, '1');
  93.       const links = createArchiveLinks(src);
  94.       // Insert after image, in DOM order
  95.       if (img.nextSibling) {
  96.         img.parentNode.insertBefore(links, img.nextSibling);
  97.       } else {
  98.         img.parentNode.appendChild(links);
  99.       }
  100.     });
  101.   }
  102.  
  103.   // ---- Throttled mutation observer logic ----
  104.   let throttleTimeout = null;
  105.   function throttledProcess() {
  106.     if (throttleTimeout) return;
  107.     throttleTimeout = setTimeout(() => {
  108.       throttleTimeout = null;
  109.       processImages();
  110.     }, 300); // 300ms throttle; adjust if needed
  111.   }
  112.  
  113.   // Observe entire posting area for changes
  114.   const observer = new MutationObserver(throttledProcess);
  115.   observer.observe(document.querySelector(POSTING_AREA_SELECTOR), {
  116.     childList: true,
  117.     subtree: true
  118.   });
  119.  
  120.   // Process initially on DOM ready
  121.   processImages();
  122.  
  123.   // Self-check mechanism to prevent excessive CPU usage on complex pages
  124.   let cycleCount = 0;
  125.   const MAX_CYCLES = 10;
  126.   const cycleMonitor = setInterval(() => {
  127.     if (++cycleCount > MAX_CYCLES) {
  128.       // If we're still active after many cycles, reduce observation sensitivity
  129.       observer.disconnect();
  130.  
  131.       // Reconnect with more conservative settings
  132.       observer.observe(document.querySelector(POSTING_AREA_SELECTOR), {
  133.         childList: true,
  134.         subtree: true,
  135.         attributes: false,
  136.         characterData: false
  137.       });
  138.  
  139.       clearInterval(cycleMonitor);
  140.     }
  141.   }, 5000);
  142.  
  143.   // Cleanup function - explicit disconnect when page unloads
  144.   window.addEventListener('beforeunload', () => {
  145.     observer.disconnect();
  146.     clearInterval(cycleMonitor);
  147.     if (throttleTimeout) {
  148.       clearTimeout(throttleTimeout);
  149.       throttleTimeout = null;
  150.     }
  151.   }, { once: true });
  152.  
  153.   // Handle potential errors to prevent script failure
  154.   window.addEventListener('error', (e) => {
  155.     if (e.error && e.error.message && e.error.message.includes('observer')) {
  156.       console.error('Image Archive Links: Observer error detected, reconnecting');
  157.       try {
  158.         observer.disconnect();
  159.         setTimeout(() => {
  160.           observer.observe(document.querySelector(POSTING_AREA_SELECTOR), {
  161.             childList: true,
  162.             subtree: true
  163.           });
  164.         }, 1000);
  165.       } catch (err) {
  166.         console.error('Image Archive Links: Recovery failed');
  167.       }
  168.     }
  169.   });
  170. })();
Advertisement
Add Comment
Please, Sign In to add comment