RedGifs Improver

Adds buttons for direct links and downloading to the sidebar.

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==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);
})();