// ==UserScript== // @name Flickr Download Link Plus // @name:zh-CN Flickr下载助手增强版 // @name:zh-TW Flickr下載助手增強版 // @version 4.2 // @description Adds download links to photos with size selection and batch download support for Flickr // @description:zh-CN 为Flickr添加图片下载按钮,支持选择不同尺寸和批量下载功能 // @description:zh-TW 為Flickr添加圖片下載按鈕,支持選擇不同尺寸和批量下載功能 // @author powcai // @namespace cuzi // @license MIT // @grant none // @icon https://combo.staticflickr.com/pw/images/favicons/favicon-228.png // @match https://flickr.com/* // @match https://*.flickr.com/* // @require https://cdn.jsdelivr.net/npm/sweetalert2@11 // @run-at document-end // @noframes // @connect flickr.com // @connect staticflickr.com // @downloadURL none // ==/UserScript== /* jshint asi: true, esversion: 8 */ (function () { 'use strict' // 下载图片函数 const downloadImage = async (imageUrl) => { try { const response = await fetch('https:' + imageUrl); const blob = await response.blob(); const blobUrl = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = blobUrl; link.download = imageUrl.split('/').pop(); document.body.appendChild(link); link.click(); document.body.removeChild(link); setTimeout(() => URL.revokeObjectURL(blobUrl), 100); } catch (error) { console.error('Download error:', error); } }; // 获取最大尺寸的图片URL const getLargestSize = (sizes) => { const availableSizes = Object.entries(sizes) .map(([key, data]) => ({ src: data.src, pixels: data.width * data.height })) .sort((a, b) => b.pixels - a.pixels); return availableSizes[0]?.src; }; const createBatchButton = () => { const buttonId = 'batch-download-button'; if (!document.getElementById(buttonId)) { const batchButton = document.createElement('div'); batchButton.id = buttonId; batchButton.innerHTML = `
⬇️ Download All (Max Size)
`; document.body.appendChild(batchButton); return batchButton; } return document.getElementById(buttonId); }; // 添加下载按钮的通用函数 function addDownloadButtons(containers, linkClass) { containers.forEach(container => { if (!container.querySelector('.download-button')) { const detailLink = container.querySelector(`.${linkClass}`).href; const photoName = container.querySelector(`.${linkClass}`).getAttribute('title') || ''; const downloadButton = document.createElement('div'); downloadButton.className = 'download-button'; downloadButton.innerHTML = ` ⬇️
`; // 点击下载按钮显示尺寸菜单 downloadButton.onclick = async function(e) { e.preventDefault(); e.stopPropagation(); const menu = downloadButton.querySelector('.size-menu'); if (menu.style.display === 'none') { try { const response = await fetch(detailLink); const text = await response.text(); const regex = /"sizes":\s*({[^}]*(?:}[^}]*)*?}),"descendingSizes"/; const match = text.match(regex); if (match) { const sizes = JSON.parse(match[1]); createSizeMenu(sizes, menu); menu.style.display = 'block'; } } catch (error) { console.error('Error:', error); } } else { menu.style.display = 'none'; } }; // 点击其他地方关闭菜单 document.addEventListener('click', (e) => { if (!downloadButton.contains(e.target)) { downloadButton.querySelector('.size-menu').style.display = 'none'; } }); container.style.position = 'relative'; container.appendChild(downloadButton); } }); } // 创建尺寸选择菜单内容 const createSizeMenu = (sizes, menu) => { menu.innerHTML = ''; const sizeNames = { 'o': 'Original', '6k': '6K', '5k': '5K', '4k': '4K', '3k': '3K', 'k': '2K', 'h': 'Large 1600', 'l': 'Large', 'c': 'Medium 800', 'z': 'Medium 640', 'm': 'Medium' }; const availableSizes = Object.entries(sizes) .map(([key, data]) => ({ key, width: data.width, height: data.height, src: data.src, pixels: data.width * data.height })) .sort((a, b) => b.pixels - a.pixels); availableSizes.forEach(size => { const button = document.createElement('div'); button.style.cssText = ` color: white; padding: 5px 10px; cursor: pointer; white-space: nowrap; margin: 2px 0; `; const sizeName = sizeNames[size.key] || `Size ${size.key.toUpperCase()}`; button.textContent = `${sizeName} (${size.width}×${size.height})`; button.onmouseover = () => { button.style.backgroundColor = 'rgba(255,255,255,0.2)'; }; button.onmouseout = () => { button.style.backgroundColor = 'transparent'; }; button.onclick = (e) => { e.stopPropagation(); downloadImage(size.src); menu.style.display = 'none'; }; menu.appendChild(button); }); }; // 处理列表视图 function page_photo_list() { const photoContainers = document.querySelectorAll('.photo-list-photo-container'); if (photoContainers.length === 0) return; const batchButton = createBatchButton('right'); // 批量下载处理 batchButton.onclick = async () => { for (const container of photoContainers) { try { const detailLink = container.querySelector('.overlay').href; const response = await fetch(detailLink); const text = await response.text(); const regex = /"sizes":\s*({[^}]*(?:}[^}]*)*?}),"descendingSizes"/; const match = text.match(regex); if (match) { const sizes = JSON.parse(match[1]); const largestSrc = getLargestSize(sizes); if (largestSrc) { await downloadImage(largestSrc); } } } catch (error) { console.error('Error downloading image:', error); } } }; addDownloadButtons(photoContainers, 'overlay'); } // 处理卡片视图 function card_photo_list() { const photoContainers = document.querySelectorAll('.photo-card-content'); if (photoContainers.length === 0) return; const batchButton = createBatchButton('left'); // 批量下载处理 batchButton.onclick = async () => { for (const container of photoContainers) { try { const detailLink = container.querySelector('.photo-link').href; const response = await fetch(detailLink); const text = await response.text(); const regex = /"sizes":\s*({[^}]*(?:}[^}]*)*?}),"descendingSizes"/; const match = text.match(regex); if (match) { const sizes = JSON.parse(match[1]); const largestSrc = getLargestSize(sizes); if (largestSrc) { await downloadImage(largestSrc); } } } catch (error) { console.error('Error downloading image:', error); } } }; addDownloadButtons(photoContainers, 'photo-link'); } function main() { page_photo_list(); card_photo_list(); } const observer = new MutationObserver((mutations) => { main(); }); observer.observe(document.body, { childList: true, subtree: true }); main(); })();