From 084ce6d6d6801abd5913145f3b3787d719a9e35c Mon Sep 17 00:00:00 2001 From: Lord_Devi Date: Thu, 21 Nov 2024 03:51:48 -0500 Subject: [PATCH] First commit. --- content-script.js | 143 ++++++++++++++++++++++++++++++++++++++++++++++ icon48.png | Bin 0 -> 506 bytes icon96.png | Bin 0 -> 846 bytes manifest.json | 18 ++++++ popup.html | 91 +++++++++++++++++++++++++++++ popup.js | 41 +++++++++++++ 6 files changed, 293 insertions(+) create mode 100644 content-script.js create mode 100644 icon48.png create mode 100644 icon96.png create mode 100644 manifest.json create mode 100644 popup.html create mode 100644 popup.js diff --git a/content-script.js b/content-script.js new file mode 100644 index 0000000..8272626 --- /dev/null +++ b/content-script.js @@ -0,0 +1,143 @@ +(function() { + function extractKeywords() { + // Find the keyword grid + const keywordGrid = document.querySelector('[role="grid"][aria-label="Keyword ideas"]'); + if (!keywordGrid) { + alert('Could not find the keyword ideas grid. Please make sure you are on the correct page.'); + return; + } + + // Initialize CSV content based on includeHeaders preference + let csvContent = window.includeHeaders ? 'Keyword,Average Monthly Searches,Source\r\n' : ''; + + // Keep track of processed keywords to prevent duplicates + const processedKeywords = new Set(); + + // Function to clean search volume + function cleanSearchVolume(volume) { + // Remove any commas from the number + return volume.replace(/,/g, ''); + } + + // Function to process rows between start and end indices + function processRowsInRange(startIdx, endIdx, source) { + const rows = document.querySelectorAll('.particle-table-row'); + + for (let i = startIdx; i < Math.min(endIdx, rows.length); i++) { + const row = rows[i]; + const keywordElement = row.querySelector('ess-cell keyword-text'); + const searchVolumeElement = row.querySelector('ess-cell sparkline-graph span'); + + if (keywordElement && searchVolumeElement) { + const keyword = keywordElement.textContent.trim(); + const searchVolume = cleanSearchVolume(searchVolumeElement.textContent.trim()); + + // Only add if we have both values, it's not a header, and we haven't processed this keyword yet + if (keyword && + !keyword.includes('Keyword') && + !keyword.includes('Add to plan') && + !processedKeywords.has(keyword)) { + // Add to processed set + processedKeywords.add(keyword); + // Properly escape the keyword if it contains commas + const escapedKeyword = keyword.includes(',') ? `"${keyword}"` : keyword; + csvContent += `${escapedKeyword},${searchVolume},"${source}"\r\n`; + } + } + } + } + + // Find all section headers and their positions + const rows = Array.from(document.querySelectorAll('.particle-table-row')); + const sections = []; + + // Find all sections and their boundaries + rows.forEach((row, index) => { + const cellText = row.querySelector('ess-cell')?.textContent?.trim(); + if (cellText === 'Keyword ideas' || cellText === 'Keywords you provided') { + sections.push({ + type: cellText, + start: index + 1, // Start after header + end: rows.length // Will be updated for all except last section + }); + } + }); + + // Update section end boundaries + for (let i = 0; i < sections.length - 1; i++) { + sections[i].end = sections[i + 1].start - 1; + } + + // Process each section + sections.forEach(section => { + const source = section.type === 'Keyword ideas' ? 'Keyword Ideas' : 'Provided Keywords'; + processRowsInRange(section.start, section.end, source); + }); + + if (processedKeywords.size === 0) { + alert('No keywords found in either section. Please make sure you\'re on the correct page.'); + return; + } + + // Create a temporary textarea to handle large amounts of text + const textarea = document.createElement('textarea'); + textarea.value = csvContent; + document.body.appendChild(textarea); + textarea.select(); + + try { + document.execCommand('copy'); + const notification = document.createElement('div'); + notification.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: #4CAF50; + color: white; + padding: 15px; + border-radius: 5px; + z-index: 9999; + box-shadow: 0 2px 5px rgba(0,0,0,0.2); + `; + const headerStatus = window.includeHeaders ? 'with headers ' : ''; + notification.textContent = `${processedKeywords.size} unique keywords copied to clipboard ${headerStatus}in CSV format!`; + document.body.appendChild(notification); + + setTimeout(() => notification.remove(), 3000); + } catch (err) { + alert('Failed to copy keywords: ' + err.message); + } finally { + document.body.removeChild(textarea); + } + } + + // Since this is an Angular app, content might load dynamically + // Let's try to wait for the content to load + function waitForContent() { + const maxAttempts = 20; + let attempts = 0; + + function tryFindContent() { + console.log('Attempt', attempts + 1, 'to find keyword content...'); + + // Look for the keyword grid and at least one row + const hasContent = document.querySelector('[role="grid"][aria-label="Keyword ideas"]') && + document.querySelector('.particle-table-row'); + + if (hasContent) { + console.log('Content found, proceeding with keyword extraction...'); + // Wait a bit more for all content to load + setTimeout(extractKeywords, 500); + } else if (attempts < maxAttempts) { + attempts++; + setTimeout(tryFindContent, 1000); // Wait 1 second before trying again + } else { + alert('Could not find keyword content after multiple attempts. Please make sure you are on the correct page.'); + } + } + + tryFindContent(); + } + + waitForContent(); +})(); \ No newline at end of file diff --git a/icon48.png b/icon48.png new file mode 100644 index 0000000000000000000000000000000000000000..54143174c997a6d8cd80e13bb392f16feaa0b238 GIT binary patch literal 506 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sQ2{<7uI>dsK5i!DSoI*pc!_9C<5d{^upC^9X|38Xp2lqkY6x^*vp2OqK#f`6F&>Tx_zAg z5WjVWC5QZ~d-Y{j4pr~=F)%P1d%8G=cpN`F`Q@f&1)kPS)lCrrAu&}?|Hf}lDvjyt znEO(8e>`K6LL;-&|MoBjFZSiki9sAEIa;1RH#KM8bI$ow=Z2jU(Nh%|e}DhkG;x*E z8MpRyhuj{OD{WIel3y*h!B&^n-Bah&^vfZsv?_r`GIu1 z?FW+I|GU)cyy1UiiB8g6fj9Mmyj4}}J}z}WIp1=Zj>%44&$lsEzf~l8-4{PAoL`xu z>QsNTC@Uso(RQWGU8bx~uFn^F?z4Va!XLgwN6FlEXOwK)w*6~vD|?5pj{G(EL5ZjM zw(lQ4NzeZA#^gq$q57P650CF*m{n@{V4nCL4qz}dc)I$ztaD0e F0s!0O)RF)I literal 0 HcmV?d00001 diff --git a/icon96.png b/icon96.png new file mode 100644 index 0000000000000000000000000000000000000000..6d262cf6b099d93baab21ecb92fbcc6c07b5f7ab GIT binary patch literal 846 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy#L1AIbU-3xqt+)eG$a9u!=b@oi=K#OEcg8YIRw69#z;(X}$`_LtI zshQ1I{Pl`|-)+<~KO#}^r%v@yurvb$Q?sXwV@Srmw-IkAEmjaH=|4Y1pmA!f>F$01 zljRnzGcI39+8ra`@G3+3R^>%1Wf~ix4qt1xX6dWs{K-p z&751Jt=EQ+8{8n*-TZYa8>&A}~auQ9~fyZ+{l>przX+Y>_{LIM6k{X5k@L~zkiIW7JgSeV z-6+2JM8bZjMB~0N#cOMmW-9bei8Qp{ms%Fba%&x<=~J~k>;7_dor*2^QFid7N{9Z; zc`U74y7paT-dgZN+pK$^{=qf57eC}g*KjV9*3WNpED*S8^-Gw?*30Fyv_n8a<@s6l zrOBIX&HZwA{-5>p^9S~hZ-!ad*MEH#)MB@A%Ld=%lRF#*{%~9oKlV;>+M=a(FLYhJ z{|3Z%Oq-LacdLcs2+)E;LBG?vLGZcHrOJ6mD5%T1^{+ueOHf)Bm1T6CFf%j=Ff!wc(_ scilDE?dLlocF`NhbMuz5*O~rfdLnoG{p8tqK^cU>)78&qol`;+0P0F^n*aa+ literal 0 HcmV?d00001 diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..472fdda --- /dev/null +++ b/manifest.json @@ -0,0 +1,18 @@ +{ + "manifest_version": 2, + "name": "Keyword Scraper", + "version": "1.0", + "description": "Scrapes keywords from Google Keyword Planner", + "permissions": [ + "activeTab", + "clipboardWrite", + "*://*.google.com/*" + ], + "browser_action": { + "default_popup": "popup.html" + }, + "icons": { + "48": "icon48.png", + "96": "icon96.png" + } +} diff --git a/popup.html b/popup.html new file mode 100644 index 0000000..54d9ca6 --- /dev/null +++ b/popup.html @@ -0,0 +1,91 @@ + + + + + + + + +
+
+

Would you like to include column headers in the export?

+
+ + +
+
+ + + + \ No newline at end of file diff --git a/popup.js b/popup.js new file mode 100644 index 0000000..9b1e5bb --- /dev/null +++ b/popup.js @@ -0,0 +1,41 @@ +document.getElementById('grabKeywords').addEventListener('click', function() { + // Add class to body to expand it + document.body.classList.add('dialog-open'); + // Show the dialog and overlay + document.getElementById('dialog').style.display = 'block'; + document.getElementById('overlay').style.display = 'block'; +}); + +async function handleDialogChoice(includeHeaders) { + try { + // Hide the dialog and overlay + document.getElementById('dialog').style.display = 'none'; + document.getElementById('overlay').style.display = 'none'; + // Remove the expanded class + document.body.classList.remove('dialog-open'); + + // Get the active tab + const tabs = await browser.tabs.query({active: true, currentWindow: true}); + const activeTab = tabs[0]; + + // First inject the content script + await browser.tabs.executeScript(activeTab.id, { + code: `window.includeHeaders = ${includeHeaders};` + }); + + // Then execute the main content script + await browser.tabs.executeScript(activeTab.id, { + file: 'content-script.js' + }); + + // Close the popup after initiating the content script + window.close(); + } catch (error) { + console.error('Error:', error); + alert('Error: ' + error.message); + } +} + +// Add click handlers for the Yes and No buttons +document.getElementById('yesButton').addEventListener('click', () => handleDialogChoice(true)); +document.getElementById('noButton').addEventListener('click', () => handleDialogChoice(false)); \ No newline at end of file