Size
16.4 KB
Version
1.1.1
Created
Dec 17, 2025
Updated
about 2 months ago
1// ==UserScript==
2// @name Success.ai Lead Data Exporter
3// @description A new extension
4// @version 1.1.1
5// @match https://*.app.success.ai/*
6// @icon https://app.success.ai/MainFavIcon.png
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('Success.ai Lead Data Exporter initialized');
12
13 let isExporting = false;
14 let allLeadsData = [];
15
16 // Utility function to wait for elements
17 function waitForElement(selector, timeout = 10000) {
18 return new Promise((resolve, reject) => {
19 if (document.querySelector(selector)) {
20 return resolve(document.querySelector(selector));
21 }
22
23 const observer = new MutationObserver(() => {
24 if (document.querySelector(selector)) {
25 observer.disconnect();
26 resolve(document.querySelector(selector));
27 }
28 });
29
30 observer.observe(document.body, {
31 childList: true,
32 subtree: true
33 });
34
35 setTimeout(() => {
36 observer.disconnect();
37 reject(new Error(`Element ${selector} not found within ${timeout}ms`));
38 }, timeout);
39 });
40 }
41
42 // Function to extract data from current page
43 function extractCurrentPageData() {
44 console.log('Extracting data from current page...');
45 const leads = [];
46
47 // Find all rows in the data grid
48 const rows = document.querySelectorAll('.MuiDataGrid-row');
49 console.log(`Found ${rows.length} rows on current page`);
50
51 rows.forEach((row, index) => {
52 try {
53 const cells = row.querySelectorAll('.MuiDataGrid-cell');
54 const leadData = {};
55
56 cells.forEach(cell => {
57 const field = cell.getAttribute('data-field');
58 if (field && field !== '__check__') {
59 const value = cell.textContent.trim();
60 leadData[field] = value;
61 }
62 });
63
64 // Only add if we have actual data
65 if (Object.keys(leadData).length > 0) {
66 leads.push(leadData);
67 console.log(`Extracted lead ${index + 1}:`, leadData);
68 }
69 } catch (error) {
70 console.error(`Error extracting row ${index}:`, error);
71 }
72 });
73
74 return leads;
75 }
76
77 // Function to set rows per page to 100
78 async function setRowsPerPage(rowCount) {
79 console.log(`Setting rows per page to ${rowCount}...`);
80
81 try {
82 // Find the rows per page dropdown
83 const dropdown = document.querySelector('.MuiSelect-select.MuiSelect-standard');
84
85 if (!dropdown) {
86 console.error('Rows per page dropdown not found');
87 return false;
88 }
89
90 // Click to open dropdown
91 dropdown.click();
92
93 // Wait for menu to appear
94 await new Promise(resolve => setTimeout(resolve, 500));
95
96 // Find the option with value 100
97 const options = document.querySelectorAll('[role="option"]');
98 let targetOption = null;
99
100 for (let option of options) {
101 if (option.textContent.trim() === String(rowCount)) {
102 targetOption = option;
103 break;
104 }
105 }
106
107 if (targetOption) {
108 console.log(`Clicking option: ${rowCount}`);
109 targetOption.click();
110
111 // Wait for the page to reload with new row count
112 await new Promise(resolve => setTimeout(resolve, 3000));
113
114 // Wait for data grid to update
115 await waitForElement('.MuiDataGrid-row', 5000);
116
117 console.log(`Successfully set rows per page to ${rowCount}`);
118 return true;
119 } else {
120 console.error(`Option ${rowCount} not found in dropdown`);
121 // Close the dropdown if we couldn't find the option
122 dropdown.click();
123 return false;
124 }
125 } catch (error) {
126 console.error('Error setting rows per page:', error);
127 return false;
128 }
129 }
130
131 // Function to check if there's a next page
132 function hasNextPage() {
133 // Find the next page button (the second button in pagination that's not disabled)
134 const paginationButtons = document.querySelectorAll('.MuiButton-root.MuiButton-text.MuiButton-textPrimary');
135
136 for (let button of paginationButtons) {
137 const startIcon = button.querySelector('.MuiButton-startIcon');
138 if (startIcon && !button.classList.contains('Mui-disabled')) {
139 // Check if this is the "next" button (usually has an arrow icon)
140 const parentDiv = button.closest('div');
141 if (parentDiv && parentDiv.previousElementSibling) {
142 console.log('Next page button found and enabled');
143 return true;
144 }
145 }
146 }
147 console.log('No next page available');
148 return false;
149 }
150
151 // Function to click next page
152 async function goToNextPage() {
153 console.log('Attempting to go to next page...');
154
155 // Find the next page button
156 const paginationButtons = document.querySelectorAll('.MuiButton-root.MuiButton-text.MuiButton-textPrimary');
157
158 for (let button of paginationButtons) {
159 const startIcon = button.querySelector('.MuiButton-startIcon');
160 if (startIcon && !button.classList.contains('Mui-disabled')) {
161 const parentDiv = button.closest('div');
162 if (parentDiv && parentDiv.previousElementSibling) {
163 console.log('Clicking next page button...');
164 button.click();
165
166 // Wait for the page to load
167 await new Promise(resolve => setTimeout(resolve, 3000));
168
169 // Wait for the data grid to update
170 await waitForElement('.MuiDataGrid-row', 5000);
171
172 return true;
173 }
174 }
175 }
176
177 return false;
178 }
179
180 // Function to convert data to CSV
181 function convertToCSV(data) {
182 if (data.length === 0) {
183 return '';
184 }
185
186 // Get all unique headers
187 const headers = new Set();
188 data.forEach(row => {
189 Object.keys(row).forEach(key => headers.add(key));
190 });
191 const headerArray = Array.from(headers);
192
193 // Create CSV header row
194 const csvRows = [];
195 csvRows.push(headerArray.map(h => `"${h}"`).join(','));
196
197 // Create CSV data rows
198 data.forEach(row => {
199 const values = headerArray.map(header => {
200 const value = row[header] || '';
201 return `"${String(value).replace(/"/g, '""')}"`;
202 });
203 csvRows.push(values.join(','));
204 });
205
206 return csvRows.join('\n');
207 }
208
209 // Function to download CSV
210 function downloadCSV(csvContent, filename) {
211 const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
212 const link = document.createElement('a');
213 const url = URL.createObjectURL(blob);
214
215 link.setAttribute('href', url);
216 link.setAttribute('download', filename);
217 link.style.visibility = 'hidden';
218
219 document.body.appendChild(link);
220 link.click();
221 document.body.removeChild(link);
222
223 console.log(`CSV downloaded: ${filename}`);
224 }
225
226 // Main export function
227 async function exportAllData() {
228 if (isExporting) {
229 console.log('Export already in progress');
230 return;
231 }
232
233 isExporting = true;
234 allLeadsData = [];
235
236 const exportButton = document.getElementById('export-leads-button');
237 const originalText = exportButton.textContent;
238
239 try {
240 exportButton.textContent = 'Preparing...';
241 exportButton.disabled = true;
242 exportButton.style.opacity = '0.6';
243 exportButton.style.cursor = 'not-allowed';
244
245 // First, set rows per page to 100
246 console.log('Setting rows per page to 100...');
247 const rowsSet = await setRowsPerPage(100);
248
249 if (!rowsSet) {
250 console.warn('Could not set rows per page to 100, continuing with current setting');
251 }
252
253 exportButton.textContent = 'Exporting...';
254
255 let pageCount = 0;
256 let hasMore = true;
257
258 console.log('Starting export process...');
259
260 // Extract first page
261 pageCount++;
262 console.log(`Processing page ${pageCount}...`);
263 const firstPageData = extractCurrentPageData();
264 allLeadsData.push(...firstPageData);
265
266 exportButton.textContent = `Exporting... (Page ${pageCount}, ${allLeadsData.length} leads)`;
267
268 // Continue to next pages until we reach the end
269 while (hasMore) {
270 hasMore = hasNextPage();
271
272 if (hasMore) {
273 const success = await goToNextPage();
274
275 if (success) {
276 pageCount++;
277 console.log(`Processing page ${pageCount}...`);
278
279 const pageData = extractCurrentPageData();
280 allLeadsData.push(...pageData);
281
282 exportButton.textContent = `Exporting... (Page ${pageCount}, ${allLeadsData.length} leads)`;
283 console.log(`Total leads collected so far: ${allLeadsData.length}`);
284 } else {
285 console.log('Could not navigate to next page, stopping');
286 hasMore = false;
287 }
288 } else {
289 console.log('Reached the end of all pages');
290 }
291 }
292
293 console.log(`Export complete! Total pages: ${pageCount}, Total leads: ${allLeadsData.length}`);
294
295 // Only download CSV after all pages are scraped
296 if (allLeadsData.length > 0) {
297 exportButton.textContent = 'Generating CSV...';
298
299 const csvContent = convertToCSV(allLeadsData);
300 const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
301 const filename = `success-ai-leads-${timestamp}.csv`;
302
303 downloadCSV(csvContent, filename);
304
305 exportButton.textContent = `✓ Exported ${allLeadsData.length} leads`;
306
307 setTimeout(() => {
308 exportButton.textContent = originalText;
309 exportButton.disabled = false;
310 exportButton.style.opacity = '1';
311 exportButton.style.cursor = 'pointer';
312 }, 3000);
313 } else {
314 console.error('No data found to export');
315 exportButton.textContent = 'No data found';
316
317 setTimeout(() => {
318 exportButton.textContent = originalText;
319 exportButton.disabled = false;
320 exportButton.style.opacity = '1';
321 exportButton.style.cursor = 'pointer';
322 }, 3000);
323 }
324
325 } catch (error) {
326 console.error('Error during export:', error);
327 exportButton.textContent = 'Export failed';
328
329 setTimeout(() => {
330 exportButton.textContent = originalText;
331 exportButton.disabled = false;
332 exportButton.style.opacity = '1';
333 exportButton.style.cursor = 'pointer';
334 }, 3000);
335 } finally {
336 isExporting = false;
337 }
338 }
339
340 // Function to create and add the export button
341 function createExportButton() {
342 console.log('Creating export button...');
343
344 // Check if button already exists
345 if (document.getElementById('export-leads-button')) {
346 console.log('Export button already exists');
347 return;
348 }
349
350 // Find the container where we want to add the button
351 // Looking for the area near the tabs or pagination
352 const targetContainer = document.querySelector('.css-1hdifnl, .css-6xnj6o, .css-aqj51v');
353
354 if (!targetContainer) {
355 console.error('Could not find target container for export button');
356 return;
357 }
358
359 // Create button container
360 const buttonContainer = document.createElement('div');
361 buttonContainer.style.cssText = `
362 display: flex;
363 align-items: center;
364 justify-content: flex-end;
365 padding: 12px 16px;
366 margin-bottom: 8px;
367 `;
368
369 // Create the export button
370 const exportButton = document.createElement('button');
371 exportButton.id = 'export-leads-button';
372 exportButton.textContent = 'Export Data';
373 exportButton.className = 'MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary';
374 exportButton.style.cssText = `
375 background-color: #1976d2;
376 color: white;
377 padding: 8px 22px;
378 border: none;
379 border-radius: 4px;
380 font-size: 14px;
381 font-weight: 500;
382 cursor: pointer;
383 text-transform: uppercase;
384 letter-spacing: 0.02857em;
385 box-shadow: 0px 3px 1px -2px rgba(0,0,0,0.2), 0px 2px 2px 0px rgba(0,0,0,0.14), 0px 1px 5px 0px rgba(0,0,0,0.12);
386 transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
387 font-family: "Roboto", "Helvetica", "Arial", sans-serif;
388 `;
389
390 // Add hover effect
391 exportButton.addEventListener('mouseenter', () => {
392 if (!exportButton.disabled) {
393 exportButton.style.backgroundColor = '#1565c0';
394 exportButton.style.boxShadow = '0px 2px 4px -1px rgba(0,0,0,0.2), 0px 4px 5px 0px rgba(0,0,0,0.14), 0px 1px 10px 0px rgba(0,0,0,0.12)';
395 }
396 });
397
398 exportButton.addEventListener('mouseleave', () => {
399 if (!exportButton.disabled) {
400 exportButton.style.backgroundColor = '#1976d2';
401 exportButton.style.boxShadow = '0px 3px 1px -2px rgba(0,0,0,0.2), 0px 2px 2px 0px rgba(0,0,0,0.14), 0px 1px 5px 0px rgba(0,0,0,0.12)';
402 }
403 });
404
405 // Add click event
406 exportButton.addEventListener('click', exportAllData);
407
408 buttonContainer.appendChild(exportButton);
409
410 // Insert the button at the top of the container
411 targetContainer.insertBefore(buttonContainer, targetContainer.firstChild);
412
413 console.log('Export button created and added to page');
414 }
415
416 // Initialize the extension
417 function init() {
418 console.log('Initializing Success.ai Lead Data Exporter...');
419
420 // Wait for the page to load
421 if (document.readyState === 'loading') {
422 document.addEventListener('DOMContentLoaded', () => {
423 setTimeout(createExportButton, 2000);
424 });
425 } else {
426 setTimeout(createExportButton, 2000);
427 }
428
429 // Also observe for dynamic content changes
430 const observer = new MutationObserver(() => {
431 if (!document.getElementById('export-leads-button')) {
432 const dataGrid = document.querySelector('.MuiDataGrid-root');
433 if (dataGrid) {
434 createExportButton();
435 }
436 }
437 });
438
439 observer.observe(document.body, {
440 childList: true,
441 subtree: true
442 });
443 }
444
445 // Start the extension
446 init();
447})();