Export all filtered Instagram leads across all pages and datasets in one click
Size
13.4 KB
Version
1.0.1
Created
Oct 29, 2025
Updated
about 1 month ago
1// ==UserScript==
2// @name Instagram Leads Bulk Exporter
3// @description Export all filtered Instagram leads across all pages and datasets in one click
4// @version 1.0.1
5// @match https://*.app.leads.cm/*
6// @icon https://static.funnelcockpit.com/upload/6aYWQjYReAF8v4Jxe/6035cfd67a2e080d248f72796b49c8de.png
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('Instagram Leads Bulk Exporter: Extension loaded');
12
13 // Utility function to wait for element
14 function waitForElement(selector, timeout = 10000) {
15 return new Promise((resolve, reject) => {
16 const element = document.querySelector(selector);
17 if (element) {
18 return resolve(element);
19 }
20
21 const observer = new MutationObserver(() => {
22 const element = document.querySelector(selector);
23 if (element) {
24 observer.disconnect();
25 resolve(element);
26 }
27 });
28
29 observer.observe(document.body, {
30 childList: true,
31 subtree: true
32 });
33
34 setTimeout(() => {
35 observer.disconnect();
36 reject(new Error(`Element ${selector} not found within ${timeout}ms`));
37 }, timeout);
38 });
39 }
40
41 // Utility function to wait/delay
42 function delay(ms) {
43 return new Promise(resolve => setTimeout(resolve, ms));
44 }
45
46 // Extract leads from current page
47 function extractLeadsFromCurrentPage() {
48 const leads = [];
49 const rows = document.querySelectorAll('#contacts-table tbody tr[data-username]');
50
51 console.log(`Extracting ${rows.length} leads from current page`);
52
53 rows.forEach(row => {
54 const username = row.getAttribute('data-username');
55 const cells = row.querySelectorAll('td');
56
57 if (cells.length >= 9) {
58 const lead = {
59 username: cells[0]?.textContent?.trim() || '',
60 name: cells[1]?.textContent?.trim() || '',
61 bio: cells[2]?.textContent?.trim() || '',
62 category: cells[3]?.textContent?.trim() || '',
63 followers: cells[4]?.textContent?.trim().replace(/\s+/g, '') || '',
64 following: cells[5]?.textContent?.trim().replace(/\s+/g, '') || '',
65 website: cells[6]?.querySelector('a')?.href || cells[6]?.textContent?.trim() || '',
66 email: cells[7]?.textContent?.trim() || '',
67 phone: cells[8]?.textContent?.trim() || ''
68 };
69 leads.push(lead);
70 }
71 });
72
73 return leads;
74 }
75
76 // Get total number of pages
77 function getTotalPages() {
78 const totalPagesElement = document.querySelector('#totalPages');
79 return totalPagesElement ? parseInt(totalPagesElement.textContent) : 1;
80 }
81
82 // Get current page number
83 function getCurrentPage() {
84 const pageInput = document.querySelector('#pageInput');
85 return pageInput ? parseInt(pageInput.value) : 1;
86 }
87
88 // Navigate to specific page
89 async function navigateToPage(pageNumber) {
90 const pageInput = document.querySelector('#pageInput');
91 if (!pageInput) {
92 throw new Error('Page input not found');
93 }
94
95 console.log(`Navigating to page ${pageNumber}`);
96 pageInput.value = pageNumber;
97
98 // Trigger change event
99 const event = new Event('change', { bubbles: true });
100 pageInput.dispatchEvent(event);
101
102 // Also try input event
103 const inputEvent = new Event('input', { bubbles: true });
104 pageInput.dispatchEvent(inputEvent);
105
106 // Press Enter key
107 const enterEvent = new KeyboardEvent('keydown', {
108 key: 'Enter',
109 code: 'Enter',
110 keyCode: 13,
111 which: 13,
112 bubbles: true
113 });
114 pageInput.dispatchEvent(enterEvent);
115
116 // Wait for page to load
117 await delay(2000);
118 }
119
120 // Get all dataset buttons
121 function getDatasetButtons() {
122 return Array.from(document.querySelectorAll('.dataset-button[data-dataset-index]'));
123 }
124
125 // Get currently selected dataset
126 function getCurrentDataset() {
127 const selectedBtn = document.querySelector('.dataset-button.selected');
128 return selectedBtn ? parseInt(selectedBtn.getAttribute('data-dataset-index')) : null;
129 }
130
131 // Navigate to specific dataset
132 async function navigateToDataset(datasetIndex) {
133 const datasetButtons = getDatasetButtons();
134 const targetButton = datasetButtons.find(btn =>
135 parseInt(btn.getAttribute('data-dataset-index')) === datasetIndex
136 );
137
138 if (!targetButton) {
139 console.error(`Dataset button ${datasetIndex} not found`);
140 return false;
141 }
142
143 console.log(`Navigating to dataset ${datasetIndex}`);
144 targetButton.click();
145
146 // Wait for dataset to load
147 await delay(2000);
148 return true;
149 }
150
151 // Convert leads to CSV
152 function convertToCSV(leads) {
153 if (leads.length === 0) {
154 return '';
155 }
156
157 const headers = ['Username', 'Name', 'Bio', 'Category', 'Followers', 'Following', 'Website', 'Email', 'Phone'];
158 const csvRows = [headers.join(',')];
159
160 leads.forEach(lead => {
161 const row = [
162 escapeCSV(lead.username),
163 escapeCSV(lead.name),
164 escapeCSV(lead.bio),
165 escapeCSV(lead.category),
166 escapeCSV(lead.followers),
167 escapeCSV(lead.following),
168 escapeCSV(lead.website),
169 escapeCSV(lead.email),
170 escapeCSV(lead.phone)
171 ];
172 csvRows.push(row.join(','));
173 });
174
175 return csvRows.join('\n');
176 }
177
178 // Escape CSV values
179 function escapeCSV(value) {
180 if (value === null || value === undefined) {
181 return '';
182 }
183
184 const stringValue = String(value);
185
186 // If value contains comma, quote, or newline, wrap in quotes and escape quotes
187 if (stringValue.includes(',') || stringValue.includes('"') || stringValue.includes('\n')) {
188 return '"' + stringValue.replace(/"/g, '""') + '"';
189 }
190
191 return stringValue;
192 }
193
194 // Download CSV file
195 function downloadCSV(csvContent, filename) {
196 const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
197 const link = document.createElement('a');
198 const url = URL.createObjectURL(blob);
199
200 link.setAttribute('href', url);
201 link.setAttribute('download', filename);
202 link.style.visibility = 'hidden';
203
204 document.body.appendChild(link);
205 link.click();
206 document.body.removeChild(link);
207
208 console.log(`CSV file "${filename}" downloaded successfully`);
209 }
210
211 // Main export function
212 async function exportAllLeads() {
213 const exportButton = document.querySelector('#export-all-leads-btn');
214 if (!exportButton) return;
215
216 try {
217 // Disable button and show progress
218 exportButton.disabled = true;
219 exportButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Exporting...';
220
221 const allLeads = [];
222 const datasetButtons = getDatasetButtons();
223 const totalDatasets = datasetButtons.length;
224
225 console.log(`Starting export of ${totalDatasets} datasets`);
226
227 // Store original position
228 const originalDataset = getCurrentDataset();
229 const originalPage = getCurrentPage();
230
231 // Iterate through each dataset
232 for (let i = 0; i < totalDatasets; i++) {
233 const datasetIndex = parseInt(datasetButtons[i].getAttribute('data-dataset-index'));
234
235 exportButton.innerHTML = `<i class="fas fa-spinner fa-spin"></i> Dataset ${i + 1}/${totalDatasets}...`;
236
237 // Navigate to dataset
238 const success = await navigateToDataset(datasetIndex);
239 if (!success) {
240 console.error(`Failed to navigate to dataset ${datasetIndex}`);
241 continue;
242 }
243
244 // Get total pages for this dataset
245 await delay(1000); // Wait for page count to update
246 const totalPages = getTotalPages();
247 console.log(`Dataset ${datasetIndex} has ${totalPages} pages`);
248
249 // Iterate through each page in the dataset
250 for (let page = 1; page <= totalPages; page++) {
251 exportButton.innerHTML = `<i class="fas fa-spinner fa-spin"></i> Dataset ${i + 1}/${totalDatasets}, Page ${page}/${totalPages}...`;
252
253 // Navigate to page if not on page 1
254 if (page > 1) {
255 await navigateToPage(page);
256 }
257
258 // Extract leads from current page
259 const pageLeads = extractLeadsFromCurrentPage();
260 allLeads.push(...pageLeads);
261
262 console.log(`Collected ${pageLeads.length} leads from dataset ${datasetIndex}, page ${page}. Total: ${allLeads.length}`);
263 }
264 }
265
266 console.log(`Export complete! Total leads collected: ${allLeads.length}`);
267
268 // Convert to CSV and download
269 if (allLeads.length > 0) {
270 const csvContent = convertToCSV(allLeads);
271 const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
272 const filename = `instagram_leads_all_${timestamp}.csv`;
273 downloadCSV(csvContent, filename);
274
275 exportButton.innerHTML = `<i class="fas fa-check"></i> Exported ${allLeads.length} leads!`;
276 } else {
277 exportButton.innerHTML = '<i class="fas fa-exclamation-triangle"></i> No leads found';
278 }
279
280 // Navigate back to original position
281 await delay(1000);
282 if (originalDataset) {
283 await navigateToDataset(originalDataset);
284 if (originalPage > 1) {
285 await navigateToPage(originalPage);
286 }
287 }
288
289 // Re-enable button after 3 seconds
290 setTimeout(() => {
291 exportButton.disabled = false;
292 exportButton.innerHTML = '<i class="fas fa-download"></i> Export All Leads';
293 }, 3000);
294
295 } catch (error) {
296 console.error('Error during export:', error);
297 exportButton.innerHTML = '<i class="fas fa-exclamation-triangle"></i> Export Failed';
298 exportButton.disabled = false;
299
300 setTimeout(() => {
301 exportButton.innerHTML = '<i class="fas fa-download"></i> Export All Leads';
302 }, 3000);
303 }
304 }
305
306 // Add the export all button
307 function addExportAllButton() {
308 // Find the filter button container
309 const filterContainer = document.querySelector('.filter-button-container');
310 if (!filterContainer) {
311 console.error('Filter button container not found');
312 return;
313 }
314
315 // Check if button already exists
316 if (document.querySelector('#export-all-leads-btn')) {
317 console.log('Export All button already exists');
318 return;
319 }
320
321 // Create the button
322 const exportAllButton = document.createElement('a');
323 exportAllButton.id = 'export-all-leads-btn';
324 exportAllButton.className = 'filter-button';
325 exportAllButton.href = 'javascript:void(0);';
326 exportAllButton.innerHTML = '<i class="fas fa-download"></i> Export All Leads';
327 exportAllButton.style.cssText = 'background-color: #28a745; color: white; margin-left: 10px;';
328
329 // Add click event
330 exportAllButton.addEventListener('click', exportAllLeads);
331
332 // Insert the button
333 filterContainer.appendChild(exportAllButton);
334
335 console.log('Export All Leads button added successfully');
336 }
337
338 // Initialize the extension
339 async function init() {
340 console.log('Initializing Instagram Leads Bulk Exporter...');
341
342 // Check if we're on the Instagram leads page
343 if (!window.location.href.includes('app.leads.cm/instagram')) {
344 console.log('Not on Instagram leads page, skipping initialization');
345 return;
346 }
347
348 try {
349 // Wait for the filter container to be available
350 await waitForElement('.filter-button-container', 10000);
351
352 // Add the export all button
353 addExportAllButton();
354
355 console.log('Instagram Leads Bulk Exporter initialized successfully');
356 } catch (error) {
357 console.error('Failed to initialize extension:', error);
358 }
359 }
360
361 // Run initialization when DOM is ready
362 if (document.readyState === 'loading') {
363 document.addEventListener('DOMContentLoaded', init);
364 } else {
365 init();
366 }
367})();