Release v0.0.4: BOM spatial map scraping and radar cache reliability.

Align selectors and workflow with the step-through scrubber, share scrub display
timestamp parsing via IScrubDisplayTimestampParser, fix pause post-click label
verification, and stop cancelling background cache updates when the HTTP request
ends. Version bumped to 0.0.4.
This commit is contained in:
2026-05-07 05:23:18 +10:00
parent 0b6d266e79
commit a54061f757
14 changed files with 462 additions and 328 deletions
+15 -10
View File
@@ -61,11 +61,9 @@
"Name": "Search Button",
"Selectors": [
"button[data-testid='searchLabel']",
"button[aria-label='Search for a location']",
"button.search-location__trigger-button",
"button.bom-button:has(span.bom-button__label:has-text('Search for a location'))",
"button:has-text('Search for a location')",
"[data-testid='showSearchModal'] button"
"button[aria-label='Search for a location'].search-location__trigger-button",
"button.search-location__trigger-button.bom-button:has(span.bom-button__label:has-text('Search for a location'))"
],
"TimeoutMs": 10000,
"Required": true,
@@ -137,7 +135,10 @@
},
"PlayPauseButton": {
"Name": "Play Pause Button",
"Selectors": ["button[data-testid='bom-time-scrub-play-pause']"],
"Selectors": [
"button[data-testid='bom-time-scrub-step-through']",
"button[data-testid='bom-time-scrub-play-pause']"
],
"TimeoutMs": 5000,
"Required": true
},
@@ -155,7 +156,11 @@
},
"StepForwardButton": {
"Name": "Step Forward Button",
"Selectors": ["button[data-testid='bom-scrub-utils__right__step-forward']"],
"Selectors": [
"button[data-testid='bom-time-scrub-step-through']",
"button.bom-scrub-action--step-through",
"button[data-testid='bom-scrub-utils__right__step-forward']"
],
"TimeoutMs": 5000,
"Required": true
},
@@ -184,9 +189,9 @@
"ExtractSearchResultsFallback": "() => { const results = Array.from(document.querySelectorAll('li.bom-linklist__item[role=\"listitem\"]')); return results.map(r => r.textContent || ''); }",
"WaitForMapCanvas": "() => { const canvas = document.querySelector('.esri-view-surface canvas'); return canvas && canvas.width > 0 && canvas.height > 0 && canvas.offsetWidth > 0 && canvas.offsetHeight > 0; }",
"WaitForEsriView": "() => { try { const elements = document.querySelectorAll('.esri-view'); for (let el of elements) { if (el.__view && el.__view.ready) { return true; } } } catch(e) {} return false; }",
"CheckActiveFrameSegment": "() => { const segments = Array.from(document.querySelectorAll('[data-testid=\"bom-scrub-segment\"]')); const activeSegment = segments.find(s => { const style = window.getComputedStyle(s); return style.backgroundColor !== 'rgb(148, 148, 148)' && style.backgroundColor !== 'rgb(148, 148, 148)'; }); return activeSegment && activeSegment.getAttribute('data-id') === '0'; }",
"CheckActiveFrameSegment": "() => { const segments = Array.from(document.querySelectorAll('[data-testid=\"bom-scrub-segment\"]')); if (segments.length === 0) return true; const activeSegment = segments.find(s => { const style = window.getComputedStyle(s); return style.backgroundColor !== 'rgb(148, 148, 148)' && style.backgroundColor !== 'rgb(148, 148, 148)'; }); return activeSegment && activeSegment.getAttribute('data-id') === '0'; }",
"WaitForMapContainer": "() => { const container = document.querySelector('.esri-view-surface'); return container && container.offsetWidth > 0 && container.offsetHeight > 0; }",
"ExtractFrameInfo": "() => { const segments = Array.from(document.querySelectorAll('[data-testid=\"bom-scrub-segment\"]')); const now = new Date(); return segments.map((seg, index) => { const ariaLabel = seg.getAttribute('aria-label') || ''; let minutes = null; const timestampMatch = ariaLabel.match(/(?:[A-Za-z]+\\s+)?(\\d{1,2})\\s+([A-Za-z]{3}),?\\s+(\\d{1,2}):(\\d{2})\\s+(am|pm)/i); if (timestampMatch) { try { const day = parseInt(timestampMatch[1]); const monthStr = timestampMatch[2]; const hour12 = parseInt(timestampMatch[3]); const minute = parseInt(timestampMatch[4]); const ampm = timestampMatch[5].toLowerCase(); const monthNames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']; const month = monthNames.indexOf(monthStr.toLowerCase()); if (month >= 0) { let hour24 = hour12; if (ampm === 'pm' && hour12 !== 12) hour24 += 12; if (ampm === 'am' && hour12 === 12) hour24 = 0; const year = now.getFullYear(); const frameTime = new Date(year, month, day, hour24, minute); if (frameTime > now) frameTime.setFullYear(year - 1); const diffMs = now - frameTime; minutes = Math.round(diffMs / (1000 * 60)); if (minutes < 0 || minutes > 120) minutes = null; } } catch(e) {} } return { index: index, minutesAgo: minutes }; }); }",
"ExtractFrameInfo": "() => { const segments = Array.from(document.querySelectorAll('[data-testid=\"bom-scrub-segment\"]')); const now = new Date(); return segments.map((seg, index) => { const ariaLabel = seg.getAttribute('aria-label') || ''; let minutes = null; const timestampMatch = ariaLabel.match(/(?:[A-Za-z]+\\s+)?(\\d{1,2})\\s+([A-Za-z]+),?\\s+(\\d{1,2}):(\\d{2})\\s*(am|pm)/i); if (timestampMatch) { try { const day = parseInt(timestampMatch[1]); const monthStr = timestampMatch[2]; const hour12 = parseInt(timestampMatch[3]); const minute = parseInt(timestampMatch[4]); const ampm = timestampMatch[5].toLowerCase(); const monthNames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']; const month = monthNames.indexOf(monthStr.toLowerCase()); if (month >= 0) { let hour24 = hour12; if (ampm === 'pm' && hour12 !== 12) hour24 += 12; if (ampm === 'am' && hour12 === 12) hour24 = 0; const year = now.getFullYear(); const frameTime = new Date(year, month, day, hour24, minute); if (frameTime > now) frameTime.setFullYear(year - 1); const diffMs = now - frameTime; minutes = Math.round(diffMs / (1000 * 60)); if (minutes < 0 || minutes > 120) minutes = null; } } catch(e) {} } return { index: index, minutesAgo: minutes }; }); }",
"CheckModalOverlay": "() => { const bomOverlay = document.querySelector('.bom-modal-overlay--after-open'); if (bomOverlay && bomOverlay.style.display !== 'none') { return true; } const recaptchaSelectors = ['.g-recaptcha', '#g-recaptcha', '.rc-anchor-container']; for (const selector of recaptchaSelectors) { const el = document.querySelector(selector); if (el) { const style = window.getComputedStyle(el); const rect = el.getBoundingClientRect(); if (style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 200 && rect.height > 200) { return true; } } } const allForms = document.querySelectorAll('form'); for (const form of allForms) { const action = form.getAttribute('action') || ''; const id = form.getAttribute('id') || ''; if (action.includes('feedback') || id.includes('feedback')) { const style = window.getComputedStyle(form); const rect = form.getBoundingClientRect(); if (style.display !== 'none' && rect.width > 200 && rect.height > 200) { const text = form.textContent || ''; if (text.includes('reCAPTCHA') || text.includes('recaptcha') || text.includes('Tell us why')) { return true; } } } } return false; }",
"CheckModalStillVisible": "() => { const bomOverlay = document.querySelector('.bom-modal-overlay--after-open'); if (bomOverlay && bomOverlay.style.display !== 'none') return true; const allForms = document.querySelectorAll('form'); for (const form of allForms) { const action = form.getAttribute('action') || ''; const id = form.getAttribute('id') || ''; if (action.includes('feedback') || id.includes('feedback')) { const style = window.getComputedStyle(form); const rect = form.getBoundingClientRect(); if (style.display !== 'none' && rect.width > 200 && rect.height > 200) { return true; } } } return false; }",
"GetViewportSize": "() => JSON.stringify({ width: window.innerWidth, height: window.innerHeight })",
@@ -194,7 +199,7 @@
},
"TextPatterns": {
"ResultsCountPattern": "(\\d+)\\s+of\\s+(\\d+)",
"TimestampPattern": "(?:[A-Za-z]+\\s+)?\\d{1,2}\\s+[A-Za-z]{3},?\\s+\\d{1,2}:\\d{2}\\s+(?:am|pm)",
"TimestampPattern": "(?:[A-Za-z]+\\s+)?\\d{1,2}\\s+[A-Za-z]+,?\\s+\\d{1,2}:\\d{2}\\s*(?:am|pm)",
"ObservationTimePattern": "Observations:\\s*(\\d+)\\s*minutes?\\s*ago",
"ForecastTimePattern": "Forecast:\\s*(\\d+)\\s*minutes?\\s+ago",
"ForecastHourAgoPattern": "Forecast:\\s*an\\s+hour\\s+ago",
@@ -218,8 +223,8 @@
"ClickRadarLink": { "Enabled": true },
"WaitForMapReady": { "Enabled": true },
"PauseRadar": { "Enabled": true },
"ResetToFirstFrame": { "Enabled": true },
"ExtractMetadata": { "Enabled": true },
"ResetToFirstFrame": { "Enabled": true },
"CalculateMapBounds": { "Enabled": true },
"CaptureFrames": {
"Enabled": true,