Adds buttons for direct links and downloading to the sidebar.
// ==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);
})();