Size
8.1 KB
Version
1.0.1
Created
Feb 26, 2026
Updated
24 days ago
1// ==UserScript==
2// @name Image URL Scraper with Copy Checkboxes
3// @description Adds checkboxes to images for easy URL copying
4// @version 1.0.1
5// @match https://*.app.base44.com/*
6// @icon https://app.base44.com/logo_v3.png
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('Image URL Scraper extension loaded');
12
13 // Add custom styles for checkboxes
14 TM_addStyle(`
15 .image-copy-wrapper {
16 position: relative;
17 display: inline-block;
18 }
19
20 .image-copy-checkbox-container {
21 position: absolute;
22 top: 5px;
23 left: 5px;
24 z-index: 1000;
25 background: rgba(255, 255, 255, 0.95);
26 border: 2px solid #4CAF50;
27 border-radius: 6px;
28 padding: 6px 10px;
29 display: flex;
30 align-items: center;
31 gap: 6px;
32 box-shadow: 0 2px 8px rgba(0,0,0,0.2);
33 cursor: pointer;
34 transition: all 0.2s ease;
35 }
36
37 .image-copy-checkbox-container:hover {
38 background: rgba(255, 255, 255, 1);
39 transform: scale(1.05);
40 box-shadow: 0 3px 12px rgba(0,0,0,0.3);
41 }
42
43 .image-copy-checkbox {
44 width: 18px;
45 height: 18px;
46 cursor: pointer;
47 accent-color: #4CAF50;
48 }
49
50 .image-copy-label {
51 font-size: 12px;
52 font-weight: 600;
53 color: #333;
54 user-select: none;
55 cursor: pointer;
56 }
57
58 .image-copy-notification {
59 position: fixed;
60 top: 20px;
61 right: 20px;
62 background: #4CAF50;
63 color: white;
64 padding: 12px 20px;
65 border-radius: 8px;
66 box-shadow: 0 4px 12px rgba(0,0,0,0.3);
67 z-index: 10000;
68 font-weight: 600;
69 animation: slideIn 0.3s ease;
70 }
71
72 @keyframes slideIn {
73 from {
74 transform: translateX(400px);
75 opacity: 0;
76 }
77 to {
78 transform: translateX(0);
79 opacity: 1;
80 }
81 }
82 `);
83
84 // Function to show notification
85 function showNotification(message) {
86 const notification = document.createElement('div');
87 notification.className = 'image-copy-notification';
88 notification.textContent = message;
89 document.body.appendChild(notification);
90
91 setTimeout(() => {
92 notification.remove();
93 }, 2000);
94 }
95
96 // Function to copy URL to clipboard
97 async function copyImageUrl(imageUrl, checkbox) {
98 try {
99 await GM.setClipboard(imageUrl);
100 console.log('Copied image URL:', imageUrl);
101 showNotification('✓ Image URL copied!');
102
103 // Keep checkbox checked for visual feedback
104 setTimeout(() => {
105 checkbox.checked = false;
106 }, 1000);
107 } catch (error) {
108 console.error('Failed to copy URL:', error);
109 showNotification('✗ Failed to copy URL');
110 checkbox.checked = false;
111 }
112 }
113
114 // Function to add checkbox to an image
115 function addCheckboxToImage(img) {
116 // Skip if already processed or if it's a tracking pixel
117 if (img.hasAttribute('data-copy-checkbox-added') ||
118 img.width === 0 ||
119 img.height === 0 ||
120 img.width < 20 ||
121 img.height < 20) {
122 return;
123 }
124
125 img.setAttribute('data-copy-checkbox-added', 'true');
126
127 // Get the image URL
128 const imageUrl = img.src;
129 if (!imageUrl || imageUrl === '') {
130 return;
131 }
132
133 // Create wrapper if image doesn't have one
134 const parent = img.parentElement;
135 let wrapper;
136
137 if (parent.classList.contains('image-copy-wrapper')) {
138 wrapper = parent;
139 } else {
140 wrapper = document.createElement('div');
141 wrapper.className = 'image-copy-wrapper';
142
143 // Copy relevant styles from image
144 const imgStyles = window.getComputedStyle(img);
145 if (imgStyles.display === 'block') {
146 wrapper.style.display = 'block';
147 }
148
149 parent.insertBefore(wrapper, img);
150 wrapper.appendChild(img);
151 }
152
153 // Create checkbox container
154 const checkboxContainer = document.createElement('div');
155 checkboxContainer.className = 'image-copy-checkbox-container';
156
157 // Create checkbox
158 const checkbox = document.createElement('input');
159 checkbox.type = 'checkbox';
160 checkbox.className = 'image-copy-checkbox';
161
162 // Create label
163 const label = document.createElement('span');
164 label.className = 'image-copy-label';
165 label.textContent = 'Copy';
166
167 // Add click handler
168 const handleClick = (e) => {
169 e.stopPropagation();
170 if (checkbox.checked) {
171 copyImageUrl(imageUrl, checkbox);
172 }
173 };
174
175 checkbox.addEventListener('change', handleClick);
176 checkboxContainer.addEventListener('click', (e) => {
177 e.stopPropagation();
178 checkbox.checked = !checkbox.checked;
179 if (checkbox.checked) {
180 copyImageUrl(imageUrl, checkbox);
181 }
182 });
183
184 // Assemble elements
185 checkboxContainer.appendChild(checkbox);
186 checkboxContainer.appendChild(label);
187 wrapper.appendChild(checkboxContainer);
188
189 console.log('Added checkbox to image:', imageUrl);
190 }
191
192 // Function to process all images on the page
193 function processAllImages() {
194 const images = document.querySelectorAll('img');
195 console.log(`Found ${images.length} images on the page`);
196
197 images.forEach((img) => {
198 // Wait for image to load before processing
199 if (img.complete) {
200 addCheckboxToImage(img);
201 } else {
202 img.addEventListener('load', () => addCheckboxToImage(img), { once: true });
203 }
204 });
205 }
206
207 // Debounce function to avoid excessive processing
208 function debounce(func, wait) {
209 let timeout;
210 return function executedFunction(...args) {
211 const later = () => {
212 clearTimeout(timeout);
213 func(...args);
214 };
215 clearTimeout(timeout);
216 timeout = setTimeout(later, wait);
217 };
218 }
219
220 // Initialize the extension
221 function init() {
222 console.log('Initializing Image URL Scraper...');
223
224 // Process existing images
225 processAllImages();
226
227 // Watch for new images being added to the page
228 const debouncedProcess = debounce(processAllImages, 500);
229
230 const observer = new MutationObserver((mutations) => {
231 let hasNewImages = false;
232
233 mutations.forEach((mutation) => {
234 mutation.addedNodes.forEach((node) => {
235 if (node.nodeType === 1) { // Element node
236 if (node.tagName === 'IMG') {
237 hasNewImages = true;
238 } else if (node.querySelectorAll) {
239 const imgs = node.querySelectorAll('img');
240 if (imgs.length > 0) {
241 hasNewImages = true;
242 }
243 }
244 }
245 });
246 });
247
248 if (hasNewImages) {
249 debouncedProcess();
250 }
251 });
252
253 observer.observe(document.body, {
254 childList: true,
255 subtree: true
256 });
257
258 console.log('Image URL Scraper initialized successfully');
259 }
260
261 // Start when DOM is ready
262 if (document.readyState === 'loading') {
263 document.addEventListener('DOMContentLoaded', init);
264 } else {
265 init();
266 }
267})();