Greasy Fork is available in English.

copy images in one click istock

provides two buttons on each image: to copy image URL and to copy image itself in the clipboard

// ==UserScript==
// @name         copy images in one click istock
// @namespace    http://tampermonkey.net/
// @version      1.0.2
// @description  provides two buttons on each image: to copy image URL and to copy image itself in the clipboard
// @author       GreatFireDragon
// @match        https://www.istockphoto.com/search/2/*
// @match        https://www.istockphoto.com/*/search/2/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=istockphoto.com
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==
(function () {
    const CLICKED_CLASS = "clicked"
const EMOJI_DIV_CLASS = "emoji-div"
const COPIED_CLASS = "emoji_copied"
const SMILEY_EMOJI = "😀"
const HEART_EMOJI = "😍"
const CLEAR_BUTTON_ID = "clearTakenImages"
const urlBuffer = []

function loadClickedImages() {
	const clickedImages = localStorage.getItem("clickedImages")
	return clickedImages ? JSON.parse(clickedImages) : []
}

function saveClickedImage(url) {
	const clickedImages = loadClickedImages()
	if (!clickedImages.includes(url)) {
		clickedImages.push(url)
		localStorage.setItem("clickedImages", JSON.stringify(clickedImages))
	}
}

function applyClickedClass() {
	const clickedImages = loadClickedImages()
	const allImages = document.querySelectorAll("div[data-max-width]:has(img)")
	allImages.forEach((imageDiv) => {
		const imgElement = imageDiv.querySelector("img")
		if (imgElement && clickedImages.includes(decodeURIComponent(imgElement.src))) {
			imageDiv.classList.add(CLICKED_CLASS)
		}
	})
}

function handleClick(event) {
	const target = event.target
	if (target && target.classList.contains(EMOJI_DIV_CLASS)) {
		const img = target.closest("div:has(img)").querySelector("img")
		if (img) {
			const imgUrl = decodeURIComponent(img.src)

			if (target.textContent === SMILEY_EMOJI) {
				handleSmileyClick(event, target, imgUrl)
			} else if (target.textContent === HEART_EMOJI) {
				handleHeartClick(target, img)
			}
		}
	}
}

function handleSmileyClick(event, target, imgUrl) {
	const tempInput = document.createElement("input")
	if (event.ctrlKey || event.shiftKey) {
		urlBuffer.push(imgUrl)
		document.body.appendChild(tempInput)
		tempInput.value = urlBuffer.join(" ")
		tempInput.select()
		document.execCommand("copy")
		document.body.removeChild(tempInput)
	} else {
		urlBuffer.length = 0
		document.body.appendChild(tempInput)
		tempInput.value = imgUrl
		tempInput.select()
		document.execCommand("copy")
		document.body.removeChild(tempInput)
	}

	saveClickedImage(imgUrl)
	target.classList.add(CLICKED_CLASS)

	target.classList.add(COPIED_CLASS)
	setTimeout(() => {
		target.classList.remove(COPIED_CLASS)
	}, 500)
}

function handleHeartClick(target, img) {
	const imgUrl = decodeURIComponent(img.src)
	const xhr = new XMLHttpRequest()
	xhr.open("GET", imgUrl, true)
	xhr.responseType = "blob"
	xhr.onload = function () {
		if (xhr.status === 200) {
			const blob = xhr.response
			const reader = new FileReader()
			reader.onloadend = function () {
				const img = new Image()
				img.src = reader.result
				img.onload = function () {
					const canvas = document.createElement("canvas")
					canvas.width = img.width
					canvas.height = img.height
					const ctx = canvas.getContext("2d")
					ctx.drawImage(img, 0, 0)
					canvas.toBlob(function (blob) {
						const item = new ClipboardItem({"image/png": blob})
						navigator.clipboard
							.write([item])
							.then(() => {
								target.classList.add(COPIED_CLASS)
								setTimeout(() => {
									target.classList.remove(COPIED_CLASS)
								}, 500)
							})
							.catch((err) => {
								console.error("Failed to copy image: ", err)
							})

						saveClickedImage(imgUrl)
						target.classList.add(CLICKED_CLASS)
					}, "image/png")
				}
			}
			reader.readAsDataURL(blob)
		}
	}
	xhr.send()
}
function addEmojis() {
	const imagesContainer = document.querySelector("div[data-testid='gallery-items-container']")
	if (imagesContainer) {
		const allImages = imagesContainer.querySelectorAll("div[data-max-width]:has(img)")
		allImages.forEach(function (imageDiv) {
			if (!imageDiv.querySelector(`.${EMOJI_DIV_CLASS}`)) {
				const smileyDiv = document.createElement("div")
				smileyDiv.textContent = SMILEY_EMOJI
				smileyDiv.classList.add(EMOJI_DIV_CLASS)
				smileyDiv.style.cursor = "pointer"

				const heartDiv = document.createElement("div")
				heartDiv.textContent = HEART_EMOJI
				heartDiv.classList.add(EMOJI_DIV_CLASS)
				heartDiv.style.cursor = "pointer"

				imageDiv.appendChild(smileyDiv)
				imageDiv.appendChild(heartDiv)
			}
		})
		imagesContainer.addEventListener("click", handleClick)
	}

	applyClickedClass()
}

function clearClickedImages() {
	localStorage.removeItem("clickedImages")
	const allImages = document.querySelectorAll(`div.${CLICKED_CLASS}`)
	allImages.forEach((imageDiv) => {
		imageDiv.classList.remove(CLICKED_CLASS)
	})
}

function addClearButton() {
	const clearButton = document.createElement("button")
	clearButton.id = CLEAR_BUTTON_ID
	clearButton.textContent = "Clear Taken Images"
	clearButton.addEventListener("click", clearClickedImages)
	// append as first child
	document.body.insertBefore(clearButton, document.body.firstChild)
}

function mutationCallback(mutations) {
	mutations.forEach(function () {
		addEmojis()
	})
}

const observer = new MutationObserver(mutationCallback)
const config = {childList: true, subtree: true}
observer.observe(document.body, config)

addEmojis()
addClearButton()

GM_addStyle(`
.emoji-div {
    position: absolute;
	top: 0;
	right: 0;
	background-color: rgba(0, 0, 0, 0.5);
	opacity: 0.3;
	padding: 5px;
}

.emoji-div:hover {opacity: 1;}

div[data-testid='gallery-mosaic-asset-overlay'] {display: none;}

div:has(.emoji-div) {position: relative;}

div[data-testid='gallery-items-container'] > a,
.affiliate-promo-code-notification-banner {
    display: none;
}

/* div[ng-non-bindable][data-component][data-prerender][data-app][data-locale][data-site][data-federated-component][data-root]  */

@keyframes backgroundColorChange {
    0% {background-color: initial;}
    50% {background-color: #01cd5d;scale: 2;}
    100% {background-color: initial;}
}
.emoji_copied {animation: backgroundColorChange 0.5s ease-in-out infinite;}

.emoji-div:nth-child(3) {right: 31px;}

.clicked:has(.emoji-div) {
    filter: grayscale(100%);
    transition: filter 0.5s;
}

.clicked:has(.emoji-div):hover {
    filter: grayscale(0%);
}
	`)
})()