Refactor to use ArtifactScraper singleton
This commit is contained in:
380
content.js
380
content.js
@ -21,218 +21,220 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Open the artifact list menu
|
// Static object containing all artifact scraping functionality
|
||||||
function openArtifactList() {
|
const ArtifactScraper = {
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// Find the global artifact menu button (has the hamburger/list icon)
|
// Open the artifact list menu
|
||||||
const artifactMenuButton = document.querySelector('button[aria-haspopup="menu"] svg[viewBox="0 0 256 256"] path[d*="M80,64a8,8,0,0,1,8-8H216"]');
|
openArtifactList() {
|
||||||
const menuButton = artifactMenuButton?.closest('button');
|
return new Promise((resolve, reject) => {
|
||||||
|
// Find the global artifact menu button (has the hamburger/list icon)
|
||||||
if (!menuButton) {
|
const artifactMenuButton = document.querySelector('button[aria-haspopup="menu"] svg[viewBox="0 0 256 256"] path[d*="M80,64a8,8,0,0,1,8-8H216"]');
|
||||||
reject(new Error('Global artifact menu button not found'));
|
const menuButton = artifactMenuButton?.closest('button');
|
||||||
return;
|
|
||||||
}
|
if (!menuButton) {
|
||||||
|
reject(new Error('Global artifact menu button not found'));
|
||||||
console.log('Opening artifact list menu...');
|
return;
|
||||||
|
}
|
||||||
let hasResolved = false;
|
|
||||||
|
console.log('Opening artifact list menu...');
|
||||||
// Listen for menu opening
|
|
||||||
const observer = new MutationObserver((mutations) => {
|
let hasResolved = false;
|
||||||
mutations.forEach((mutation) => {
|
|
||||||
if (mutation.type === 'childList') {
|
// Listen for menu opening
|
||||||
const addedNodes = Array.from(mutation.addedNodes);
|
const observer = new MutationObserver((mutations) => {
|
||||||
const menuAdded = addedNodes.some(node =>
|
mutations.forEach((mutation) => {
|
||||||
node.nodeType === Node.ELEMENT_NODE &&
|
if (mutation.type === 'childList') {
|
||||||
(node.querySelector('li[role="none"] div[role="menuitem"]') ||
|
const addedNodes = Array.from(mutation.addedNodes);
|
||||||
node.getAttribute('role') === 'menu' ||
|
const menuAdded = addedNodes.some(node =>
|
||||||
node.querySelector('[role="menu"]'))
|
node.nodeType === Node.ELEMENT_NODE &&
|
||||||
);
|
(node.querySelector('li[role="none"] div[role="menuitem"]') ||
|
||||||
|
node.getAttribute('role') === 'menu' ||
|
||||||
if (menuAdded) {
|
node.querySelector('[role="menu"]'))
|
||||||
console.log('Artifact list menu detected as opened');
|
);
|
||||||
observer.disconnect();
|
|
||||||
if (!hasResolved) {
|
if (menuAdded) {
|
||||||
hasResolved = true;
|
console.log('Artifact list menu detected as opened');
|
||||||
resolve(true);
|
observer.disconnect();
|
||||||
|
if (!hasResolved) {
|
||||||
|
hasResolved = true;
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
|
|
||||||
|
// Focus and send Enter key event to open menu
|
||||||
|
menuButton.focus();
|
||||||
|
const enterEvent = new KeyboardEvent('keydown', {
|
||||||
|
key: 'Enter',
|
||||||
|
code: 'Enter',
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true
|
||||||
|
});
|
||||||
|
menuButton.dispatchEvent(enterEvent);
|
||||||
|
|
||||||
|
// Fallback timeout
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!hasResolved) {
|
||||||
|
observer.disconnect();
|
||||||
|
hasResolved = true;
|
||||||
|
reject(new Error('Failed to open artifact list menu - timeout'));
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Parse the artifact list and return filename -> isSelected hash
|
||||||
|
getArtifactListItems() {
|
||||||
|
const menuItems = document.querySelectorAll('li[role="none"] div[role="menuitem"]');
|
||||||
|
const artifactHash = {};
|
||||||
|
|
||||||
|
if (menuItems.length === 0) {
|
||||||
|
console.log('No artifact list items found');
|
||||||
|
return artifactHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
menuItems.forEach((item, index) => {
|
||||||
|
const filenameDiv = item.querySelector('div.line-clamp-2');
|
||||||
|
if (filenameDiv) {
|
||||||
|
const filename = filenameDiv.textContent.trim();
|
||||||
|
const isSelected = item.classList.contains('border-accent-secondary-100') ||
|
||||||
|
item.getAttribute('class').includes('border-accent-secondary-100');
|
||||||
|
|
||||||
|
artifactHash[filename] = isSelected;
|
||||||
|
console.log(`Found artifact: ${filename} (${isSelected ? 'selected' : 'unselected'})`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return artifactHash;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Select a specific artifact list item
|
||||||
|
selectArtifactListItem(filename) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const menuItems = document.querySelectorAll('li[role="none"] div[role="menuitem"]');
|
||||||
|
let targetItem = null;
|
||||||
|
|
||||||
|
// Find the matching artifact by filename
|
||||||
|
menuItems.forEach(item => {
|
||||||
|
const filenameDiv = item.querySelector('div.line-clamp-2');
|
||||||
|
if (filenameDiv && filenameDiv.textContent.trim() === filename) {
|
||||||
|
targetItem = item;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(document.body, { childList: true, subtree: true });
|
|
||||||
|
|
||||||
// Focus and send Enter key event to open menu
|
|
||||||
menuButton.focus();
|
|
||||||
const enterEvent = new KeyboardEvent('keydown', {
|
|
||||||
key: 'Enter',
|
|
||||||
code: 'Enter',
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: true
|
|
||||||
});
|
|
||||||
menuButton.dispatchEvent(enterEvent);
|
|
||||||
|
|
||||||
// Fallback timeout
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!hasResolved) {
|
|
||||||
observer.disconnect();
|
|
||||||
hasResolved = true;
|
|
||||||
reject(new Error('Failed to open artifact list menu - timeout'));
|
|
||||||
}
|
|
||||||
}, 3000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5: Parse the artifact list and return filename -> isSelected hash
|
|
||||||
function getArtifactListItems() {
|
|
||||||
const menuItems = document.querySelectorAll('li[role="none"] div[role="menuitem"]');
|
|
||||||
const artifactHash = {};
|
|
||||||
|
|
||||||
if (menuItems.length === 0) {
|
|
||||||
console.log('No artifact list items found');
|
|
||||||
return artifactHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
menuItems.forEach((item, index) => {
|
|
||||||
const filenameDiv = item.querySelector('div.line-clamp-2');
|
|
||||||
if (filenameDiv) {
|
|
||||||
const filename = filenameDiv.textContent.trim();
|
|
||||||
const isSelected = item.classList.contains('border-accent-secondary-100') ||
|
|
||||||
item.getAttribute('class').includes('border-accent-secondary-100');
|
|
||||||
|
|
||||||
artifactHash[filename] = isSelected;
|
if (!targetItem) {
|
||||||
console.log(`Found artifact: ${filename} (${isSelected ? 'selected' : 'unselected'})`);
|
reject(new Error(`Could not find artifact item for: ${filename}`));
|
||||||
}
|
return;
|
||||||
});
|
|
||||||
|
|
||||||
return artifactHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 6: Select a specific artifact list item
|
|
||||||
function selectArtifactListItem(filename) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const menuItems = document.querySelectorAll('li[role="none"] div[role="menuitem"]');
|
|
||||||
let targetItem = null;
|
|
||||||
|
|
||||||
// Find the matching artifact by filename
|
|
||||||
menuItems.forEach(item => {
|
|
||||||
const filenameDiv = item.querySelector('div.line-clamp-2');
|
|
||||||
if (filenameDiv && filenameDiv.textContent.trim() === filename) {
|
|
||||||
targetItem = item;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`Selecting artifact: ${filename}`);
|
||||||
|
|
||||||
|
// Focus and send Enter key event to select the item
|
||||||
|
targetItem.focus();
|
||||||
|
const enterEvent = new KeyboardEvent('keydown', {
|
||||||
|
key: 'Enter',
|
||||||
|
code: 'Enter',
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true
|
||||||
|
});
|
||||||
|
targetItem.dispatchEvent(enterEvent);
|
||||||
|
|
||||||
|
// Wait for artifact to load
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(true);
|
||||||
|
}, 1000);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
if (!targetItem) {
|
|
||||||
reject(new Error(`Could not find artifact item for: ${filename}`));
|
// Get artifact content and handle selection if needed
|
||||||
return;
|
getArtifact(filename, isCurrentlySelected) {
|
||||||
}
|
return new Promise(async (resolve, reject) => {
|
||||||
|
// If not currently selected, we need to select it
|
||||||
console.log(`Selecting artifact: ${filename}`);
|
if (!isCurrentlySelected) {
|
||||||
|
// Open menu if not already open
|
||||||
// Focus and send Enter key event to select the item
|
const menuOpen = document.querySelector('li[role="none"] div[role="menuitem"]');
|
||||||
targetItem.focus();
|
if (!menuOpen) {
|
||||||
const enterEvent = new KeyboardEvent('keydown', {
|
try {
|
||||||
key: 'Enter',
|
await this.openArtifactList();
|
||||||
code: 'Enter',
|
} catch (error) {
|
||||||
bubbles: true,
|
reject(new Error(`Failed to open menu to select: ${filename} - ${error.message}`));
|
||||||
cancelable: true
|
return;
|
||||||
});
|
}
|
||||||
targetItem.dispatchEvent(enterEvent);
|
}
|
||||||
|
|
||||||
// Wait for artifact to load
|
// Select the artifact
|
||||||
setTimeout(() => {
|
|
||||||
resolve(true);
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 7: Get artifact content and handle selection if needed
|
|
||||||
function getArtifact(filename, isCurrentlySelected) {
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
// If not currently selected, we need to select it
|
|
||||||
if (!isCurrentlySelected) {
|
|
||||||
// Open menu if not already open
|
|
||||||
const menuOpen = document.querySelector('li[role="none"] div[role="menuitem"]');
|
|
||||||
if (!menuOpen) {
|
|
||||||
try {
|
try {
|
||||||
await openArtifactList();
|
await this.selectArtifactListItem(filename);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reject(new Error(`Failed to open menu to select: ${filename} - ${error.message}`));
|
reject(new Error(`Failed to select artifact: ${filename} - ${error.message}`));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select the artifact
|
// Extract content from the code block using the updated selector
|
||||||
try {
|
const codeBlock = document.querySelector('div.right-0 code');
|
||||||
await selectArtifactListItem(filename);
|
if (!codeBlock) {
|
||||||
} catch (error) {
|
reject(new Error(`No code block found for artifact: ${filename}`));
|
||||||
reject(new Error(`Failed to select artifact: ${filename} - ${error.message}`));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
const content = codeBlock.textContent || codeBlock.innerText || '';
|
||||||
|
console.log(`Extracted content for ${filename}: ${content.length} characters`);
|
||||||
|
|
||||||
|
resolve(content);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Main collection method
|
||||||
|
async collectArtifacts() {
|
||||||
|
console.log('Starting artifact collection...');
|
||||||
|
|
||||||
// Extract content from the code block
|
// Initialize empty collection
|
||||||
const codeBlock = document.querySelector('div.right-0 code');
|
const artifactCollection = {};
|
||||||
if (!codeBlock) {
|
|
||||||
reject(new Error(`No code block found for artifact: ${filename}`));
|
// Open artifact list
|
||||||
|
await this.openArtifactList();
|
||||||
|
|
||||||
|
// Get list of artifacts
|
||||||
|
const artifactList = this.getArtifactListItems();
|
||||||
|
|
||||||
|
if (Object.keys(artifactList).length === 0) {
|
||||||
|
console.log('No artifacts found in list');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = codeBlock.textContent || codeBlock.innerText || '';
|
console.log(`Found ${Object.keys(artifactList).length} artifacts to collect`);
|
||||||
console.log(`Extracted content for ${filename}: ${content.length} characters`);
|
|
||||||
|
|
||||||
resolve(content);
|
// Iterate through artifacts and collect content
|
||||||
});
|
for (const [filename, isSelected] of Object.entries(artifactList)) {
|
||||||
}
|
console.log(`Processing artifact: ${filename}`);
|
||||||
|
|
||||||
// Step 2: Main collection method
|
try {
|
||||||
async function collectArtifacts() {
|
const content = await this.getArtifact(filename, isSelected);
|
||||||
console.log('Starting artifact collection...');
|
artifactCollection[filename] = content;
|
||||||
|
console.log(`✓ Collected: ${filename}`);
|
||||||
// Step 3: Initialize empty collection
|
} catch (error) {
|
||||||
const artifactCollection = {};
|
console.log(`✗ Failed to collect: ${filename} - ${error.message}`);
|
||||||
|
}
|
||||||
// Step 4: Open artifact list
|
|
||||||
const menuOpened = await openArtifactList();
|
// Small delay between artifacts
|
||||||
if (!menuOpened) {
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
console.log('Failed to open artifact list');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5: Get list of artifacts
|
|
||||||
const artifactList = getArtifactListItems();
|
|
||||||
|
|
||||||
if (Object.keys(artifactList).length === 0) {
|
|
||||||
console.log('No artifacts found in list');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Found ${Object.keys(artifactList).length} artifacts to collect`);
|
|
||||||
|
|
||||||
// Step 6-8: Iterate through artifacts and collect content
|
|
||||||
for (const [filename, isSelected] of Object.entries(artifactList)) {
|
|
||||||
console.log(`Processing artifact: ${filename}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const content = await getArtifact(filename, isSelected);
|
|
||||||
artifactCollection[filename] = content;
|
|
||||||
console.log(`✓ Collected: ${filename}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`✗ Failed to collect: ${filename} - ${error.message}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small delay between artifacts
|
// Log final collection
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
console.log('=== ARTIFACT COLLECTION COMPLETE ===');
|
||||||
|
console.log(`Collected ${Object.keys(artifactCollection).length} artifacts:`);
|
||||||
|
console.log(artifactCollection);
|
||||||
|
|
||||||
|
return artifactCollection;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
// Step 9: Log final collection
|
|
||||||
console.log('=== ARTIFACT COLLECTION COMPLETE ===');
|
|
||||||
console.log(`Collected ${Object.keys(artifactCollection).length} artifacts:`);
|
|
||||||
console.log(artifactCollection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 1: Content script is loaded, start collection
|
// Main function
|
||||||
async function main() {
|
async function main() {
|
||||||
console.log('Running artifact collector');
|
console.log('Running artifact collector');
|
||||||
|
|
||||||
@ -240,7 +242,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await collectArtifacts();
|
await ArtifactScraper.collectArtifacts();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the main function
|
// Run the main function
|
||||||
|
|||||||
Reference in New Issue
Block a user