Image Grabber

grab images from pages that you can't right click on images

目前为 2024-01-01 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Image Grabber
  3. // @namespace https://fxzfun.com/userscripts
  4. // @version 1.0.0
  5. // @description grab images from pages that you can't right click on images
  6. // @author FXZFun
  7. // @match *://*/*
  8. // @icon https://fxzfun.com/favicon.ico
  9. // @grant GM_addStyle
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15. let elements;
  16. let overlay;
  17. let dialog;
  18.  
  19. window.addEventListener('keypress', function(e){
  20. if (e.shiftKey && e.keyCode == 126) {
  21. startGrabber();
  22. }
  23. }, false);
  24.  
  25. function isPointInBBox(x, y, bbox) {
  26. return ((x > bbox.left) &&
  27. (x < bbox.right) &&
  28. (y > bbox.top) &&
  29. (y < bbox.bottom))
  30. }
  31.  
  32. function startGrabber() {
  33. GM_addStyle(`
  34. .fxzfun-ig-highlight { border: 1px solid darkred; }
  35. .fxzfun-ig-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 1000;}
  36.  
  37. dialog {
  38. width: 500px;
  39. margin: auto;
  40. border: none;
  41. border-radius: 12px;
  42. box-shadow: 0 0 5px #21212121;
  43. background-color: #fafafa;
  44. font-family: monospace, 'Roboto', 'Lato', 'Open Sans', sans-serif;
  45. outline: none;
  46. }
  47.  
  48. dialog::backdrop { background: rgba(0, 0, 0, 0.2); }
  49.  
  50. p.title {
  51. text-transform: uppercase;
  52. margin-top: 0;
  53. color: #757575;
  54. }
  55.  
  56. .fxzfun-ig-gallery {
  57. display: flex;
  58. flex-wrap: wrap;
  59. gap: 20px;
  60. }
  61.  
  62. .fxzfun-ig-gallery::empty {
  63. content: "No images found";
  64. }
  65.  
  66. .fxzfun-ig-gallery img {
  67. width: 120px;
  68. height: 120px;
  69. border-radius: 6px;
  70. }
  71.  
  72. .fxzfun-ig-gallery-item {
  73. display: flex;
  74. flex-direction: row;
  75. flex-wrap: wrap;
  76. gap: 20px;
  77. width: 100%;
  78. font-size: 1.2em;
  79. align-items: center;
  80. }
  81.  
  82. .fxzfun-ig-gallery-item p {
  83. overflow: hidden;
  84. width: 300px;
  85. text-overflow: ellipsis;
  86. text-wrap: nowrap;
  87. }
  88.  
  89. .fxzfun-ig-gallery-item div {
  90. flex-basis: 50%;
  91. }
  92.  
  93. .fxzfun-ig-actions a {
  94. color: #757575;
  95. text-decoration: none;
  96. font-size: 16px;
  97. text-transform: uppercase;
  98. }
  99.  
  100. .fxzfun-ig-actions a:hover {
  101. color: #bdbdbd;
  102. }
  103. `);
  104.  
  105. overlay = document.createElement("div");
  106. overlay.classList.add("fxzfun-ig-overlay");
  107. document.body.appendChild(overlay);
  108.  
  109. elements = new Set();
  110.  
  111. document.body.addEventListener("mousemove", mouseMoveListener);
  112.  
  113. overlay.addEventListener("click", e => {
  114. let images = new Set();
  115. elements.forEach(el => {
  116. let style = (el.currentStyle || window.getComputedStyle(el, false));
  117. if (el.tagName === "IMG") images.add(el.src);
  118. if (el.tagName === "SVG") images.add(URL.createObjectURL(new Blob([new XMLSerializer().serializeToString(el)], { type: 'image/svg+xml' })));
  119. if (style.backgroundImage.startsWith('data:image') || style.background.startsWith('data:image')) images.add(style.background);
  120.  
  121. let backgroundImageMatches = style.backgroundImage.match(/url\(["']?(.*?)["']?\)/);
  122. if (backgroundImageMatches?.length > 1) images.add(backgroundImageMatches?.[1]);
  123.  
  124. let backgroundMatches = style.backgroundImage.match(/url\(["']?(.*?)["']?\)/);
  125. if (backgroundMatches?.length > 1) images.add(backgroundMatches?.[1]);
  126. });
  127. showDialog(images);
  128. });
  129. }
  130.  
  131. function mouseMoveListener(e) {
  132. document.querySelectorAll('*').forEach(node => {
  133. if (isPointInBBox(e.x, e.y, node.getBoundingClientRect())) {
  134. if (!node.classList.contains("fxzfun-ig-highlight") && !node.classList.contains("fxzfun-ig-overlay")) {
  135. node.classList.add("fxzfun-ig-highlight");
  136. elements.add(node);
  137. }
  138. } else {
  139. node.classList.remove("fxzfun-ig-highlight");
  140. elements.delete(node);
  141. }
  142. });
  143. }
  144.  
  145. function showDialog(images) {
  146. dialog = dialog ?? document.createElement("dialog");
  147. let imageLinks = Array.from(images);
  148. dialog.innerHTML = `
  149. <p class="title">[/] Image Grabber</p>
  150. <div class="fxzfun-ig-gallery">
  151. ${imageLinks.map((url, index) => !!url && `
  152. <div class="fxzfun-ig-gallery-item" id="item${index}">
  153. <img src="${url}" />
  154. <div>
  155. <p>${url.split("/").at(-1)}</p>
  156. <p class="fxzfun-ig-actions">
  157. <a href="${url}" target="_blank">Open ${NEW_TAB_ICON}</a>
  158. <a href="${url}" target="_blank" download="${url.split("/").at(-1)}">Download ${DOWNLOAD_ICON}</a>
  159. </p>
  160. </div>
  161. </div>
  162. `).join('')}
  163. </div>`;
  164. document.body.appendChild(dialog);
  165. dialog.showModal();
  166.  
  167. dialog.addEventListener('click', (e) => e.target.isEqualNode(dialog) && dialog.close());
  168.  
  169. overlay.remove();
  170. document.querySelectorAll(".fxzfun-ig-highlight").forEach(el => el.classList.remove("fxzfun-ig-highlight"));
  171. document.querySelectorAll(".fxzfun-ig-copyLink").forEach(el => el);
  172. document.body.removeEventListener("mousemove", mouseMoveListener);
  173. }
  174.  
  175. const NEW_TAB_ICON = `<svg width="16" height="16" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M17 17H3V3h5V1H3a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5h-2z"/><path fill="currentColor" d="m11 1l3.3 3.3L8.6 10l1.4 1.4l5.7-5.7L19 9V1z"/></svg>`;
  176. const DOWNLOAD_ICON = `<svg width="16" height="16" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M17 12v5H3v-5H1v5a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5z"/><path fill="currentColor" d="M15 9h-4V1H9v8H5l5 6z"/></svg>`;
  177. const COPY_ICON = `<svg width="16" height="16" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M17 3H9v2H7V3c0-1.1.895-2 2-2h8c1.1 0 2 .895 2 2v8c0 1.1-.895 2-2 2h-2v-2h2z"/><path fill="currentColor" d="M3 9v8h8V9zm8-2c1.1 0 2 .895 2 2v8c0 1.1-.895 2-2 2H3c-1.1 0-2-.895-2-2V9c0-1.1.895-2 2-2z"/></svg>`;
  178. })();