ImageSnatcher

Quickly download images by hovering over them and pressing the S key.

  1. // ==UserScript==
  2. // @name ImageSnatcher
  3. // @name:es ImageSnatcher
  4. // @version 1.2.3
  5. // @description Quickly download images by hovering over them and pressing the S key.
  6. // @description:es Descarga imágenes rápidamente pasando el cursor sobre ellas y presionando la tecla S.
  7. // @author Adam Jensen
  8. // @match *://*/*
  9. // @grant GM_download
  10. // @license MIT
  11. // @namespace http://tampermonkey.net/
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. const validExtensions = /\.(jpe?g|png|gif|webp|bmp|svg|ico|tiff?|avif|jxl|heic|heif|dds|apng|pjpeg|pjpg|webm)$/i;
  18. const preprocessedImages = []; // Array to store the last 10 preprocessed images
  19. const maxHistory = 10; // Maximum number of images to keep in the history
  20. const downloadQueue = []; // Queue of images to be downloaded
  21. let isDownloading = false; // Flag to track the download state
  22.  
  23. function preprocessImage(target) {
  24. if (!target) return;
  25.  
  26. let imageUrl = null;
  27. let imageTitle = '';
  28.  
  29. if (target.tagName.toLowerCase() === 'img') {
  30. imageUrl = target.src;
  31. imageTitle = target.alt.trim() || '';
  32. } else if (target.style.backgroundImage) {
  33. imageUrl = target.style.backgroundImage.replace(/url\(["']?([^"']+)["']?\)/, '$1');
  34. const parentAnchor = target.closest('a');
  35. if (parentAnchor && parentAnchor.href) {
  36. imageTitle = parentAnchor.href.split('/').pop().split('?')[0];
  37. }
  38. }
  39.  
  40. if (!imageTitle) {
  41. const parentAnchor = target.closest('a');
  42. if (parentAnchor && parentAnchor.href) {
  43. imageTitle = parentAnchor.href.split('/').pop().split('?')[0];
  44. }
  45. }
  46.  
  47. if (!imageTitle) {
  48. imageTitle = imageUrl ? imageUrl.split('/').pop().split('?')[0] : 'unknown_image';
  49. }
  50.  
  51. imageTitle = imageTitle.replace(/[\/:*?"<>|]/g, '_'); // Make filename safe
  52.  
  53. // Ensure the title has a valid extension
  54. if (!validExtensions.test(imageTitle)) {
  55. const extensionMatch = imageUrl ? imageUrl.match(validExtensions) : null;
  56. const extension = extensionMatch ? extensionMatch[0] : '.jpg'; // Default to .jpg
  57. imageTitle += extension;
  58. }
  59.  
  60. // Check if the image is already in the history
  61. const existingImage = preprocessedImages.find(img => img.url === imageUrl);
  62. if (!existingImage) {
  63. // Add the new image to the history
  64. preprocessedImages.push({ url: imageUrl, title: imageTitle });
  65. if (preprocessedImages.length > maxHistory) {
  66. preprocessedImages.shift(); // Remove the oldest image if history exceeds the limit
  67. }
  68. console.log(`Preprocessed image: URL=${imageUrl}, Title=${imageTitle}`);
  69. }
  70. }
  71.  
  72. // Detect hovered image and preprocess it
  73. document.addEventListener('mousemove', function (event) {
  74. if (event.target.tagName.toLowerCase() === 'img' || event.target.style.backgroundImage) {
  75. preprocessImage(event.target);
  76. }
  77. });
  78.  
  79. // Change cursor to "loading" during the download
  80. function setCursorLoading(isLoading) {
  81. if (isLoading) {
  82. document.body.style.cursor = 'progress'; // Show loading cursor
  83. } else {
  84. document.body.style.cursor = ''; // Restore normal cursor
  85. }
  86. }
  87.  
  88. // Start downloading the images in the queue
  89. function processDownloadQueue() {
  90. if (isDownloading || downloadQueue.length === 0) return; // Don't start a new download if one is already in progress or queue is empty
  91.  
  92. const nextImage = downloadQueue.shift(); // Get the next image in the queue
  93. isDownloading = true; // Set the flag that download is in progress
  94. setCursorLoading(true); // Change cursor to "loading"
  95.  
  96. GM_download({
  97. url: nextImage.url,
  98. name: nextImage.title,
  99. onerror: function (err) {
  100. console.error('Failed to download the image:', err);
  101. isDownloading = false; // Reset the download flag
  102. setCursorLoading(false); // Restore cursor
  103. processDownloadQueue(); // Continue with the next image in the queue
  104. },
  105. onload: function () {
  106. console.log(`Downloaded image: ${nextImage.title}`);
  107. isDownloading = false; // Reset the download flag
  108. setCursorLoading(false); // Restore cursor
  109. processDownloadQueue(); // Continue with the next image in the queue
  110. }
  111. });
  112. }
  113.  
  114. // Handle key press for adding the most recent preprocessed image to the download queue
  115. document.addEventListener('keydown', function (event) {
  116. // Ignore the keypress if the target is an input or textarea
  117. const activeElement = document.activeElement;
  118. const isInputField = activeElement.tagName.toLowerCase() === 'input' ||
  119. activeElement.tagName.toLowerCase() === 'textarea' ||
  120. activeElement.isContentEditable;
  121.  
  122. if (isInputField || isDownloading) return; // Prevent adding to queue if already downloading
  123.  
  124. if (event.key.toLowerCase() === 's' && preprocessedImages.length > 0) {
  125. const latestImage = preprocessedImages[preprocessedImages.length - 1];
  126. downloadQueue.push(latestImage); // Add the image to the queue
  127. console.log(`Added image to queue: ${latestImage.title}`);
  128. processDownloadQueue(); // Start processing the queue if not already in progress
  129. }
  130. });
  131. })();