您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds polished download buttons on RedGIFs watch pages and batch buttons on user profiles. Loading indicator, custom filename, dark mode, accessibility included.
// ==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(); } })();