DuckDuckGo Image Link Extractor (Iterate Tiles)

Iterates through div.tile--img, clicks, extracts, closes (fixed)

  1. // ==UserScript==
  2. // @name DuckDuckGo Image Link Extractor (Iterate Tiles)
  3. // @namespace yournamespace
  4. // @match https://duckduckgo.com/*images*
  5. // @grant none
  6. // @version 1.7
  7. // @license GNU General Public License v3.0
  8. // @description Iterates through div.tile--img, clicks, extracts, closes (fixed)
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. let isProcessing = false;
  15. let stopProcessing = false;
  16. let processedCount = 0;
  17. let totalCount = 0;
  18.  
  19. async function processImageTiles() {
  20. if (isProcessing) return;
  21. isProcessing = true;
  22. stopProcessing = false;
  23. processedCount = 0;
  24. const imageTiles = document.querySelectorAll('div.tile--img');
  25. totalCount = imageTiles.length;
  26. updateProgress(0, totalCount);
  27. for (const tile of imageTiles) {
  28. if (stopProcessing) break;
  29. const imgElement = tile.querySelector('.tile--img__img');
  30. const dimensions = tile.querySelector('.tile--img__dimensions');
  31. if (imgElement && dimensions) {
  32. // Parse dimensions (format: "2560 × 1677")
  33. const [width, height] = dimensions.textContent.split(' × ').map(Number);
  34. // Calculate total pixels and compare to 1920x1080 (2,073,600 pixels)
  35. const totalPixels = width * height;
  36. if (totalPixels < 2073600) {
  37. processedCount++; // Count as processed
  38. updateProgress(processedCount, totalCount);
  39. continue;
  40. }
  41. try {
  42. imgElement.click();
  43. const detailViewSuccess = await waitForDetailView();
  44. if (detailViewSuccess) {
  45. await extractAndClose();
  46. processedCount++;
  47. updateProgress(processedCount, totalCount);
  48. }
  49. } catch (error) {
  50. console.error('Error processing tile:', error);
  51. // Continue with next tile even if one fails
  52. }
  53. }
  54. await new Promise(resolve => setTimeout(resolve, 500)); // Increased delay between tiles
  55. }
  56.  
  57. isProcessing = false;
  58. showPopup(stopProcessing ? "Extraction stopped" : "Extraction complete");
  59. // Show select all button
  60. const selectAllBtn = document.querySelector('#extractedLinksList + div button');
  61. if (selectAllBtn) {
  62. selectAllBtn.style.display = 'block';
  63. }
  64. }
  65.  
  66. async function waitForDetailView() {
  67. return new Promise(resolve => {
  68. const checkInterval = setInterval(() => {
  69. const detailBtn = document.querySelector('a.c-detail__btn, .detail__media, .detail__content');
  70. if (detailBtn) {
  71. clearInterval(checkInterval);
  72. resolve(true);
  73. }
  74. }, 200);
  75. setTimeout(() => {
  76. clearInterval(checkInterval);
  77. resolve(false);
  78. }, 3000);
  79. });
  80. }
  81.  
  82. async function extractAndClose() {
  83. const linkElement = document.querySelector('a.c-detail__btn');
  84. if (linkElement) {
  85. const href = linkElement.getAttribute('href');
  86. if (href) {
  87. displaySingleLink(href);
  88. }
  89. }
  90. const closeButton = document.querySelector('a.detail__close');
  91. if (closeButton) {
  92. closeButton.click();
  93. await new Promise(resolve => setTimeout(resolve, 500)); // Increased delay after close
  94. // await waitForClose(); //removed waitForClose function
  95. }
  96. }
  97.  
  98. function displaySingleLink(link) {
  99. let linkList = document.getElementById('extractedLinksList');
  100. if (!linkList) {
  101. createLinkLists();
  102. linkList = document.getElementById('extractedLinksList');
  103. }
  104.  
  105. if (!linkList) {
  106. linkList = document.createElement('div');
  107. linkList.id = 'extractedLinksList';
  108. linkList.style.position = 'fixed';
  109. linkList.style.top = '10px';
  110. linkList.style.left = '10px';
  111. linkList.style.backgroundColor = '#f5f5f5';
  112. linkList.style.border = '1px solid #ddd';
  113. linkList.style.padding = '10px';
  114. linkList.style.zIndex = '1000';
  115. linkList.style.maxHeight = '33vh';
  116. linkList.style.overflowY = 'auto';
  117. linkList.style.width = '300px';
  118. linkList.style.borderRadius = '4px';
  119. linkList.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
  120. document.body.appendChild(linkList);
  121. }
  122.  
  123. const ul = document.createElement('ul');
  124. ul.style.listStyle = 'none';
  125. ul.style.margin = '0';
  126. ul.style.padding = '0';
  127. const li = document.createElement('li');
  128. li.style.margin = '5px 0';
  129. li.style.padding = '8px';
  130. li.style.backgroundColor = 'white';
  131. li.style.border = '1px solid #eee';
  132. li.style.borderRadius = '3px';
  133. const a = document.createElement('a');
  134. a.href = link;
  135. a.textContent = link;
  136. a.target = "_blank";
  137. a.style.color = '#0077cc';
  138. a.style.textDecoration = 'none';
  139. a.style.wordBreak = 'break-all';
  140. a.style.display = 'block';
  141. a.addEventListener('mouseover', () => {
  142. a.style.textDecoration = 'underline';
  143. });
  144. a.addEventListener('mouseout', () => {
  145. a.style.textDecoration = 'none';
  146. });
  147.  
  148. li.appendChild(a);
  149. ul.appendChild(li);
  150. linkList.appendChild(ul);
  151. // Update link counter
  152. const linkCounter = document.getElementById('linkCounter');
  153. if (linkCounter) {
  154. const linkCount = linkList.querySelectorAll('ul').length;
  155. linkCounter.textContent = `${linkCount} link${linkCount !== 1 ? 's' : ''}`;
  156. }
  157. }
  158.  
  159. function showPopup(message) {
  160. const popup = document.createElement('div');
  161. popup.textContent = message;
  162. popup.style.position = 'fixed';
  163. popup.style.top = '50%';
  164. popup.style.left = '50%';
  165. popup.style.transform = 'translate(-50%, -50%)';
  166. popup.style.backgroundColor = 'white';
  167. popup.style.border = '1px solid black';
  168. popup.style.padding = '10px';
  169. popup.style.zIndex = '1001';
  170.  
  171. document.body.appendChild(popup);
  172.  
  173. setTimeout(() => {
  174. document.body.removeChild(popup);
  175. }, 2000);
  176. }
  177.  
  178. function createLinkLists() {
  179. const container = document.createElement('div');
  180. container.style.position = 'fixed';
  181. container.style.top = '10px';
  182. container.style.left = '10px';
  183. container.style.zIndex = '1000';
  184. container.style.display = 'flex';
  185. container.style.flexDirection = 'column';
  186. container.style.gap = '10px';
  187.  
  188. // Create select all button
  189. const selectAllBtn = document.createElement('button');
  190. selectAllBtn.textContent = 'Select All';
  191. selectAllBtn.style.position = 'absolute';
  192. selectAllBtn.style.left = '320px'; // Position to the right of the link boxes
  193. selectAllBtn.style.top = '10px'; // Align with top of first box
  194. selectAllBtn.style.zIndex = '1001';
  195. selectAllBtn.style.padding = '4px 8px';
  196. selectAllBtn.style.borderRadius = '3px';
  197. selectAllBtn.style.border = '1px solid #ccc';
  198. selectAllBtn.style.backgroundColor = '#f5f5f5';
  199. selectAllBtn.style.cursor = 'pointer';
  200. selectAllBtn.addEventListener('click', () => {
  201. const linkList = document.getElementById('extractedLinksList');
  202. if (linkList) {
  203. const range = document.createRange();
  204. range.selectNodeContents(linkList);
  205. const selection = window.getSelection();
  206. selection.removeAllRanges();
  207. selection.addRange(range);
  208. }
  209. });
  210.  
  211. // Create good links list
  212. const goodList = document.createElement('div');
  213. goodList.id = 'extractedLinksList';
  214. goodList.style.backgroundColor = '#f5f5f5';
  215. goodList.style.border = '1px solid #ddd';
  216. goodList.style.padding = '10px';
  217. goodList.style.maxHeight = '33vh';
  218. goodList.style.overflowY = 'auto';
  219. goodList.style.width = '300px';
  220. goodList.style.borderRadius = '4px';
  221. goodList.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
  222. goodList.style.position = 'relative'; // Needed for absolute positioning of counter
  223.  
  224. // Create link counter
  225. const linkCounter = document.createElement('div');
  226. linkCounter.id = 'linkCounter';
  227. linkCounter.style.position = 'absolute';
  228. linkCounter.style.bottom = '5px';
  229. linkCounter.style.right = '10px';
  230. linkCounter.style.backgroundColor = 'rgba(255,255,255,0.8)';
  231. linkCounter.style.padding = '2px 6px';
  232. linkCounter.style.borderRadius = '3px';
  233. linkCounter.style.fontSize = '0.8em';
  234. linkCounter.style.color = '#666';
  235. linkCounter.textContent = '0 links';
  236. goodList.appendChild(linkCounter);
  237.  
  238. container.appendChild(goodList);
  239. container.appendChild(selectAllBtn);
  240. document.body.appendChild(container);
  241. }
  242.  
  243.  
  244. function createControlPanel() {
  245. const panel = document.createElement('div');
  246. panel.style.position = 'fixed';
  247. panel.style.top = '10px';
  248. panel.style.right = '10px';
  249. panel.style.zIndex = '1000';
  250. panel.style.display = 'flex';
  251. panel.style.gap = '5px';
  252. panel.style.backgroundColor = 'white';
  253. panel.style.padding = '5px';
  254. panel.style.border = '1px solid #ccc';
  255. panel.style.borderRadius = '4px';
  256.  
  257. const startButton = document.createElement('button');
  258. startButton.textContent = 'Start Extraction';
  259. startButton.addEventListener('click', processImageTiles);
  260.  
  261. const stopButton = document.createElement('button');
  262. stopButton.textContent = 'Stop';
  263. stopButton.style.backgroundColor = '#ff4444';
  264. stopButton.addEventListener('click', () => {
  265. stopProcessing = true;
  266. });
  267.  
  268. const progress = document.createElement('div');
  269. progress.id = 'extractionProgress';
  270. progress.style.marginLeft = '10px';
  271. progress.style.padding = '5px 10px';
  272.  
  273. panel.appendChild(startButton);
  274. panel.appendChild(stopButton);
  275. panel.appendChild(progress);
  276. document.body.appendChild(panel);
  277. }
  278.  
  279. function updateProgress(current, total) {
  280. const progress = document.getElementById('extractionProgress');
  281. if (progress) {
  282. progress.textContent = `${current}/${total} (${Math.round((current/total)*100)}%)`;
  283. progress.style.color = current === total ? 'green' : 'black';
  284. }
  285. }
  286.  
  287. createControlPanel();
  288. })();