Created
September 16, 2025 23:49
-
-
Save kmorrill/0b9c8472e1f88eacbfa53169c000c012 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (function(){ | |
| if (window.__sbRemoteInitialized) { | |
| if (typeof window.__sbRemoteStart === 'function') window.__sbRemoteStart(); | |
| return; | |
| } | |
| window.__sbRemoteInitialized = true; | |
| const log = (...args) => console.log('[SB][remote]', ...args); | |
| function sleep(ms) { | |
| return new Promise(resolve => setTimeout(resolve, ms)); | |
| } | |
| async function waitFor(selector, attempts, delay) { | |
| attempts = attempts || 40; | |
| delay = delay || 250; | |
| for (let i = 0; i < attempts; i += 1) { | |
| const node = document.querySelector(selector); | |
| if (node) { | |
| log('Found', selector); | |
| return node; | |
| } | |
| await sleep(delay); | |
| } | |
| log('Failed to find', selector); | |
| return null; | |
| } | |
| function findReviewsLink() { | |
| const link = Array.from(document.querySelectorAll('a')).find(anchor => { | |
| const text = (anchor.textContent || '').trim().toLowerCase(); | |
| return text.includes('google reviews'); | |
| }); | |
| log('Anchor for Google reviews', link); | |
| return link; | |
| } | |
| async function ensureDialog() { | |
| const link = findReviewsLink(); | |
| if (link) { | |
| try { link.scrollIntoView({ block: 'center' }); log('Scrolling link into view'); } catch (err) { log('scrollIntoView failed', err); } | |
| log('Clicking reviews link'); | |
| link.click(); | |
| await sleep(2000); | |
| } | |
| const dialog = await waitFor('div[role="dialog"]', 40, 300); | |
| log('Dialog detected?', !!dialog); | |
| return dialog || document; | |
| } | |
| function clickWithEvents(node) { | |
| if (!node) return false; | |
| log('Attempting clickWithEvents on', node); | |
| try { node.focus({ preventScroll: true }); } catch (err) { log('focus failed', err); } | |
| const events = [ | |
| new PointerEvent('pointerdown', { bubbles: true, pointerType: 'mouse', button: 0 }), | |
| new MouseEvent('mousedown', { bubbles: true, button: 0 }), | |
| new PointerEvent('pointerup', { bubbles: true, pointerType: 'mouse', button: 0 }), | |
| new MouseEvent('mouseup', { bubbles: true, button: 0 }), | |
| ]; | |
| events.forEach(evt => { try { node.dispatchEvent(evt); } catch (err) { log('dispatch failed', evt.type, err); } }); | |
| try { | |
| node.click(); | |
| log('click() invoked'); | |
| } catch (err) { | |
| log('click() failed', err); | |
| return false; | |
| } | |
| return true; | |
| } | |
| function findCandidates(selector) { | |
| const nodes = Array.from(document.querySelectorAll(selector)); | |
| log('Found', nodes.length, 'candidates for', selector); | |
| return nodes; | |
| } | |
| async function selectNewestFromGroup() { | |
| log('Selecting newest from radiogroup'); | |
| const group = await waitFor('div[role="radiogroup"][aria-label="Sort reviews"]', 40, 300); | |
| if (!group) return false; | |
| let candidates = group.querySelectorAll('[data-sort="2"]'); | |
| if (!candidates.length) candidates = group.querySelectorAll('[role="radio"]'); | |
| candidates = Array.from(candidates); | |
| log('Radiogroup candidates', candidates.length); | |
| for (const node of candidates) { | |
| const label = ((node.textContent || '') + ' ' + (node.getAttribute('aria-label') || '')).toLowerCase(); | |
| const matches = label.includes('newest') || node.getAttribute('data-sort') === '2'; | |
| log('Checking candidate', node, 'matches?', matches, 'aria-checked', node.getAttribute('aria-checked')); | |
| if (!matches) continue; | |
| if (node.getAttribute('aria-checked') === 'true') { | |
| log('Already newest'); | |
| return true; | |
| } | |
| if (clickWithEvents(node)) { | |
| await sleep(500); | |
| const selected = node.getAttribute('aria-checked') === 'true'; | |
| log('After click aria-checked', node.getAttribute('aria-checked')); | |
| if (selected) return true; | |
| } | |
| } | |
| log('Radiogroup selection failed'); | |
| return false; | |
| } | |
| async function selectNewestFromTabs() { | |
| log('Selecting newest from tabs'); | |
| let candidates = document.querySelectorAll('[data-sort="2"]'); | |
| if (!candidates.length) candidates = document.querySelectorAll('[role="tab"]'); | |
| candidates = Array.from(candidates); | |
| log('Tab candidates', candidates.length); | |
| for (const tab of candidates) { | |
| const label = ((tab.textContent || '') + ' ' + (tab.getAttribute('aria-label') || '')).toLowerCase(); | |
| const matches = label.includes('newest') || tab.getAttribute('data-sort') === '2'; | |
| log('Tab candidate', tab, 'matches?', matches, 'aria-selected', tab.getAttribute('aria-selected')); | |
| if (!matches) continue; | |
| if (tab.getAttribute('aria-selected') === 'true') { | |
| log('Tab already selected'); | |
| return true; | |
| } | |
| if (clickWithEvents(tab)) { | |
| await sleep(500); | |
| const selected = tab.getAttribute('aria-selected') === 'true'; | |
| log('After click aria-selected', tab.getAttribute('aria-selected')); | |
| if (selected) return true; | |
| } | |
| } | |
| log('Tab selection failed'); | |
| return false; | |
| } | |
| async function ensureNewest() { | |
| log('Ensuring newest sort'); | |
| let selected = await selectNewestFromGroup(); | |
| if (!selected) selected = await selectNewestFromTabs(); | |
| log('Newest selected?', selected); | |
| return selected; | |
| } | |
| async function findScrollContainer() { | |
| let container = await waitFor('div.RVCQse', 60, 300); | |
| if (!container) container = await waitFor('[jsname="fk8dgd"]', 20, 300); | |
| if (!container) container = await waitFor('[role="main"]', 20, 300); | |
| log('Scroll container found?', !!container); | |
| return container; | |
| } | |
| async function scrollContainer(container) { | |
| for (let i = 0; i < 40; i += 1) { | |
| if (!document.body.contains(container)) { | |
| log('Container removed, stopping scroll'); | |
| return; | |
| } | |
| const step = Math.max(400, container.clientHeight || 800); | |
| container.scrollBy(0, step); | |
| log('Scrolled step', i + 1, 'scrollTop now', container.scrollTop); | |
| await sleep(800); | |
| } | |
| } | |
| async function run() { | |
| try { | |
| await ensureDialog(); | |
| await ensureNewest(); | |
| const container = await findScrollContainer(); | |
| if (!container) return; | |
| container.scrollTop = 0; | |
| await scrollContainer(container); | |
| } catch (error) { | |
| console.error('[SB][remote] error', error); | |
| } | |
| } | |
| async function start() { | |
| if (window.__sbRemoteRunning) return false; | |
| window.__sbRemoteRunning = true; | |
| try { | |
| await run(); | |
| } finally { | |
| window.__sbRemoteRunning = false; | |
| } | |
| return true; | |
| } | |
| window.__sbRemoteStart = start; | |
| window.__sbRemoteLoaded = true; | |
| start(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment