Extract email addresses from company profiles across multiple pages
Size
14.5 KB
Version
1.1.1
Created
Dec 9, 2025
Updated
7 days ago
1// ==UserScript==
2// @name Company Email Extractor for MyLimoBiz
3// @description Extract email addresses from company profiles across multiple pages
4// @version 1.1.1
5// @match https://*.manage.mylimobiz.com/*
6// @icon https://manage.mylimobiz.com/_images/favicons/favicon-32x32.png
7// @grant GM.getValue
8// @grant GM.setValue
9// @grant GM.xmlhttpRequest
10// ==/UserScript==
11(function() {
12 'use strict';
13
14 // Utility function to delay execution
15 const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
16
17 // Debounce function for MutationObserver
18 function debounce(func, wait) {
19 let timeout;
20 return function executedFunction(...args) {
21 const later = () => {
22 clearTimeout(timeout);
23 func(...args);
24 };
25 clearTimeout(timeout);
26 timeout = setTimeout(later, wait);
27 };
28 }
29
30 // Extract emails from a profile page HTML
31 function extractEmailsFromProfile(html, companyName, profileUrl) {
32 const parser = new DOMParser();
33 const doc = parser.parseFromString(html, 'text/html');
34
35 const emails = [];
36 const emailLinks = doc.querySelectorAll('a[href^="mailto:"]');
37
38 emailLinks.forEach(link => {
39 const email = link.href.replace('mailto:', '');
40 const section = link.closest('section');
41 let contactType = 'Unknown';
42
43 if (section) {
44 const header = section.querySelector('h5');
45 if (header) {
46 contactType = header.textContent.trim();
47 }
48 }
49
50 emails.push({
51 email: email,
52 contactType: contactType
53 });
54 });
55
56 return {
57 companyName: companyName,
58 profileUrl: profileUrl,
59 emails: emails
60 };
61 }
62
63 // Fetch a page and extract company links
64 async function fetchPageCompanies(pageNumber) {
65 console.log(`Fetching page ${pageNumber}...`);
66
67 const url = `https://manage.mylimobiz.com/adminnew/LaNet/SearchAffiliates/US?PageIndex=${pageNumber - 1}&CompanyName=&state=&AirportsServiced=&MarketsServiced=&LaBlackChecked=false`;
68
69 return new Promise((resolve, reject) => {
70 GM.xmlhttpRequest({
71 method: 'GET',
72 url: url,
73 anonymous: false,
74 onload: function(response) {
75 console.log(`Response received for page ${pageNumber}, status: ${response.status}`);
76
77 const parser = new DOMParser();
78 const doc = parser.parseFromString(response.responseText, 'text/html');
79
80 const companies = [];
81 const rows = doc.querySelectorAll('table.table tbody tr');
82
83 console.log(`Found ${rows.length} rows on page ${pageNumber}`);
84
85 rows.forEach(row => {
86 const nameCell = row.querySelector('td:first-child');
87 const linkCell = row.querySelector('td:last-child a[href*="/AffiliateProfile/"]');
88
89 if (nameCell && linkCell) {
90 const companyName = nameCell.textContent.trim();
91 const profileUrl = 'https://manage.mylimobiz.com' + linkCell.getAttribute('href');
92
93 console.log(`Found company: ${companyName}`);
94
95 companies.push({
96 name: companyName,
97 url: profileUrl
98 });
99 }
100 });
101
102 console.log(`Extracted ${companies.length} companies from page ${pageNumber}`);
103 resolve(companies);
104 },
105 onerror: function(error) {
106 console.error(`Error fetching page ${pageNumber}:`, error);
107 reject(error);
108 }
109 });
110 });
111 }
112
113 // Fetch a company profile and extract emails
114 async function fetchCompanyProfile(company) {
115 console.log(`Fetching profile for: ${company.name}`);
116
117 return new Promise((resolve, reject) => {
118 GM.xmlhttpRequest({
119 method: 'GET',
120 url: company.url,
121 anonymous: false,
122 onload: function(response) {
123 const profileData = extractEmailsFromProfile(response.responseText, company.name, company.url);
124 resolve(profileData);
125 },
126 onerror: function(error) {
127 console.error(`Error fetching profile for ${company.name}:`, error);
128 reject(error);
129 }
130 });
131 });
132 }
133
134 // Main extraction function
135 async function extractAllEmails(startPage, endPage, progressCallback) {
136 const allResults = [];
137 let totalCompanies = 0;
138 let processedCompanies = 0;
139
140 try {
141 // First, collect all companies from all pages
142 console.log(`Collecting companies from pages ${startPage} to ${endPage}...`);
143 const allCompanies = [];
144
145 for (let page = startPage; page <= endPage; page++) {
146 try {
147 const companies = await fetchPageCompanies(page);
148 allCompanies.push(...companies);
149 totalCompanies = allCompanies.length;
150
151 if (progressCallback) {
152 progressCallback({
153 phase: 'collecting',
154 currentPage: page,
155 totalPages: endPage - startPage + 1,
156 totalCompanies: totalCompanies
157 });
158 }
159
160 await delay(500); // Small delay between page requests
161 } catch (error) {
162 console.error(`Failed to fetch page ${page}:`, error);
163 }
164 }
165
166 console.log(`Found ${totalCompanies} companies. Now extracting emails...`);
167
168 // Now fetch each company profile
169 for (const company of allCompanies) {
170 try {
171 const profileData = await fetchCompanyProfile(company);
172 allResults.push(profileData);
173 processedCompanies++;
174
175 if (progressCallback) {
176 progressCallback({
177 phase: 'extracting',
178 processedCompanies: processedCompanies,
179 totalCompanies: totalCompanies,
180 currentCompany: company.name
181 });
182 }
183
184 await delay(300); // Small delay between profile requests
185 } catch (error) {
186 console.error(`Failed to fetch profile for ${company.name}:`, error);
187 }
188 }
189
190 // Save results
191 await GM.setValue('extractedEmails', JSON.stringify(allResults));
192 console.log('Extraction complete!', allResults);
193
194 return allResults;
195 } catch (error) {
196 console.error('Error during extraction:', error);
197 throw error;
198 }
199 }
200
201 // Convert results to CSV
202 function convertToCSV(results) {
203 const rows = [['Company Name', 'Contact Type', 'Email Address', 'Profile URL']];
204
205 results.forEach(company => {
206 if (company.emails.length === 0) {
207 rows.push([company.companyName, 'No email found', '', company.profileUrl]);
208 } else {
209 company.emails.forEach(emailData => {
210 rows.push([
211 company.companyName,
212 emailData.contactType,
213 emailData.email,
214 company.profileUrl
215 ]);
216 });
217 }
218 });
219
220 return rows.map(row => row.map(cell => `"${cell}"`).join(',')).join('\n');
221 }
222
223 // Download CSV file
224 function downloadCSV(csv, filename) {
225 const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
226 const link = document.createElement('a');
227 const url = URL.createObjectURL(blob);
228
229 link.setAttribute('href', url);
230 link.setAttribute('download', filename);
231 link.style.visibility = 'hidden';
232 document.body.appendChild(link);
233 link.click();
234 document.body.removeChild(link);
235 }
236
237 // Create UI
238 function createUI() {
239 // Check if we're on the search page
240 if (!window.location.href.includes('/SearchAffiliates/')) {
241 return;
242 }
243
244 // Create container
245 const container = document.createElement('div');
246 container.id = 'email-extractor-ui';
247 container.style.cssText = `
248 position: fixed;
249 top: 20px;
250 right: 20px;
251 background: white;
252 border: 2px solid #007bff;
253 border-radius: 8px;
254 padding: 20px;
255 box-shadow: 0 4px 6px rgba(0,0,0,0.1);
256 z-index: 10000;
257 min-width: 350px;
258 font-family: Arial, sans-serif;
259 `;
260
261 container.innerHTML = `
262 <h3 style="margin: 0 0 15px 0; color: #333; font-size: 18px;">Email Extractor</h3>
263 <div style="margin-bottom: 15px;">
264 <label style="display: block; margin-bottom: 5px; font-weight: bold; color: #555;">Start Page:</label>
265 <input type="number" id="start-page" value="119" min="1" max="217" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
266 </div>
267 <div style="margin-bottom: 15px;">
268 <label style="display: block; margin-bottom: 5px; font-weight: bold; color: #555;">End Page:</label>
269 <input type="number" id="end-page" value="217" min="1" max="217" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
270 </div>
271 <button id="start-extraction" style="width: 100%; padding: 10px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: bold; margin-bottom: 10px;">
272 Start Extraction
273 </button>
274 <button id="download-csv" style="width: 100%; padding: 10px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: bold; display: none;">
275 Download CSV
276 </button>
277 <div id="progress-info" style="margin-top: 15px; padding: 10px; background: #f8f9fa; border-radius: 4px; display: none;">
278 <div style="font-weight: bold; color: #333; margin-bottom: 5px;">Status:</div>
279 <div id="progress-text" style="color: #666; font-size: 14px;"></div>
280 </div>
281 `;
282
283 document.body.appendChild(container);
284
285 // Add event listeners
286 const startBtn = document.getElementById('start-extraction');
287 const downloadBtn = document.getElementById('download-csv');
288 const progressInfo = document.getElementById('progress-info');
289 const progressText = document.getElementById('progress-text');
290
291 let extractedData = null;
292
293 startBtn.addEventListener('click', async () => {
294 const startPage = parseInt(document.getElementById('start-page').value);
295 const endPage = parseInt(document.getElementById('end-page').value);
296
297 if (startPage < 1 || startPage > 217 || endPage < 1 || endPage > 217 || startPage > endPage) {
298 alert('Please enter valid page numbers (1-217)');
299 return;
300 }
301
302 startBtn.disabled = true;
303 startBtn.style.background = '#6c757d';
304 startBtn.textContent = 'Extracting...';
305 progressInfo.style.display = 'block';
306 downloadBtn.style.display = 'none';
307
308 try {
309 extractedData = await extractAllEmails(startPage, endPage, (progress) => {
310 if (progress.phase === 'collecting') {
311 progressText.innerHTML = `
312 <strong>Phase 1:</strong> Collecting companies<br>
313 Page: ${progress.currentPage} / ${progress.totalPages}<br>
314 Companies found: ${progress.totalCompanies}
315 `;
316 } else if (progress.phase === 'extracting') {
317 progressText.innerHTML = `
318 <strong>Phase 2:</strong> Extracting emails<br>
319 Progress: ${progress.processedCompanies} / ${progress.totalCompanies}<br>
320 Current: ${progress.currentCompany}
321 `;
322 }
323 });
324
325 progressText.innerHTML = `
326 <strong style="color: #28a745;">✓ Complete!</strong><br>
327 Extracted emails from ${extractedData.length} companies
328 `;
329
330 downloadBtn.style.display = 'block';
331 startBtn.textContent = 'Start Extraction';
332 startBtn.style.background = '#28a745';
333 startBtn.disabled = false;
334
335 } catch (error) {
336 progressText.innerHTML = `<strong style="color: #dc3545;">Error:</strong> ${error.message}`;
337 startBtn.textContent = 'Start Extraction';
338 startBtn.style.background = '#28a745';
339 startBtn.disabled = false;
340 }
341 });
342
343 downloadBtn.addEventListener('click', () => {
344 if (extractedData) {
345 const csv = convertToCSV(extractedData);
346 const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
347 downloadCSV(csv, `company-emails-${timestamp}.csv`);
348 }
349 });
350 }
351
352 // Initialize
353 function init() {
354 if (document.readyState === 'loading') {
355 document.addEventListener('DOMContentLoaded', createUI);
356 } else {
357 createUI();
358 }
359 }
360
361 init();
362})();