RedGifs Improver

Adds buttons for direct links and downloading to the sidebar.

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         RedGifs Improver
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Adds buttons for direct links and downloading to the sidebar.
// @license MIT
// @match        https://www.redgifs.com/*
// @connect      media.redgifs.com
// @connect      v3.redgifs.com
// @grant        GM_download
// @grant        GM_openInTab
// @grant        GM_xmlhttpRequest
// @run-at       document-idle
// ==/UserScript==


(function() {
    'use strict';

    // Global download queue for rate-limiting bulk downloads
    const downloadQueue = [];
    let isProcessingQueue = false;
    const DOWNLOAD_DELAY_MS = 5000;

    function processDownloadQueue() {
        if (isProcessingQueue) return;
        if (downloadQueue.length === 0) return;

        isProcessingQueue = true;
        const { mp4Url, filename } = downloadQueue.shift();

        triggerAutoDownload(mp4Url, filename, () => {
            setTimeout(() => {
                isProcessingQueue = false;
                processDownloadQueue();
            }, DOWNLOAD_DELAY_MS);
        });
    }

    function enqueueDownload(mp4Url, filename) {
        if (!downloadQueue.some(item => item.mp4Url === mp4Url)) {
            downloadQueue.push({ mp4Url, filename });
            processDownloadQueue();
        }
    }

    function allowRightClick(element) {
        if (!element) return;
        ['contextmenu', 'mousedown'].forEach(eventType => {
            element.addEventListener(eventType, function(e) {
                if (e.target.closest('.sideBar, .GifPreview-SideBarWrap, .custom-sidebar-item, .custom-tile-overlay-container')) {
                    return;
                }
                if (e.type === 'contextmenu' || e.button === 2) {
                    e.stopPropagation();
                    e.stopImmediatePropagation();
                }
            }, true);
        });
    }

    function triggerAutoDownload(mp4Url, filename, onComplete) {
        if (typeof GM_download === 'function') {
            GM_download({
                url: mp4Url,
                name: filename,
                saveAs: false,
                onload: () => {
                    if (typeof onComplete === 'function') onComplete();
                },
                onerror: (err) => {
                    console.warn('GM_download failed, attempting blob fallback:', err);
                    fallbackBlobDownload(mp4Url, filename, onComplete);
                }
            });
        } else {
            fallbackBlobDownload(mp4Url, filename, onComplete);
        }
    }

    function fallbackBlobDownload(mp4Url, filename, onComplete) {
        if (typeof GM_xmlhttpRequest === 'function') {
            GM_xmlhttpRequest({
                method: 'GET',
                url: mp4Url,
                responseType: 'blob',
                onload: function(response) {
                    if (response.status === 200) {
                        const blob = response.response;
                        const blobUrl = URL.createObjectURL(blob);
                        const a = document.createElement('a');
                        a.href = blobUrl;
                        a.download = filename;
                        a.style.display = 'none';
                        document.body.appendChild(a);
                        a.click();
                        setTimeout(() => {
                            document.body.removeChild(a);
                            URL.revokeObjectURL(blobUrl);
                            if (typeof onComplete === 'function') onComplete();
                        }, 1000);
                    } else {
                        if (typeof GM_openInTab === 'function') {
                            GM_openInTab(mp4Url, { active: true, insert: true });
                        }
                        if (typeof onComplete === 'function') onComplete();
                    }
                },
                onerror: () => {
                    if (typeof GM_openInTab === 'function') {
                        GM_openInTab(mp4Url, { active: true, insert: true });
                    }
                    if (typeof onComplete === 'function') onComplete();
                }
            });
        } else {
            if (typeof GM_openInTab === 'function') {
                GM_openInTab(mp4Url, { active: true, insert: true });
            }
            if (typeof onComplete === 'function') onComplete();
        }
    }

    function extractCdnUrl(tile) {
        const img = tile.querySelector('img.thumbnail, img[src*="redgifs.com"]');
        if (img && img.src) {
            const match = img.src.match(/\/([A-Za-z0-9]+)-(?:mobile|large|poster)/i);
            if (match && match[1]) {
                return `https://media.redgifs.com/${match[1]}.mp4`;
            }
        }

        let id = tile.getAttribute('data-feed-item-id') ||
                 tile.getAttribute('data-id') ||
                 tile.dataset?.feedItemId ||
                 tile.dataset?.id;

        if (id) {
            const formatted = id.charAt(0).toUpperCase() + id.slice(1);
            return `https://media.redgifs.com/${formatted}.mp4`;
        }

        return null;
    }

    function extractWatchId(tile) {
        let id = tile.getAttribute('data-feed-item-id') ||
                 tile.getAttribute('data-id') ||
                 tile.dataset?.feedItemId ||
                 tile.dataset?.id;
        if (id) return id;

        const img = tile.querySelector('img.thumbnail, img[src*="redgifs.com"]');
        if (img && img.src) {
            const match = img.src.match(/\/([A-Za-z0-9]+)-(?:mobile|large|poster)/i);
            if (match && match[1]) return match[1];
        }

        return null;
    }

    function getTileTimestamp(tile) {
        const img = tile.querySelector('img.thumbnail, img[alt*="uploaded by"]');
        if (!img || !img.alt) return 0;

        const match = img.alt.match(/(\d{2})\/(\d{2})\/(\d{4}),?\s*(\d{2}):(\d{2}):(\d{2})/);
        if (!match) return 0;

        const [, day, month, year, hours, minutes, seconds] = match;
        return new Date(`${year}-${month}-${day}T${hours}:${minutes}:${seconds}`).getTime() || 0;
    }

    function sortTilesByNewest() {
        const tiles = Array.from(document.querySelectorAll('.tileItem, .GifPreview'));
        if (tiles.length < 2) return;

        const parent = tiles[0].parentElement;
        if (!parent) return;

        let needsSort = false;
        const sortedTiles = [...tiles].sort((a, b) => {
            const timeA = getTileTimestamp(a);
            const timeB = getTileTimestamp(b);
            return timeB - timeA;
        });

        for (let i = 0; i < tiles.length; i++) {
            if (tiles[i] !== sortedTiles[i]) {
                needsSort = true;
                break;
            }
        }

        if (needsSort) {
            sortedTiles.forEach(tile => parent.appendChild(tile));
        }
    }

    function processTile(tile) {
        const id = extractWatchId(tile);
        if (!id) return;

        const mp4Url = extractCdnUrl(tile);
        const watchUrl = `https://www.redgifs.com/watch/${id.toLowerCase()}`;

        allowRightClick(tile);

        let overlay = tile.querySelector('.custom-tile-overlay-container');

        if (tile.classList.contains('tileItem') && !tile.querySelector('.sideBar') && !tile.querySelector('.GifPreview-SideBarWrap')) {
            if (!overlay) {
                overlay = document.createElement('div');
                overlay.className = 'custom-tile-overlay-container';
                overlay.style.cssText = 'position: absolute !important; top: 8px !important; right: 8px !important; z-index: 10 !important; display: flex !important; gap: 4px !important; pointer-events: auto !important;';

                if (getComputedStyle(tile).position === 'static') {
                    tile.style.position = 'relative';
                }
                tile.appendChild(overlay);
            } else {
                // Ensure existing container is explicitly positioned at top right
                overlay.style.cssText = 'position: absolute !important; top: 8px !important; right: 8px !important; z-index: 10 !important; display: flex !important; gap: 4px !important; pointer-events: auto !important;';
            }

            if (!overlay.querySelector('.custom-tile-link-btn')) {
                const linkAnchor = document.createElement('a');
                linkAnchor.href = watchUrl;
                linkAnchor.className = 'custom-tile-btn custom-tile-link-btn';
                linkAnchor.title = `Direct link to watch/${id}`;
                linkAnchor.style.cssText = `
                    display: flex !important;
                    align-items: center !important;
                    justify-content: center !important;
                    width: 32px !important;
                    height: 32px !important;
                    background: rgba(235, 250, 99, 0.2) !important;
                    border: 1px solid #EBFA63 !important;
                    border-radius: 50% !important;
                    cursor: pointer !important;
                    box-sizing: border-box !important;
                `;
                linkAnchor.innerHTML = `
                    <svg width="16" height="16" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                        <path d="M12.083 4.16666H12.5C15.7217 4.16666 18.333 6.77801 18.333 9.99999C18.333 13.222 15.7217 15.8333 12.5 15.8333H12.083M7.91634 4.16666H7.49967C4.27801 4.16666 1.66634 6.77801 1.66634 9.99999C1.66634 13.222 4.27801 15.8333 7.49967 15.8333H7.91634M6.66634 9.99999H13.333" stroke="#EBFA63" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
                    </svg>
                `;
                linkAnchor.addEventListener('click', function(e) {
                    e.preventDefault();
                    e.stopPropagation();
                    if (typeof GM_openInTab === 'function') {
                        GM_openInTab(watchUrl, { active: true, insert: true });
                    } else {
                        window.location.href = watchUrl;
                    }
                }, true);
                overlay.appendChild(linkAnchor);
            }

            if (mp4Url && !overlay.querySelector('.custom-tile-dl-btn')) {
                const dlAnchor = document.createElement('a');
                dlAnchor.href = mp4Url;
                dlAnchor.className = 'custom-tile-btn custom-tile-dl-btn';
                dlAnchor.title = `Queue download for ${id}.mp4`;
                dlAnchor.style.cssText = `
                    display: flex !important;
                    align-items: center !important;
                    justify-content: center !important;
                    width: 32px !important;
                    height: 32px !important;
                    background: rgba(99, 250, 148, 0.2) !important;
                    border: 1px solid #63FA94 !important;
                    border-radius: 50% !important;
                    cursor: pointer !important;
                    box-sizing: border-box !important;
                `;
                dlAnchor.innerHTML = `
                    <svg width="16" height="16" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                        <path d="M10 3.33334V13.3333M10 13.3333L14.1667 9.16667M10 13.3333L5.83334 9.16667M3.33334 16.6667H16.6667" stroke="#63FA94" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
                    </svg>
                `;
                dlAnchor.addEventListener('click', function(e) {
                    e.preventDefault();
                    e.stopPropagation();
                    enqueueDownload(mp4Url, `${id}.mp4`);
                }, true);
                overlay.appendChild(dlAnchor);
            }
        } else {
            let sidebar = tile.querySelector('.sideBar, .GifPreview-SideBarWrap ul');
            if (!sidebar) return;

            if (!sidebar.querySelector('.custom-sidebar-link-item')) {
                const linkLi = document.createElement('li');
                linkLi.className = 'sideBarItem custom-sidebar-item custom-sidebar-link-item';
                linkLi.style.cssText = 'margin-bottom: 4px !important;';

                const linkAnchor = document.createElement('a');
                linkAnchor.href = watchUrl;
                linkAnchor.title = `Direct link to watch/${id}`;
                linkAnchor.style.cssText = `
                    display: flex !important;
                    align-items: center !important;
                    justify-content: center !important;
                    width: 32px !important;
                    height: 32px !important;
                    background: rgba(235, 250, 99, 0.2) !important;
                    border: 1px solid #EBFA63 !important;
                    border-radius: 50% !important;
                    cursor: pointer !important;
                    box-sizing: border-box !important;
                `;
                linkAnchor.innerHTML = `
                    <svg width="18" height="18" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                        <path d="M12.083 4.16666H12.5C15.7217 4.16666 18.333 6.77801 18.333 9.99999C18.333 13.222 15.7217 15.8333 12.5 15.8333H12.083M7.91634 4.16666H7.49967C4.27801 4.16666 1.66634 6.77801 1.66634 9.99999C1.66634 13.222 4.27801 15.8333 7.49967 15.8333H7.91634M6.66634 9.99999H13.333" stroke="#EBFA63" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
                    </svg>
                `;

                linkAnchor.addEventListener('click', function(e) {
                    e.preventDefault();
                    e.stopPropagation();
                    if (typeof GM_openInTab === 'function') {
                        GM_openInTab(watchUrl, { active: true, insert: true });
                    } else {
                        window.location.href = watchUrl;
                    }
                }, true);

                linkLi.appendChild(linkAnchor);
                sidebar.prepend(linkLi);
            }

            if (mp4Url && !sidebar.querySelector('.custom-sidebar-download-item')) {
                const dlLi = document.createElement('li');
                dlLi.className = 'sideBarItem custom-sidebar-item custom-sidebar-download-item';
                dlLi.style.cssText = 'margin-bottom: 4px !important;';

                const dlAnchor = document.createElement('a');
                dlAnchor.href = mp4Url;
                dlAnchor.title = `Queue download for ${id}.mp4`;
                dlAnchor.style.cssText = `
                    display: flex !important;
                    align-items: center !important;
                    justify-content: center !important;
                    width: 32px !important;
                    height: 32px !important;
                    background: rgba(99, 250, 148, 0.2) !important;
                    border: 1px solid #63FA94 !important;
                    border-radius: 50% !important;
                    cursor: pointer !important;
                    box-sizing: border-box !important;
                `;
                dlAnchor.innerHTML = `
                    <svg width="18" height="18" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                        <path d="M10 3.33334V13.3333M10 13.3333L14.1667 9.16667M10 13.3333L5.83334 9.16667M3.33334 16.6667H16.6667" stroke="#63FA94" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
                    </svg>
                `;

                dlAnchor.addEventListener('click', function(e) {
                    e.preventDefault();
                    e.stopPropagation();
                    enqueueDownload(mp4Url, `${id}.mp4`);
                }, true);

                dlLi.appendChild(dlAnchor);
                sidebar.prepend(dlLi);
            }
        }

        const mediaElements = tile.querySelectorAll('video, img, .Player-Video, .thumbnail');
        mediaElements.forEach(el => allowRightClick(el));
    }

    function scanAll() {
        const targets = document.querySelectorAll('.tileItem, .GifPreview, [class*="GifPreview"]');
        targets.forEach(tile => processTile(tile));

        sortTilesByNewest();
    }

    setInterval(scanAll, 500);
})();