Twitter Media Gallery Slideshow with Timer

Extract images from Twitter media and play slideshow with controls and countdown timer

当前为 2025-07-12 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Twitter Media Gallery Slideshow with Timer
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Extract images from Twitter media and play slideshow with controls and countdown timer
// @author       pshot
// @match        https://twitter.com/*
// @match        https://x.com/*
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    let slideshowOverlay = null;
    let slideshowImages = [];
    let currentIndex = 0;
    let slideshowInterval = null;
    let countdownInterval = null;
    let countdownSeconds = 10;

    // Extract full-size image URLs from Twitter media gallery
    function extractImageUrls() {
        const seen = new Set();
        const images = [];

        const imgElements = document.querySelectorAll('img[src*="pbs.twimg.com/media/"]');

        imgElements.forEach(img => {
            const src = img.src;
            const baseUrl = src.split('?')[0];
            if (!seen.has(baseUrl)) {
                seen.add(baseUrl);
                const fullUrl = baseUrl + '?format=jpg&name=large';
                images.push(fullUrl);
            }
        });

        return images;
    }

    // Create and show slideshow overlay
    function showSlideshow(images) {
        if (images.length === 0) {
            alert('No images found!');
            return;
        }

        if (slideshowOverlay) {
            slideshowOverlay.remove();
            clearInterval(slideshowInterval);
            clearInterval(countdownInterval);
        }

        slideshowImages = images;
        currentIndex = 0;
        countdownSeconds = 10;

        slideshowOverlay = document.createElement('div');
        slideshowOverlay.style.position = 'fixed';
        slideshowOverlay.style.top = '0';
        slideshowOverlay.style.left = '0';
        slideshowOverlay.style.width = '100vw';
        slideshowOverlay.style.height = '100vh';
        slideshowOverlay.style.backgroundColor = 'rgba(0,0,0,0.9)';
        slideshowOverlay.style.display = 'flex';
        slideshowOverlay.style.alignItems = 'center';
        slideshowOverlay.style.justifyContent = 'center';
        slideshowOverlay.style.flexDirection = 'column';
        slideshowOverlay.style.zIndex = '100000';

        // Image element
        const img = document.createElement('img');
        img.src = slideshowImages[currentIndex];
        img.style.maxWidth = '90%';
        img.style.maxHeight = '90%';
        img.style.borderRadius = '10px';
        img.style.boxShadow = '0 0 20px rgba(255, 255, 255, 0.7)';
        slideshowOverlay.appendChild(img);

        // Countdown timer display
        const timerDisplay = document.createElement('div');
        timerDisplay.style.color = 'white';
        timerDisplay.style.fontSize = '18px';
        timerDisplay.style.marginTop = '10px';
        timerDisplay.style.fontFamily = 'Arial, sans-serif';
        timerDisplay.textContent = `Next image in: ${countdownSeconds} seconds`;
        slideshowOverlay.appendChild(timerDisplay);

        // Close button
        const closeBtn = document.createElement('button');
        closeBtn.innerText = '✕';
        closeBtn.style.position = 'fixed';
        closeBtn.style.top = '20px';
        closeBtn.style.right = '20px';
        closeBtn.style.fontSize = '30px';
        closeBtn.style.background = 'transparent';
        closeBtn.style.color = 'white';
        closeBtn.style.border = 'none';
        closeBtn.style.cursor = 'pointer';
        closeBtn.style.zIndex = '100001';
        closeBtn.title = 'Close Slideshow';
        closeBtn.addEventListener('click', () => {
            clearInterval(slideshowInterval);
            clearInterval(countdownInterval);
            slideshowOverlay.remove();
            slideshowOverlay = null;
        });
        slideshowOverlay.appendChild(closeBtn);

        // Navigation functions
        function showImage(index) {
            currentIndex = (index + slideshowImages.length) % slideshowImages.length;
            img.src = slideshowImages[currentIndex];
        }

        function nextImage() {
            showImage(currentIndex + 1);
            countdownSeconds = 10;
            timerDisplay.textContent = `Next image in: ${countdownSeconds} seconds`;
        }

        function prevImage() {
            showImage(currentIndex - 1);
            countdownSeconds = 10;
            timerDisplay.textContent = `Next image in: ${countdownSeconds} seconds`;
        }

        // Keyboard controls
        function keyHandler(e) {
            if (!slideshowOverlay) return;

            if (e.key === 'ArrowRight') {
                nextImage();
                resetInterval();
            } else if (e.key === 'ArrowLeft') {
                prevImage();
                resetInterval();
            } else if (e.key === 'Escape') {
                closeBtn.click();
            }
        }

        document.addEventListener('keydown', keyHandler);

        // Auto-advance every 10 seconds
        function resetInterval() {
            clearInterval(slideshowInterval);
            clearInterval(countdownInterval);
            countdownSeconds = 10;
            timerDisplay.textContent = `Next image in: ${countdownSeconds} seconds`;

            slideshowInterval = setInterval(nextImage, 10000);

            countdownInterval = setInterval(() => {
                countdownSeconds--;
                if (countdownSeconds < 0) countdownSeconds = 0;
                timerDisplay.textContent = `Next image in: ${countdownSeconds} seconds`;
            }, 1000);
        }
        resetInterval();

        document.body.appendChild(slideshowOverlay);
    }

    // Add control button to page
    function addButton() {
        if (document.getElementById('extract-images-btn')) return;

        const btn = document.createElement('button');
        btn.innerText = 'Start Twitter Slideshow';
        btn.id = 'extract-images-btn';
        btn.style.position = 'fixed';
        btn.style.top = '10px';
        btn.style.right = '10px';
        btn.style.zIndex = '9999';
        btn.style.padding = '10px';
        btn.style.background = '#1da1f2';
        btn.style.color = 'white';
        btn.style.border = 'none';
        btn.style.borderRadius = '5px';
        btn.style.cursor = 'pointer';
        btn.title = 'Extract images and start slideshow';

        btn.addEventListener('click', () => {
            const images = extractImageUrls();
            showSlideshow(images);
        });

        document.body.appendChild(btn);
    }

    // Observe DOM to add button dynamically (Twitter SPA)
    const observer = new MutationObserver(() => {
        addButton();
    });
    observer.observe(document.body, { childList: true, subtree: true });

    // Initial call
    addButton();
})();