// ==UserScript==
// @name RedGIFs Download Button
// @namespace http://tampermonkey.net/
// @version 6.0
// @description Adds polished download buttons on RedGIFs watch pages and batch buttons on user profiles. Loading indicator, custom filename, dark mode, accessibility included.
// @author Slagger122
// @match https://www.redgifs.com/watch/*
// @match https://www.redgifs.com/users/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
// Icons (SVG alternatives recommended later)
const ICON_SOUND_ON = '🔊';
const ICON_SOUND_OFF = '🔇';
const ICON_DOWNLOAD = '⬇';
// Utility: Create styled button element
function createButton(text, title, id, disabled = false) {
const btn = document.createElement('button');
btn.id = id;
btn.type = 'button';
btn.disabled = disabled;
btn.setAttribute('aria-label', title);
btn.title = title;
btn.tabIndex = 0;
btn.style.cssText = `
display: flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
background: var(--btn-bg, #e50914);
color: var(--btn-color, white);
font-weight: 600;
border: none;
border-radius: 8px;
font-family: Arial, sans-serif;
font-size: 14px;
cursor: ${disabled ? 'not-allowed' : 'pointer'};
user-select: none;
box-shadow: 0 0 8px rgba(0,0,0,0.3);
transition: background 0.3s ease;
outline-offset: 2px;
`;
btn.addEventListener('mouseenter', () => {
if (!disabled) btn.style.background = 'var(--btn-bg-hover, #b0060f)';
});
btn.addEventListener('mouseleave', () => {
btn.style.background = 'var(--btn-bg, #e50914)';
});
btn.innerHTML = text;
return btn;
}
// Detect dark mode and set CSS variables accordingly
function applyDarkModeStyles() {
const dark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
if (dark) {
document.documentElement.style.setProperty('--btn-bg', '#bb2525');
document.documentElement.style.setProperty('--btn-bg-hover', '#7f1b1b');
document.documentElement.style.setProperty('--btn-color', '#fff');
}
}
// Get video ID from URL path (watch pages)
function getVideoId() {
const m = window.location.pathname.match(/^\/watch\/([^/#?]+)/);
return m ? m[1] : null;
}
// Get meta video URL from og:video tag
function getMetaVideoUrl() {
const meta = document.querySelector('meta[property="og:video"]');
return meta ? meta.content : null;
}
// Test if a URL exists by HEAD request
function testUrl(url, cb) {
fetch(url, { method: 'HEAD' })
.then(res => cb(res.ok ? url : null))
.catch(() => cb(null));
}
// Auto-download helper
function triggerDownload(url, filename) {
const a = document.createElement('a');
a.href = url;
a.download = filename || '';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
// Insert the main page download button
function insertMainDownloadButton(videoId, url, hasAudio) {
if (document.getElementById('redgifs-download-btn')) return;
const fileName = `${videoId}.mp4`;
const icon = hasAudio ? ICON_SOUND_ON : ICON_SOUND_OFF;
const tooltip = hasAudio
? 'Click to download video with audio'
: 'Silent video — audio not available';
const btn = createButton(`${icon} Download MP4`, tooltip, 'redgifs-download-btn', !hasAudio);
btn.style.position = 'fixed';
btn.style.top = '20px';
btn.style.right = '20px';
btn.style.zIndex = 9999;
btn.style.fontSize = '16px';
if (hasAudio) {
btn.addEventListener('click', () => triggerDownload(url, fileName));
}
document.body.appendChild(btn);
}
// Show loading indicator button
function insertLoadingButton() {
if (document.getElementById('redgifs-download-btn')) return;
const btn = createButton('⏳ Checking audio...', 'Loading video info', 'redgifs-download-btn', true);
btn.style.position = 'fixed';
btn.style.top = '20px';
btn.style.right = '20px';
btn.style.zIndex = 9999;
btn.style.fontSize = '16px';
document.body.appendChild(btn);
}
// Main logic for watch page
function handleWatchPage() {
const videoId = getVideoId();
if (!videoId) return;
const originalUrl = getMetaVideoUrl();
if (!originalUrl) {
console.warn('[RedGIFs Downloader] No meta video URL found.');
return;
}
insertLoadingButton();
if (originalUrl.endsWith('-silent.mp4')) {
const modifiedUrl = originalUrl.replace('-silent.mp4', '.mp4');
testUrl(modifiedUrl, (validUrl) => {
const btn = document.getElementById('redgifs-download-btn');
if (btn) btn.remove();
if (validUrl) {
insertMainDownloadButton(videoId, validUrl, true);
console.log(`[RedGIFs Downloader] ✅ Using audio URL: ${validUrl}`);
} else {
insertMainDownloadButton(videoId, originalUrl, false);
console.log(`[RedGIFs Downloader] 🔇 Silent only available: ${originalUrl}`);
}
});
} else {
const btn = document.getElementById('redgifs-download-btn');
if (btn) btn.remove();
insertMainDownloadButton(videoId, originalUrl, true);
console.log(`[RedGIFs Downloader] ✅ Using provided URL: ${originalUrl}`);
}
}
// Batch download buttons for user profiles
function handleUserProfilePage() {
// Container for batch buttons - if needed for "Download All"
// For now, we just add individual download buttons per video thumbnail.
const containerSelector = '.gif-grid .gif-grid-item a[href^="/watch/"]';
const links = document.querySelectorAll(containerSelector);
if (!links.length) return;
// Add a style for small batch buttons
const style = document.createElement('style');
style.textContent = `
.batch-download-btn {
position: absolute;
top: 5px;
right: 5px;
background: rgba(229, 9, 20, 0.85);
border-radius: 4px;
padding: 4px 7px;
font-size: 12px;
color: white;
font-weight: bold;
cursor: pointer;
user-select: none;
z-index: 100;
text-shadow: 0 0 2px black;
transition: background 0.3s ease;
}
.batch-download-btn:hover {
background: rgba(176, 0, 15, 0.85);
}
.gif-grid-item {
position: relative;
}
`;
document.head.appendChild(style);
links.forEach(link => {
const href = link.getAttribute('href');
if (!href) return;
const videoIdMatch = href.match(/\/watch\/([^/#?]+)/);
if (!videoIdMatch) return;
const videoId = videoIdMatch[1];
// Skip if button already added
if (link.parentElement.querySelector('.batch-download-btn')) return;
const btn = document.createElement('div');
btn.className = 'batch-download-btn';
btn.title = 'Download MP4';
btn.textContent = ICON_DOWNLOAD;
btn.addEventListener('click', async (e) => {
e.stopPropagation();
e.preventDefault();
// Fetch the meta tag for this video by fetching its page HTML
try {
const response = await fetch(`https://www.redgifs.com/watch/${videoId}`);
if (!response.ok) throw new Error('Failed to fetch video page');
const text = await response.text();
// Parse meta tag content from the fetched HTML text
const metaMatch = text.match(/<meta property="og:video" content="([^"]+)"/);
if (!metaMatch) throw new Error('Meta video tag not found');
let videoUrl = metaMatch[1];
if (videoUrl.endsWith('-silent.mp4')) {
// try removing -silent for audio version
const audioUrl = videoUrl.replace('-silent.mp4', '.mp4');
// test if audio URL exists by HEAD
const testResp = await fetch(audioUrl, { method: 'HEAD' });
if (testResp.ok) videoUrl = audioUrl;
}
triggerDownload(videoUrl, `${videoId}.mp4`);
} catch (err) {
alert('Download failed: ' + err.message);
console.error('Batch download error:', err);
}
});
// Append the button inside the gif-grid-item parent
link.parentElement.style.position = 'relative';
link.parentElement.appendChild(btn);
});
}
// Entry point
function init() {
applyDarkModeStyles();
if (window.location.pathname.startsWith('/watch/')) {
handleWatchPage();
} else if (window.location.pathname.startsWith('/users/')) {
handleUserProfilePage();
}
}
if (document.readyState === 'loading') {
window.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();