diff --git a/content.js b/content.js index d078b16..a4b2c17 100644 --- a/content.js +++ b/content.js @@ -541,8 +541,14 @@ function findTileMenuButton(tile) { } function findMenuItem(re) { - return [...document.querySelectorAll('[role="menu"] [role="menuitem"], [role="menuitem"], [role="menu"] button, [role="dialog"] button')] + const roled = [...document.querySelectorAll('[role="menu"] [role="menuitem"], [role="menuitem"], [role="menu"] button, [role="dialog"] button')] .filter(visible).find(e => re.test((e.textContent || '').trim())); + if (roled) return roled; + // fallback for menus without ARIA roles: small visible leaf nodes with matching text + const leaf = [...document.querySelectorAll('div, span, li, button')].filter(visible) + .filter(e => re.test((e.textContent || '').trim()) && (e.textContent || '').trim().length < 30 && + !e.querySelector('div, span, li, button')); + return leaf.length ? (leaf[0].closest('[role="menuitem"], li, button') || leaf[0]) : null; } async function executeHarvest(task) { @@ -550,7 +556,7 @@ async function executeHarvest(task) { let got = 0, missed = 0, skipped = 0; try { await waitForAgentReady(30000); - const stored = await new Promise(r => chrome.storage.local.get({ harvested_urls: [] }, d => r(d.harvested_urls))); + const stored = await new Promise(r => chrome.storage.local.get({ harvested_urls_v2: [] }, d => r(d.harvested_urls_v2))); const seen = new Set(stored); await bg({ type: 'harvestRouting', on: true }); window.scrollTo(0, 0); await sleep(800); @@ -568,14 +574,20 @@ async function executeHarvest(task) { const dots = findTileMenuButton(tile); if (!dots) { missed++; seen.add(href); log('info', `harvest: no ⋮ on a tile — ${gridCensus()}`); continue; } await bg({ type: 'clickAt', ...center(dots) }); await sleep(800); - const dl = findMenuItem(/^\s*download\s*$/i); - if (!dl) { missed++; seen.add(href); await bg({ type: 'pressEscape' }); await sleep(400); continue; } - await bg({ type: 'clickAt', ...center(dl) }); await sleep(800); - const oneK = findMenuItem(/1k|original/i); - if (!oneK) { missed++; seen.add(href); await bg({ type: 'pressEscape' }); await sleep(400); continue; } + const dl = findMenuItem(/^\s*download\b/i); + if (!dl) { missed++; seen.add(href); log('info', `harvest: menu open but no Download item — ${gridCensus()}`); await bg({ type: 'pressEscape' }); await sleep(400); continue; } + // "Download" is a submenu parent: HOVER unfurls the nested 1K/2K/4K flyout + // (clicking it closes the menu — the bug the first pass hit). + await bg({ type: 'hoverAt', ...center(dl) }); await sleep(800); + let oneK = findMenuItem(/\b1k\b|original/i); + if (!oneK) { // some builds expand on click instead — try once + await bg({ type: 'clickAt', ...center(dl) }); await sleep(800); + oneK = findMenuItem(/\b1k\b|original/i); + } + if (!oneK) { missed++; seen.add(href); log('info', 'harvest: Download hovered but no 1K flyout appeared.'); await bg({ type: 'pressEscape' }); await sleep(400); continue; } await bg({ type: 'clickAt', ...center(oneK) }); got++; seen.add(href); - chrome.storage.local.set({ harvested_urls: [...seen].slice(-3000) }); + chrome.storage.local.set({ harvested_urls_v2: [...seen].slice(-3000) }); if (got % 10 === 0) log('info', `harvest: ${got} downloaded so far…`); await sleep(1800 + Math.random() * 1500); // pace — don't machine-gun the account }