First commit.
This commit is contained in:
commit
084ce6d6d6
143
content-script.js
Normal file
143
content-script.js
Normal file
|
|
@ -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();
|
||||
})();
|
||||
BIN
icon48.png
Normal file
BIN
icon48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 506 B |
BIN
icon96.png
Normal file
BIN
icon96.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 846 B |
18
manifest.json
Normal file
18
manifest.json
Normal file
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
91
popup.html
Normal file
91
popup.html
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
width: 200px;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
font-family: Arial, sans-serif;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
body.dialog-open {
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
}
|
||||
#grabKeywords {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin: 5px 0;
|
||||
cursor: pointer;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
#grabKeywords:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
#dialog {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
z-index: 1000;
|
||||
}
|
||||
#dialog p {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.dialog-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
.dialog-buttons button {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.dialog-buttons button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
#overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<button id="grabKeywords">Grab GKWP Keywords</button>
|
||||
|
||||
<div id="overlay"></div>
|
||||
<div id="dialog">
|
||||
<p>Would you like to include column headers in the export?</p>
|
||||
<div class="dialog-buttons">
|
||||
<button id="yesButton">Yes</button>
|
||||
<button id="noButton">No</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
41
popup.js
Normal file
41
popup.js
Normal file
|
|
@ -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));
|
||||
Loading…
Reference in a new issue