Guest User

Untitled

a guest
Jul 2nd, 2025
35
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name         Stealthgram Carousel Media Gallery Toggle
  3. // @namespace    http://tampermonkey.net/
  4. // @version      1.4
  5. // @description  Floating button to extract all images/videos from carousels and show in a modal gallery; cleans up on toggle-off
  6. // @author       Your Name
  7. // @match        *://stealthgram.com/*
  8. // @grant        none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12.   'use strict';
  13.  
  14.   // ----- Configurable parameters -----
  15.   const BUTTON_LABEL = "ON";
  16.   const BUTTON_POSITION = 'bottom: 32px; right: 32px;';
  17.   const BUTTON_ACTIVE_STYLE = `
  18.     background: #1976D2;
  19.     color: #fff;
  20.     border: 2px solid #1976D2;
  21.     box-shadow: 0 2px 10px rgba(25,118,210,0.15);
  22.   `;
  23.   const BUTTON_INACTIVE_STYLE = `
  24.     background: #fff;
  25.     color: #1976D2;
  26.     border: 2px solid #1976D2;
  27.     box-shadow: 0 2px 10px rgba(25,118,210,0.08);
  28.   `;
  29.   const BUTTON_COMMON_STYLE = `
  30.     position: fixed; z-index: 99999; ${BUTTON_POSITION}
  31.     font-size: 1.1rem;
  32.     font-family: inherit;
  33.     font-weight: bold;
  34.     padding: 14px 28px;
  35.     border-radius: 32px;
  36.     cursor: pointer;
  37.     transition: all 0.2s;
  38.     outline: none;
  39.     opacity: .95;
  40.   `;
  41.   const SCRIPT_NAME = "Stealthgram Carousel Media Gallery Toggle";
  42.   const MAX_CONSECUTIVE_NO_MEDIA = 3; // Stop after 3 cycles with no new media
  43.  
  44.   // ------- State --------
  45.   let active = false;
  46.   let overlay = null;
  47.   let sessionId = Date.now();  // Unique session ID
  48.  
  49.   // ------- Main gallery extraction logic -------
  50.   function isVisible(el) {
  51.     return !!(el && (el.offsetWidth || el.offsetHeight || el.getClientRects().length));
  52.   }
  53.  
  54.   function getCarousel() {
  55.     return document.querySelector('.border-2');
  56.   }
  57.  
  58.   function getClickableNextBtn(carousel) {
  59.     if(!carousel) return null;
  60.     let btns = Array.from(carousel.querySelectorAll('div.absolute.right-3,div[class*=right-3],div[style*=right]'));
  61.     for(let btn of btns) {
  62.       if (btn.querySelector('svg') && isVisible(btn) && typeof btn.click === 'function') return btn;
  63.     }
  64.     let backup = carousel.querySelector('div.absolute.right-3') || carousel.querySelector('div[class*=right-3]');
  65.     if(backup && typeof backup.click === 'function') return backup;
  66.     let svg = carousel.querySelector('svg[data-testid="ArrowForwardIosIcon"],svg[data-testid="KeyboardArrowDownIcon"]');
  67.     if(svg) {
  68.       let parentDiv = svg.closest('div');
  69.       if(parentDiv && typeof parentDiv.click === 'function' && isVisible(parentDiv)) return parentDiv;
  70.     }
  71.     return null;
  72.   }
  73.  
  74.   function collectMedia(carousel, seenSrc, collected) {
  75.     const img = carousel.querySelector('img[src]:not([aria-hidden="true"])');
  76.     const vid = carousel.querySelector('video, source');
  77.    
  78.     if (img && img.src && !seenSrc.has(img.src)) {
  79.       seenSrc.add(img.src);
  80.      
  81.       // Get large URL if available
  82.       let largeUrl = img.src;
  83.       try {
  84.         const linkParent = img.closest('a');
  85.         if (linkParent && linkParent.href && linkParent.href !== img.src) {
  86.           largeUrl = linkParent.href;
  87.         }
  88.       } catch(e) {
  89.         console.error(`[${SCRIPT_NAME}] Error getting large URL:`, e);
  90.       }
  91.      
  92.       collected.push({
  93.         type: 'img',
  94.         src: img.src,
  95.         large: largeUrl
  96.       });
  97.       return true; // New media collected
  98.     }
  99.    
  100.     if (vid && vid.src && !seenSrc.has(vid.src)) {
  101.       seenSrc.add(vid.src);
  102.       collected.push({
  103.         type: 'video',
  104.         src: vid.src,
  105.         large: vid.src  // Videos use same URL for both
  106.       });
  107.       return true; // New media collected
  108.     }
  109.     return false; // No new media collected
  110.   }
  111.  
  112.   function extractMediaAndShowGallery() {
  113.     console.log(`\n\n=== ${SCRIPT_NAME} v1.4 ===`);
  114.     console.log(`Session ID: ${sessionId} | Started at: ${new Date().toLocaleTimeString()}`);
  115.     console.log('Usage: Click floating button to extract media. Click thumbnails to view, click preview to toggle quality.');
  116.    
  117.     const carousel = getCarousel();
  118.     if (!carousel) {
  119.       console.error('[Gallery] Carousel not found!');
  120.       alert('Carousel not found!');
  121.       return;
  122.     }
  123.  
  124.     const seenSrc = new Set();
  125.     const collected = [];
  126.     let cycleCount = 0;
  127.     let consecutiveNoMediaCount = 0;
  128.     const maxCycles = 100;
  129.     const THROTTLE_DELAY = 1200;
  130.  
  131.     function hasNext() {
  132.       const nextBtn = getClickableNextBtn(carousel);
  133.       return nextBtn && isVisible(nextBtn) && !nextBtn.disabled;
  134.     }
  135.    
  136.     function clickNext(cb) {
  137.       const nextBtn = getClickableNextBtn(carousel);
  138.       if(!nextBtn) {
  139.         cb();
  140.         return;
  141.       }
  142.       nextBtn.click();
  143.       setTimeout(cb, THROTTLE_DELAY);
  144.     }
  145.  
  146.     function cycleCarousel() {
  147.       if (!active) return;
  148.      
  149.       // Collect media and track if new media was found
  150.       const newMediaFound = collectMedia(carousel, seenSrc, collected);
  151.       cycleCount++;
  152.      
  153.       if (newMediaFound) {
  154.         console.log(`[Carousel] Collected ${collected.length} items so far`);
  155.         consecutiveNoMediaCount = 0; // Reset no-media counter
  156.       } else {
  157.         consecutiveNoMediaCount++;
  158.         console.log(`[Carousel] No new media (${consecutiveNoMediaCount}/${MAX_CONSECUTIVE_NO_MEDIA})`);
  159.       }
  160.      
  161.       // Check early termination conditions
  162.       if (consecutiveNoMediaCount >= MAX_CONSECUTIVE_NO_MEDIA) {
  163.         console.log(`[Carousel] Early termination - ${MAX_CONSECUTIVE_NO_MEDIA} cycles with no new media`);
  164.         console.log(`[Gallery] Collected ${collected.length} media items`);
  165.         showGallery(collected);
  166.         return;
  167.       }
  168.      
  169.       if (cycleCount >= maxCycles) {
  170.         console.log(`[Carousel] Cycle limit reached (${maxCycles})`);
  171.         console.log(`[Gallery] Collected ${collected.length} media items`);
  172.         showGallery(collected);
  173.         return;
  174.       }
  175.      
  176.       // Check if we should continue cycling
  177.       const nextBtn = getClickableNextBtn(carousel);
  178.       const nextBtnVisible = nextBtn && isVisible(nextBtn) && !nextBtn.disabled;
  179.      
  180.       if (nextBtnVisible) {
  181.         console.log(`[Carousel] Cycling to next item (cycle ${cycleCount})`);
  182.         clickNext(() => cycleCarousel());
  183.       } else {
  184.         console.log(`[Carousel] Reached end of carousel after ${cycleCount} cycles`);
  185.         console.log(`[Gallery] Collected ${collected.length} media items`);
  186.         showGallery(collected);
  187.       }
  188.     }
  189.    
  190.     console.log('[Extraction] Starting media collection...');
  191.     cycleCarousel();
  192.   }
  193.  
  194.   // ----- Modal Gallery -----
  195.   function showGallery(collected) {
  196.     if (!active) return;
  197.     if (!collected.length) {
  198.       console.error('[Gallery] No media items found');
  199.       alert('No images or videos found');
  200.       return;
  201.     }
  202.  
  203.     console.log(`[Gallery] Showing ${collected.length} items`);
  204.     removeOverlay();
  205.  
  206.     overlay = document.createElement('div');
  207.     overlay.setAttribute('id', 'sgm-modal-gallery-overlay');
  208.     overlay.style = `
  209.       position:fixed;z-index:99998;
  210.       top:0;left:0;width:100vw;height:100vh;
  211.       background:rgba(34,37,54,0.93);
  212.       display:flex;flex-direction:column;align-items:center;justify-content:center;
  213.       animation:sgm-fadein .3s;
  214.     `;
  215.     overlay.innerHTML = `
  216.       <style>
  217.         @keyframes sgm-fadein { from { opacity:0 } to { opacity:1 } }
  218.         .sgm-gallery-close { position:absolute;top:10px;right:30px;font-size:2.2em;background:#fff;border:none;cursor:pointer;z-index:2;border-radius:50%;width:48px;height:48px;line-height:48px;text-align:center;transition:box-shadow .2s; }
  219.         .sgm-gallery-close:hover { box-shadow:0 0 10px #1976D277 }
  220.         .sgm-gallery-grid { display:flex;gap:18px;max-width:80vw;max-height:60vh;overflow:auto;flex-wrap:wrap;justify-content:center; }
  221.         .sgm-gallery-thumb { box-sizing:border-box;max-width:124px;max-height:124px;cursor:pointer;border:2.5px solid #fff;border-radius:7px;transition:box-shadow .2s; background:#000; }
  222.         .sgm-gallery-thumb:hover { box-shadow:0 0 10px #1976D2bb }
  223.         .sgm-gallery-viewer { margin-top:28px;text-align:center; }
  224.         .sgm-gallery-viewer img, .sgm-gallery-viewer video { max-width:80vw;max-height:60vh;box-shadow:0 0 12px #222c; border-radius:12px; background:#000; cursor: pointer; }
  225.       </style>
  226.       <button class="sgm-gallery-close" title="Close Gallery" aria-label="Close Gallery">&times;</button>
  227.       <div class="sgm-gallery-grid"></div>
  228.       <div class="sgm-gallery-viewer"></div>
  229.     `;
  230.  
  231.     document.body.appendChild(overlay);
  232.     overlay.querySelector('.sgm-gallery-close').onclick = removeOverlay;
  233.  
  234.     const grid = overlay.querySelector('.sgm-gallery-grid');
  235.     const viewer = overlay.querySelector('.sgm-gallery-viewer');
  236.  
  237.     collected.forEach((media, index) => {
  238.       let thumb;
  239.       if (media.type === 'img') {
  240.         thumb = document.createElement('img');
  241.         thumb.src = media.src;
  242.         thumb.className = 'sgm-gallery-thumb';
  243.         thumb.alt = "Image preview";
  244.       } else {
  245.         thumb = document.createElement('video');
  246.         thumb.src = media.src;
  247.         thumb.className = 'sgm-gallery-thumb';
  248.         thumb.controls = true;
  249.       }
  250.      
  251.       thumb.onclick = () => {
  252.         viewer.innerHTML = '';
  253.         if (media.type === 'img') {
  254.           const big = document.createElement('img');
  255.           big.src = media.src;
  256.           big.alt = "Large Image";
  257.           big.classList.add('sgm-preview-image');
  258.          
  259.           // Toggle quality on click
  260.           big.addEventListener('click', (e) => {
  261.             e.stopPropagation();
  262.             if (big.src === media.src) {
  263.               big.src = media.large;
  264.               console.log(`[Preview] Loading HQ version: ${media.large}`);
  265.             } else {
  266.               big.src = media.src;
  267.               console.log(`[Preview] Reverting to preview version`);
  268.             }
  269.           });
  270.          
  271.           viewer.appendChild(big);
  272.         } else {
  273.           const big = document.createElement('video');
  274.           big.src = media.src;
  275.           big.controls = true;
  276.           big.autoplay = true;
  277.           viewer.appendChild(big);
  278.         }
  279.       };
  280.      
  281.       grid.appendChild(thumb);
  282.     });
  283.   }
  284.  
  285.   function removeOverlay() {
  286.     if (overlay && overlay.parentNode) {
  287.       overlay.parentNode.removeChild(overlay);
  288.       overlay = null;
  289.       console.log('[Gallery] Closed media viewer');
  290.     }
  291.   }
  292.  
  293.   // ------ Floating Toggle Button ------
  294.   function updateButtonState(btn, isActive) {
  295.     btn.style.cssText = BUTTON_COMMON_STYLE + (isActive ? BUTTON_ACTIVE_STYLE : BUTTON_INACTIVE_STYLE);
  296.     btn.textContent = BUTTON_LABEL;
  297.     btn.title = isActive ? "Click to turn OFF gallery" : "Click to turn ON gallery";
  298.     btn.setAttribute('aria-pressed', isActive ? 'true' : 'false');
  299.   }
  300.  
  301.   function createToggleButton() {
  302.     const btn = document.createElement('button');
  303.     btn.type = 'button';
  304.     btn.id = 'sgm-floating-media-toggle-btn';
  305.     updateButtonState(btn, active);
  306.    
  307.     btn.onclick = () => {
  308.       active = !active;
  309.       updateButtonState(btn, active);
  310.      
  311.       if (active) {
  312.         console.log(`\n[${SCRIPT_NAME}] ACTIVATED | v1.4`);
  313.         sessionId = Date.now(); // Reset session ID
  314.         removeOverlay();
  315.         extractMediaAndShowGallery();
  316.       } else {
  317.         console.log(`[${SCRIPT_NAME}] DEACTIVATED`);
  318.         removeOverlay();
  319.       }
  320.     };
  321.    
  322.     document.body.appendChild(btn);
  323.   }
  324.  
  325.   // Cleanup
  326.   window.addEventListener('beforeunload', () => {
  327.     active = false;
  328.     removeOverlay();
  329.     const btn = document.getElementById('sgm-floating-media-toggle-btn');
  330.     if (btn) btn.remove();
  331.   });
  332.  
  333.   // Initialize
  334.   console.log(`${SCRIPT_NAME} v1.4 loaded. Waiting for activation...`);
  335.   createToggleButton();
  336.  
  337. })();
  338.  
Advertisement
Add Comment
Please, Sign In to add comment