Size
12.4 KB
Version
1.1.1
Created
Nov 15, 2025
Updated
28 days ago
1// ==UserScript==
2// @name Saudi Print & Pack Website Scraper
3// @description A new extension
4// @version 1.1.1
5// @match https://*.saudi-pp.com/*
6// @icon https://saudi-pp.com/wp-content/uploads/2023/11/print-icon-180x180.png
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('Saudi Print & Pack Website Scraper loaded');
12
13 // Function to scrape all website URLs from the table
14 async function scrapeWebsites() {
15 console.log('Starting website scraping...');
16
17 const table = document.querySelector('table[data-footable_id="23093"]');
18 if (!table) {
19 console.error('Table not found');
20 return [];
21 }
22
23 const rows = table.querySelectorAll('tbody tr[data-row_id]');
24 console.log(`Found ${rows.length} rows to process`);
25
26 // Limit to first 50 rows
27 const maxRows = Math.min(50, rows.length);
28 console.log(`Scraping first ${maxRows} rows`);
29
30 const websites = [];
31
32 for (let i = 0; i < maxRows; i++) {
33 const row = rows[i];
34
35 // Get company name
36 const companyNameCell = row.querySelector('.ninja_column_1');
37 const companyName = companyNameCell ? companyNameCell.textContent.trim() : 'Unknown';
38
39 // Check if row is already expanded
40 const isExpanded = row.classList.contains('footable-detail-show');
41
42 if (!isExpanded) {
43 // Click to expand the row
44 const toggleButton = row.querySelector('.footable-toggle');
45 if (toggleButton) {
46 toggleButton.click();
47 // Wait for the details to load
48 await new Promise(resolve => setTimeout(resolve, 100));
49 }
50 }
51
52 // Find the details row - it's the next sibling element
53 const detailsRow = row.nextElementSibling;
54
55 if (detailsRow && detailsRow.classList.contains('footable-detail-row')) {
56 // Look for website link in the details
57 const websiteCell = detailsRow.querySelector('.ninja_column_8 a');
58
59 if (websiteCell) {
60 const websiteUrl = websiteCell.textContent.trim();
61 const websiteHref = websiteCell.getAttribute('href');
62
63 websites.push({
64 company: companyName,
65 website: websiteUrl,
66 link: websiteHref
67 });
68
69 // Log progress every 10 rows
70 if ((i + 1) % 10 === 0) {
71 console.log(`Progress: ${i + 1}/${maxRows} rows processed, ${websites.length} websites found`);
72 }
73 } else {
74 // Log when no website is found
75 if ((i + 1) % 10 === 0) {
76 console.log(`Progress: ${i + 1}/${maxRows} rows processed, ${websites.length} websites found`);
77 }
78 }
79 }
80
81 // Collapse the row to keep the page clean
82 if (!isExpanded) {
83 const toggleButton = row.querySelector('.footable-toggle');
84 if (toggleButton) {
85 toggleButton.click();
86 }
87 }
88 }
89
90 console.log(`Scraping complete! Found ${websites.length} websites out of ${maxRows} companies`);
91 return websites;
92 }
93
94 // Function to display results in a modal
95 function displayResults(websites) {
96 // Remove existing modal if any
97 const existingModal = document.getElementById('scraper-modal');
98 if (existingModal) {
99 existingModal.remove();
100 }
101
102 // Create modal
103 const modal = document.createElement('div');
104 modal.id = 'scraper-modal';
105 modal.style.cssText = `
106 position: fixed;
107 top: 50%;
108 left: 50%;
109 transform: translate(-50%, -50%);
110 background: white;
111 color: black;
112 padding: 30px;
113 border-radius: 10px;
114 box-shadow: 0 4px 20px rgba(0,0,0,0.3);
115 z-index: 10000;
116 max-width: 800px;
117 max-height: 80vh;
118 overflow-y: auto;
119 width: 90%;
120 `;
121
122 // Create content
123 let content = `
124 <h2 style="margin-top: 0; color: #333; border-bottom: 2px solid #007bff; padding-bottom: 10px;">
125 Scraped Websites (${websites.length} found)
126 </h2>
127 <div style="margin: 20px 0;">
128 `;
129
130 if (websites.length > 0) {
131 content += '<ul style="list-style: none; padding: 0;">';
132 websites.forEach((item, index) => {
133 content += `
134 <li style="margin: 10px 0; padding: 10px; background: #f8f9fa; border-left: 3px solid #007bff; border-radius: 3px;">
135 <strong style="color: #333;">${index + 1}. ${item.company}</strong><br>
136 <a href="${item.link}" target="_blank" style="color: #007bff; text-decoration: none; word-break: break-all;">
137 ${item.website}
138 </a>
139 </li>
140 `;
141 });
142 content += '</ul>';
143 } else {
144 content += '<p style="color: #666;">No websites found in the table.</p>';
145 }
146
147 content += '</div>';
148
149 // Add buttons
150 content += `
151 <div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;">
152 <button id="copy-websites-btn" style="
153 background: #28a745;
154 color: white;
155 border: none;
156 padding: 10px 20px;
157 border-radius: 5px;
158 cursor: pointer;
159 font-size: 14px;
160 font-weight: bold;
161 ">Copy All URLs</button>
162 <button id="download-csv-btn" style="
163 background: #17a2b8;
164 color: white;
165 border: none;
166 padding: 10px 20px;
167 border-radius: 5px;
168 cursor: pointer;
169 font-size: 14px;
170 font-weight: bold;
171 ">Download CSV</button>
172 <button id="close-modal-btn" style="
173 background: #dc3545;
174 color: white;
175 border: none;
176 padding: 10px 20px;
177 border-radius: 5px;
178 cursor: pointer;
179 font-size: 14px;
180 font-weight: bold;
181 ">Close</button>
182 </div>
183 `;
184
185 modal.innerHTML = content;
186
187 // Create overlay
188 const overlay = document.createElement('div');
189 overlay.id = 'scraper-overlay';
190 overlay.style.cssText = `
191 position: fixed;
192 top: 0;
193 left: 0;
194 width: 100%;
195 height: 100%;
196 background: rgba(0,0,0,0.5);
197 z-index: 9999;
198 `;
199
200 document.body.appendChild(overlay);
201 document.body.appendChild(modal);
202
203 // Add event listeners
204 document.getElementById('close-modal-btn').addEventListener('click', () => {
205 modal.remove();
206 overlay.remove();
207 });
208
209 document.getElementById('copy-websites-btn').addEventListener('click', async () => {
210 const urlList = websites.map(item => item.website).join('\n');
211 try {
212 await GM.setClipboard(urlList);
213 alert('All website URLs copied to clipboard!');
214 } catch (error) {
215 console.error('Failed to copy to clipboard:', error);
216 // Fallback to textarea method
217 const textarea = document.createElement('textarea');
218 textarea.value = urlList;
219 document.body.appendChild(textarea);
220 textarea.select();
221 document.execCommand('copy');
222 document.body.removeChild(textarea);
223 alert('All website URLs copied to clipboard!');
224 }
225 });
226
227 document.getElementById('download-csv-btn').addEventListener('click', () => {
228 let csv = 'Company Name,Website URL,Link\n';
229 websites.forEach(item => {
230 csv += `"${item.company}","${item.website}","${item.link}"\n`;
231 });
232
233 const blob = new Blob([csv], { type: 'text/csv' });
234 const url = URL.createObjectURL(blob);
235 const a = document.createElement('a');
236 a.href = url;
237 a.download = 'saudi-pp-websites.csv';
238 document.body.appendChild(a);
239 a.click();
240 document.body.removeChild(a);
241 URL.revokeObjectURL(url);
242 });
243
244 overlay.addEventListener('click', () => {
245 modal.remove();
246 overlay.remove();
247 });
248 }
249
250 // Function to create and add the scrape button
251 function addScrapeButton() {
252 // Check if button already exists
253 if (document.getElementById('scrape-websites-btn')) {
254 return;
255 }
256
257 // Find the table wrapper
258 const tableWrapper = document.querySelector('#footable_parent_23093');
259 if (!tableWrapper) {
260 console.log('Table wrapper not found, will retry...');
261 return;
262 }
263
264 // Create button container
265 const buttonContainer = document.createElement('div');
266 buttonContainer.style.cssText = `
267 margin: 20px 0;
268 text-align: right;
269 `;
270
271 // Create scrape button
272 const scrapeButton = document.createElement('button');
273 scrapeButton.id = 'scrape-websites-btn';
274 scrapeButton.textContent = '🌐 Scrape All Websites';
275 scrapeButton.style.cssText = `
276 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
277 color: white;
278 border: none;
279 padding: 12px 24px;
280 border-radius: 8px;
281 cursor: pointer;
282 font-size: 16px;
283 font-weight: bold;
284 box-shadow: 0 4px 6px rgba(0,0,0,0.1);
285 transition: all 0.3s ease;
286 `;
287
288 scrapeButton.addEventListener('mouseenter', () => {
289 scrapeButton.style.transform = 'translateY(-2px)';
290 scrapeButton.style.boxShadow = '0 6px 12px rgba(0,0,0,0.15)';
291 });
292
293 scrapeButton.addEventListener('mouseleave', () => {
294 scrapeButton.style.transform = 'translateY(0)';
295 scrapeButton.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)';
296 });
297
298 scrapeButton.addEventListener('click', async () => {
299 scrapeButton.disabled = true;
300 scrapeButton.textContent = '⏳ Scraping...';
301
302 try {
303 const websites = await scrapeWebsites();
304 displayResults(websites);
305 } catch (error) {
306 console.error('Error during scraping:', error);
307 alert('An error occurred while scraping. Check the console for details.');
308 } finally {
309 scrapeButton.disabled = false;
310 scrapeButton.textContent = '🌐 Scrape All Websites';
311 }
312 });
313
314 buttonContainer.appendChild(scrapeButton);
315 tableWrapper.parentNode.insertBefore(buttonContainer, tableWrapper);
316
317 console.log('Scrape button added successfully');
318 }
319
320 // Initialize the extension
321 function init() {
322 console.log('Initializing Saudi Print & Pack Website Scraper...');
323
324 // Wait for the table to be loaded
325 const checkTable = setInterval(() => {
326 const table = document.querySelector('table[data-footable_id="23093"]');
327 if (table) {
328 clearInterval(checkTable);
329 console.log('Table found, adding scrape button...');
330 addScrapeButton();
331 }
332 }, 1000);
333
334 // Stop checking after 30 seconds
335 setTimeout(() => {
336 clearInterval(checkTable);
337 }, 30000);
338 }
339
340 // Run when DOM is ready
341 if (document.readyState === 'loading') {
342 document.addEventListener('DOMContentLoaded', init);
343 } else {
344 init();
345 }
346
347})();