561 lines
17 KiB
JavaScript
561 lines
17 KiB
JavaScript
/* eslint-disable */
|
|
// enhanced-search-helper.js
|
|
// Enhanced search automation with multiple submission methods
|
|
|
|
if (window.__ENHANCED_SEARCH_HELPER_INITIALIZED__) {
|
|
// Already initialized, skip
|
|
} else {
|
|
window.__ENHANCED_SEARCH_HELPER_INITIALIZED__ = true;
|
|
|
|
/**
|
|
* Perform Google search with enhanced reliability
|
|
* @param {string} selector - CSS selector for the search box
|
|
* @param {string} query - Search query
|
|
* @returns {Promise<Object>} - Result of the search operation
|
|
*/
|
|
async function performGoogleSearch(selector, query) {
|
|
try {
|
|
console.log(`🔍 Attempting Google search with selector: ${selector}, query: ${query}`);
|
|
|
|
// Find the search element
|
|
const searchElement = document.querySelector(selector);
|
|
if (!searchElement) {
|
|
return {
|
|
success: false,
|
|
error: `Search element with selector "${selector}" not found`,
|
|
};
|
|
}
|
|
|
|
// Focus and clear the search box
|
|
searchElement.focus();
|
|
await sleep(200);
|
|
|
|
// Clear existing content
|
|
searchElement.select();
|
|
await sleep(100);
|
|
|
|
// Fill the search box
|
|
searchElement.value = query;
|
|
|
|
// Trigger input events to ensure the page recognizes the input
|
|
searchElement.dispatchEvent(new Event('input', { bubbles: true }));
|
|
searchElement.dispatchEvent(new Event('change', { bubbles: true }));
|
|
|
|
await sleep(500);
|
|
|
|
// Try multiple submission methods
|
|
const submissionSuccess = await submitGoogleSearch(searchElement, query);
|
|
|
|
if (submissionSuccess) {
|
|
console.log(`✅ Google search submitted successfully using selector: ${selector}`);
|
|
return {
|
|
success: true,
|
|
selector,
|
|
query,
|
|
method: submissionSuccess.method,
|
|
};
|
|
} else {
|
|
return {
|
|
success: false,
|
|
error: 'All submission methods failed',
|
|
};
|
|
}
|
|
} catch (error) {
|
|
console.error('Error in performGoogleSearch:', error);
|
|
return {
|
|
success: false,
|
|
error: `Unexpected error: ${error.message}`,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Try multiple methods to submit Google search
|
|
* @param {Element} searchElement - The search input element
|
|
* @param {string} query - Search query
|
|
* @returns {Promise<Object|null>} - Success result or null
|
|
*/
|
|
async function submitGoogleSearch(searchElement, query) {
|
|
const methods = [
|
|
{
|
|
name: 'enter_key',
|
|
action: async () => {
|
|
console.log('🔄 Method 1: Trying Enter key');
|
|
searchElement.focus();
|
|
await sleep(200);
|
|
|
|
const enterEvent = new KeyboardEvent('keydown', {
|
|
key: 'Enter',
|
|
code: 'Enter',
|
|
keyCode: 13,
|
|
which: 13,
|
|
bubbles: true,
|
|
cancelable: true,
|
|
});
|
|
|
|
searchElement.dispatchEvent(enterEvent);
|
|
await sleep(1000);
|
|
|
|
// Check if search was successful
|
|
if (await checkSearchResultsLoaded()) {
|
|
return { method: 'enter_key' };
|
|
}
|
|
return null;
|
|
},
|
|
},
|
|
{
|
|
name: 'search_button',
|
|
action: async () => {
|
|
console.log('🔄 Method 2: Trying search button');
|
|
|
|
const buttonSelectors = [
|
|
'input[value*="Google Search"]',
|
|
'button[aria-label*="Google Search"]',
|
|
'input[type="submit"][value*="Google Search"]',
|
|
'.gNO89b', // Google Search button class
|
|
'center input[type="submit"]:first-of-type',
|
|
'button[type="submit"]',
|
|
'[role="button"][aria-label*="search"]',
|
|
'.Tg7LZd',
|
|
];
|
|
|
|
for (const buttonSelector of buttonSelectors) {
|
|
try {
|
|
const button = document.querySelector(buttonSelector);
|
|
if (button) {
|
|
button.click();
|
|
await sleep(1000);
|
|
|
|
if (await checkSearchResultsLoaded()) {
|
|
return { method: 'search_button', selector: buttonSelector };
|
|
}
|
|
}
|
|
} catch (e) {
|
|
continue;
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
},
|
|
{
|
|
name: 'form_submit',
|
|
action: async () => {
|
|
console.log('🔄 Method 3: Trying form submission');
|
|
|
|
const form = searchElement.closest('form');
|
|
if (form) {
|
|
form.submit();
|
|
await sleep(1000);
|
|
|
|
if (await checkSearchResultsLoaded()) {
|
|
return { method: 'form_submit' };
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
},
|
|
{
|
|
name: 'double_enter',
|
|
action: async () => {
|
|
console.log('🔄 Method 4: Trying double Enter');
|
|
searchElement.focus();
|
|
await sleep(200);
|
|
|
|
// First Enter
|
|
const enterEvent1 = new KeyboardEvent('keydown', {
|
|
key: 'Enter',
|
|
code: 'Enter',
|
|
keyCode: 13,
|
|
which: 13,
|
|
bubbles: true,
|
|
cancelable: true,
|
|
});
|
|
searchElement.dispatchEvent(enterEvent1);
|
|
await sleep(300);
|
|
|
|
// Second Enter
|
|
const enterEvent2 = new KeyboardEvent('keydown', {
|
|
key: 'Enter',
|
|
code: 'Enter',
|
|
keyCode: 13,
|
|
which: 13,
|
|
bubbles: true,
|
|
cancelable: true,
|
|
});
|
|
searchElement.dispatchEvent(enterEvent2);
|
|
await sleep(1000);
|
|
|
|
if (await checkSearchResultsLoaded()) {
|
|
return { method: 'double_enter' };
|
|
}
|
|
return null;
|
|
},
|
|
},
|
|
];
|
|
|
|
for (const method of methods) {
|
|
try {
|
|
const result = await method.action();
|
|
if (result) {
|
|
console.log(`✅ Submission method "${method.name}" successful`);
|
|
return result;
|
|
}
|
|
} catch (error) {
|
|
console.debug(`Submission method "${method.name}" failed:`, error);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
console.warn('❌ All submission methods failed');
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Check if Google search results have loaded
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async function checkSearchResultsLoaded() {
|
|
const resultIndicators = [
|
|
'#search', // Main search results container
|
|
'#rso', // Results container
|
|
'.g', // Individual result
|
|
'.tF2Cxc', // Modern Google result container
|
|
'#result-stats', // Search statistics
|
|
'.yuRUbf', // Result link container
|
|
];
|
|
|
|
for (const indicator of resultIndicators) {
|
|
const element = document.querySelector(indicator);
|
|
if (element && element.children.length > 0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Extract search results from the current page with intelligent selector discovery
|
|
* @param {number} maxResults - Maximum number of results to extract
|
|
* @returns {Promise<Object>} - Extracted results
|
|
*/
|
|
async function extractSearchResults(maxResults = 10) {
|
|
try {
|
|
console.log('🔍 Starting intelligent search result extraction...');
|
|
const results = [];
|
|
|
|
// Try multiple selectors for Google search results
|
|
const resultSelectors = [
|
|
'.tF2Cxc', // Current Google search result container
|
|
'.g', // Traditional Google search result
|
|
'#rso .g', // Results container with .g class
|
|
'.yuRUbf', // Google result link container
|
|
'.rc', // Another Google result class
|
|
];
|
|
|
|
let resultElements = [];
|
|
let successfulSelector = null;
|
|
|
|
// First try standard selectors
|
|
for (const selector of resultSelectors) {
|
|
resultElements = document.querySelectorAll(selector);
|
|
if (resultElements.length > 0) {
|
|
successfulSelector = selector;
|
|
console.log(`✅ Found results with standard selector: ${selector}`);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If standard selectors fail, try intelligent discovery
|
|
if (resultElements.length === 0) {
|
|
console.log('🧠 Standard selectors failed, trying intelligent discovery...');
|
|
const discoveryResult = await discoverSearchResultElements();
|
|
resultElements = discoveryResult.elements;
|
|
successfulSelector = discoveryResult.selector;
|
|
}
|
|
|
|
// Extract results from found elements
|
|
for (let i = 0; i < Math.min(resultElements.length, maxResults); i++) {
|
|
const element = resultElements[i];
|
|
|
|
try {
|
|
const extractedResult = extractResultFromElement(element, i + 1);
|
|
if (extractedResult) {
|
|
results.push(extractedResult);
|
|
}
|
|
} catch (e) {
|
|
console.debug(`Error extracting result ${i}:`, e);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
results,
|
|
totalFound: results.length,
|
|
selectorUsed: successfulSelector,
|
|
method: resultElements.length > 0 ? 'extraction' : 'none',
|
|
};
|
|
} catch (error) {
|
|
console.error('Error extracting search results:', error);
|
|
return {
|
|
success: false,
|
|
error: error.message,
|
|
results: [],
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Intelligent discovery of search result elements
|
|
* @returns {Object} - Object with elements array and successful selector
|
|
*/
|
|
async function discoverSearchResultElements() {
|
|
console.log('🔬 Starting intelligent element discovery...');
|
|
|
|
// Intelligent selectors based on common patterns
|
|
const intelligentSelectors = [
|
|
// Modern Google patterns (2024+)
|
|
'[data-ved] h3',
|
|
'[data-ved]:has(h3)',
|
|
'[data-ved]:has(a[href*="http"])',
|
|
'[jscontroller]:has(h3)',
|
|
'[jscontroller]:has(a[href*="http"])',
|
|
|
|
// Generic search result patterns
|
|
'div[class*="result"]:has(h3)',
|
|
'div[class*="search"]:has(h3)',
|
|
'article:has(h3)',
|
|
'li[class*="result"]:has(h3)',
|
|
'[role="main"] div:has(h3)',
|
|
|
|
// Link-based patterns
|
|
'a[href*="http"]:has(h3)',
|
|
'div:has(h3):has(a[href*="http"])',
|
|
|
|
// Container patterns
|
|
'div[class*="container"] > div:has(h3)',
|
|
'div[id*="result"]:has(h3)',
|
|
'div[id*="search"]:has(h3)',
|
|
|
|
// Semantic patterns
|
|
'[role="article"]:has(h3)',
|
|
'[role="listitem"]:has(h3)',
|
|
'div[aria-label*="result"]:has(h3)',
|
|
|
|
// Fallback broad patterns
|
|
'main div:has(h3)',
|
|
'#main div:has(h3)',
|
|
'.main div:has(h3)',
|
|
'h3:has(+ div)',
|
|
'div:has(h3)',
|
|
];
|
|
|
|
for (const selector of intelligentSelectors) {
|
|
try {
|
|
const elements = document.querySelectorAll(selector);
|
|
if (elements.length > 0) {
|
|
// Validate that these look like search results
|
|
const validElements = Array.from(elements).filter((el) =>
|
|
validateSearchResultElement(el),
|
|
);
|
|
|
|
if (validElements.length > 0) {
|
|
console.log(
|
|
`✅ Found ${validElements.length} results with intelligent selector: ${selector}`,
|
|
);
|
|
return {
|
|
elements: validElements,
|
|
selector: `intelligent-${selector}`,
|
|
};
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.debug(`Intelligent selector failed: ${selector}`, e);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Final fallback - DOM structure analysis
|
|
console.log('🔬 Trying DOM structure analysis...');
|
|
return analyzeDOMForSearchResults();
|
|
}
|
|
|
|
/**
|
|
* Validate that an element looks like a search result
|
|
* @param {Element} element - Element to validate
|
|
* @returns {boolean} - True if element looks like a search result
|
|
*/
|
|
function validateSearchResultElement(element) {
|
|
try {
|
|
// Check for common search result indicators
|
|
const hasHeading = element.querySelector('h1, h2, h3, h4, h5, h6');
|
|
const hasLink = element.querySelector('a[href*="http"]');
|
|
const hasText = element.textContent && element.textContent.trim().length > 50;
|
|
|
|
// Must have at least heading and link, or substantial text
|
|
return (hasHeading && hasLink) || hasText;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Analyze DOM structure to find search results using heuristics
|
|
* @returns {Object} - Object with elements array and successful selector
|
|
*/
|
|
function analyzeDOMForSearchResults() {
|
|
console.log('🔬 Analyzing DOM structure for search results...');
|
|
|
|
try {
|
|
// Look for containers with multiple links (likely search results)
|
|
const heuristicSelectors = [
|
|
'div:has(a[href*="http"]):has(h3)',
|
|
'li:has(a[href*="http"]):has(h3)',
|
|
'article:has(a[href*="http"])',
|
|
'main > div:has(h3)',
|
|
'#main > div:has(h3)',
|
|
'[role="main"] > div:has(h3)',
|
|
'div:has(h3):has(a[href*="http"])',
|
|
'section:has(h3):has(a[href*="http"])',
|
|
];
|
|
|
|
for (const selector of heuristicSelectors) {
|
|
try {
|
|
const elements = document.querySelectorAll(selector);
|
|
if (elements.length > 0) {
|
|
const validElements = Array.from(elements).filter((el) =>
|
|
validateSearchResultElement(el),
|
|
);
|
|
|
|
if (validElements.length > 0) {
|
|
console.log(
|
|
`✅ Found ${validElements.length} results with DOM analysis: ${selector}`,
|
|
);
|
|
return {
|
|
elements: validElements,
|
|
selector: `dom-analysis-${selector}`,
|
|
};
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.debug(`DOM analysis selector failed: ${selector}`, e);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Ultimate fallback - any elements with links
|
|
const fallbackElements = document.querySelectorAll('a[href*="http"]');
|
|
if (fallbackElements.length > 0) {
|
|
console.log(`⚠️ Using fallback: found ${fallbackElements.length} link elements`);
|
|
return {
|
|
elements: Array.from(fallbackElements).slice(0, 10), // Limit to 10
|
|
selector: 'fallback-links',
|
|
};
|
|
}
|
|
|
|
console.warn('❌ DOM analysis failed to find any search results');
|
|
return {
|
|
elements: [],
|
|
selector: null,
|
|
};
|
|
} catch (e) {
|
|
console.error('Error in DOM analysis:', e);
|
|
return {
|
|
elements: [],
|
|
selector: null,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extract result data from a single element
|
|
* @param {Element} element - Element to extract from
|
|
* @param {number} index - Result index
|
|
* @returns {Object|null} - Extracted result or null
|
|
*/
|
|
function extractResultFromElement(element, index) {
|
|
try {
|
|
// Try multiple patterns for title extraction
|
|
const titleSelectors = ['h3', 'h2', 'h1', '.LC20lb', '.DKV0Md', 'a[href*="http"]'];
|
|
let titleElement = null;
|
|
|
|
for (const selector of titleSelectors) {
|
|
titleElement = element.querySelector(selector);
|
|
if (titleElement) break;
|
|
}
|
|
|
|
// Try multiple patterns for link extraction
|
|
const linkElement =
|
|
element.querySelector('a[href*="http"]') || (element.tagName === 'A' ? element : null);
|
|
|
|
// Try multiple patterns for snippet extraction
|
|
const snippetSelectors = ['.VwiC3b', '.s', '.st', 'p', 'div:not(:has(h1,h2,h3,h4,h5,h6))'];
|
|
let snippetElement = null;
|
|
|
|
for (const selector of snippetSelectors) {
|
|
snippetElement = element.querySelector(selector);
|
|
if (snippetElement && snippetElement.textContent.trim().length > 20) break;
|
|
}
|
|
|
|
// Extract data
|
|
const title = titleElement?.textContent?.trim() || 'No title found';
|
|
const url = linkElement?.href || '';
|
|
const snippet = snippetElement?.textContent?.trim() || '';
|
|
|
|
// Validate we have meaningful data
|
|
if (title && title !== 'No title found' && url) {
|
|
return {
|
|
title,
|
|
url,
|
|
snippet,
|
|
index,
|
|
};
|
|
}
|
|
|
|
return null;
|
|
} catch (e) {
|
|
console.debug(`Error extracting from element:`, e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sleep utility function
|
|
* @param {number} ms - Milliseconds to sleep
|
|
* @returns {Promise<void>}
|
|
*/
|
|
function sleep(ms) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
// Listen for messages from the extension
|
|
chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => {
|
|
if (request.action === 'performGoogleSearch') {
|
|
performGoogleSearch(request.selector, request.query)
|
|
.then(sendResponse)
|
|
.catch((error) => {
|
|
sendResponse({
|
|
success: false,
|
|
error: `Unexpected error: ${error.message}`,
|
|
});
|
|
});
|
|
return true; // Indicates async response
|
|
} else if (request.action === 'extractSearchResults') {
|
|
extractSearchResults(request.maxResults)
|
|
.then(sendResponse)
|
|
.catch((error) => {
|
|
sendResponse({
|
|
success: false,
|
|
error: `Unexpected error: ${error.message}`,
|
|
results: [],
|
|
});
|
|
});
|
|
return true; // Indicates async response
|
|
} else if (request.action === 'enhanced_search_ping') {
|
|
sendResponse({ status: 'pong' });
|
|
return false;
|
|
}
|
|
});
|
|
}
|