233 lines
No EOL
8.5 KiB
JavaScript
233 lines
No EOL
8.5 KiB
JavaScript
// ==UserScript==
|
||
// @name flickr easy download
|
||
// @version 1.0.5
|
||
// @description download the highest resolution image on flickr with just one click!
|
||
// @author Mjokfox
|
||
// @updateURL https://gitinthebutt.ofafox.com/Mjokfox/flickr_photostream_dl/raw/branch/main/flickrezdl.user.js
|
||
// @license GPLv3
|
||
// @match https://www.flickr.com/*
|
||
// @match https://flickr.com/*
|
||
// @icon https://www.google.com/s2/favicons?sz=64&domain=flickr.com
|
||
// @grant none
|
||
// @inject-into content
|
||
// ==/UserScript==
|
||
|
||
(function() {
|
||
'use strict';
|
||
|
||
// Function to fetch HTML content
|
||
async function fetchHtml(url) {
|
||
try {
|
||
const response = await fetch(url);
|
||
return await response.text();
|
||
} catch (error) {
|
||
console.error(`Error fetching the HTML page for ${url}: ${error}`);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// Function to find the image URL from HTML content
|
||
function findImageUrl(html) {
|
||
const parser = new DOMParser();
|
||
const doc = parser.parseFromString(html, 'text/html');
|
||
const element = doc.querySelector('div#allsizes-photo img'); // child of div.allsizes-photo with type <img>
|
||
return element ? element.src : null;
|
||
}
|
||
|
||
// Function to find the largest resolution image URL
|
||
async function findLargestResolution(html) {
|
||
const parser = new DOMParser();
|
||
const doc = parser.parseFromString(html, 'text/html');
|
||
const smallElements = doc.querySelectorAll('small');
|
||
let maxResolution = 0;
|
||
let largesti = 0;
|
||
let maxHref = null;
|
||
|
||
// instead of doing arithmatic, exploit the standard page layout
|
||
if (smallElements[0].textContent == "(75 × 75)") {
|
||
largesti = smallElements.length - 1;
|
||
} else if (smallElements[1].parentElement.querySelector(':scope > a').innerHTML == "Original"){
|
||
largesti = 1;
|
||
} else {
|
||
largesti = 0;
|
||
}
|
||
|
||
// find the next page url to the largest image
|
||
const parent = smallElements[largesti].parentElement;
|
||
if (parent) {
|
||
const link = parent.querySelector(':scope > a'); // single layer depth
|
||
maxHref = link ? link.href : null; // if there is no <a> element, we are already on the largest size page
|
||
}
|
||
if (smallElements[largesti].textContent == "(All sizes of this photo are available for download under a Creative Commons license)") {
|
||
return maxHref // stupid edge case
|
||
}
|
||
if (maxHref) { // If it's not null, fetch the HTML for the larger image
|
||
html = await fetchHtml(maxHref);
|
||
return findImageUrl(html);
|
||
}
|
||
return findImageUrl(html);
|
||
}
|
||
|
||
// download the image
|
||
function downloadImage(url) {
|
||
fetch(url)
|
||
.then(response => response.blob())
|
||
.then(blob => {
|
||
const burl = window.URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.style.display = 'none';
|
||
a.href = burl;
|
||
a.download = url.split('/').pop();;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
window.URL.revokeObjectURL(url);
|
||
document.body.removeChild(a);
|
||
})
|
||
.catch((error) => console.error(`Failed to download image for: ${url}: ${error}`));
|
||
}
|
||
|
||
// remove everything after /in/ because that can sometimes be in the url
|
||
function stripAfterIn(url) {
|
||
var index = url.indexOf("/in/");
|
||
if (index !== -1) {
|
||
return url.substring(0, index);
|
||
}
|
||
return url;
|
||
}
|
||
|
||
// main function
|
||
async function downloadLargestFlickrImage(element) {
|
||
let pageUrl = "";
|
||
if (element.type != "click"){pageUrl = element.parentElement.parentElement.querySelector('a').href;}
|
||
else{pageUrl = window.location.href;}
|
||
if (!pageUrl){console.error("url not found"); return}
|
||
pageUrl = stripAfterIn(pageUrl);
|
||
|
||
// add "sizes" to the url
|
||
if (!pageUrl.includes("sizes")) {
|
||
pageUrl += pageUrl.endsWith("/") ? "sizes/" : "/sizes/"
|
||
}
|
||
const html = await fetchHtml(pageUrl);
|
||
if (html) {
|
||
const imageUrl = await findLargestResolution(html);
|
||
if (imageUrl) {
|
||
downloadImage(imageUrl);
|
||
} else {
|
||
console.error('Failed to find the image URL.');
|
||
}
|
||
} else {
|
||
console.error('Failed to download the HTML page.');
|
||
}
|
||
}
|
||
|
||
function delay(ms) {
|
||
return new Promise(resolve => setTimeout(resolve, ms));
|
||
}
|
||
|
||
async function downloadAll(buttonelement) {
|
||
buttonelement.disabled = true
|
||
const elements = document.querySelectorAll("a.overlay");
|
||
|
||
for (const element of elements) {
|
||
downloadLargestFlickrImage(element)
|
||
await delay(500 + Math.floor(Math.random() * 500));
|
||
}
|
||
buttonelement.disabled = false
|
||
}
|
||
|
||
// Add a global floating button so it can be added and removed without querying
|
||
function makeButton(text, clickHandler) {
|
||
const button = document.createElement('button');
|
||
button.style.position = 'fixed';
|
||
button.style.bottom = '10px';
|
||
button.style.right = '10px';
|
||
button.style.height = "32px";
|
||
button.style.fontFamily = "Proxima Nova, helvetica neue, helvetica, arial, sans-serif";
|
||
button.style.fontWeight = "600";
|
||
button.style.padding = '0px 20px';
|
||
button.style.zIndex = '1000';
|
||
button.style.backgroundColor = '#1c9be9';
|
||
button.style.color = '#FFF';
|
||
button.style.border = 'none';
|
||
button.style.borderRadius = '5px';
|
||
button.style.fontSize = '16px';
|
||
button.id = "amogus";
|
||
|
||
button.innerHTML = text;
|
||
|
||
button.addEventListener('click', clickHandler);
|
||
|
||
return button;
|
||
}
|
||
const buttonSingle = makeButton('Download Image', downloadLargestFlickrImage);
|
||
buttonSingle.style.cursor = "pointer"
|
||
const buttonAll = makeButton('Download All Images', function() {
|
||
downloadAll(buttonAll);
|
||
});
|
||
|
||
|
||
function addFloatingButton(){
|
||
if (document.getElementById('amogus')){document.body.removeChild(document.getElementById('amogus'));}
|
||
if (document.documentElement.classList.contains('html-photo-page-scrappy-view') || window.location.href.includes("sizes")) {
|
||
document.body.appendChild(buttonSingle);
|
||
} else if (document.documentElement.classList.contains('html-search-photos-unified-page-view')) {
|
||
document.body.appendChild(buttonAll);
|
||
}
|
||
}
|
||
|
||
// in photostream download button
|
||
function addDownloadButton(element) {
|
||
if(element.querySelector('a.engagement-item.download')){return} // skip if it already exists
|
||
const a = document.createElement('a');
|
||
a.className = 'engagement-item download';
|
||
a.title = "Download this photo";
|
||
const i = document.createElement('i');
|
||
i.className = 'ui-icon-download'; // use the page built in icon
|
||
a.appendChild(i);
|
||
|
||
a.addEventListener('click', function() {
|
||
a.style.cursor = "wait"
|
||
a.style.backgroundColor = "#0a0"
|
||
a.style.border = "2px solid white"
|
||
downloadLargestFlickrImage(element).then(() => {
|
||
a.style.cursor = "inherit";
|
||
})
|
||
});
|
||
|
||
element.appendChild(a);
|
||
}
|
||
|
||
// Callback function to execute when mutations are observed
|
||
const observerCallback = function(mutationsList, observer) {
|
||
for (const mutation of mutationsList) {
|
||
if (mutation.type === 'childList') {
|
||
for (const node of mutation.addedNodes) {
|
||
if (node.nodeType === Node.ELEMENT_NODE && node.matches('div.engagement')) {
|
||
addDownloadButton(node);
|
||
}
|
||
// check descendants
|
||
const overlayElements = node.querySelectorAll && node.querySelectorAll('div.engagement');
|
||
if (overlayElements) {overlayElements.forEach(addDownloadButton);}
|
||
}
|
||
}
|
||
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
||
addFloatingButton()
|
||
}
|
||
}
|
||
};
|
||
const style = document.createElement('style');
|
||
style.innerHTML = `
|
||
#amogus:disabled {
|
||
cursor: wait !important;
|
||
opacity: 0.6;
|
||
}
|
||
`;
|
||
document.head.appendChild(style);
|
||
// Create an observer instance linked to the callback function
|
||
const observer = new MutationObserver(observerCallback);
|
||
observer.observe(document.body, { childList: true, subtree: true, attributes: true });
|
||
|
||
// maybe some already exist on load
|
||
document.querySelectorAll('div.engagement').forEach(addDownloadButton);
|
||
|
||
})(); |