### Start WebCap Server Source: https://github.com/blacklanternsecurity/webcap/blob/master/README.md Launches the built-in web interface for WebCap, accessible via localhost. ```bash webcap server ``` -------------------------------- ### Install WebCap via pipx Source: https://github.com/blacklanternsecurity/webcap/blob/master/README.md Installs the WebCap CLI tool using pipx to ensure an isolated environment. ```bash pipx install webcap ``` -------------------------------- ### WebCap CLI: Launch Web Server Source: https://context7.com/blacklanternsecurity/webcap/llms.txt The `webcap server` command starts a FastAPI-based web interface for browsing captured screenshots. It serves the screenshot directory with an interactive GUI, allowing easy visual review and analysis. The server can be configured with custom listen addresses, ports, and screenshot directories. ```bash # Start the server with default settings (localhost:8000) webcap server # Custom listen address and port webcap server --listen-address 0.0.0.0 --listen-port 9000 # Serve screenshots from a specific directory webcap server -d ./my_screenshots # Enable auto-reload for development webcap server --auto-reload -d ./screenshots # Typical workflow: scan URLs then browse results webcap scan urls.txt -o ./output webcap server -d ./output # Browse to http://localhost:8000 ``` -------------------------------- ### Examine WebScreenshot Class Details with Python Source: https://context7.com/blacklanternsecurity/webcap/llms.txt Illustrates how to use the `WebScreenshot` class from the webcap library to capture and examine detailed information about a single URL. This example shows how to initialize the `Browser` with various options (DOM, JavaScript, network requests/responses, OCR) and then capture a screenshot. It demonstrates accessing core properties like URL, status code, title, and hostname, as well as raw image data, perception hashes, DOM content, JavaScript snippets, navigation history, network requests/responses, and OCR text. The example also shows how to export all captured data as JSON. ```python import asyncio from webcap import Browser async def examine_screenshot(): browser = Browser( dom=True, javascript=True, requests=True, responses=True, ocr=True ) await browser.start() webscreenshot = await browser.screenshot("https://httpbin.org/html") # Core properties print(f"Original URL: {webscreenshot.url}") print(f"Final URL: {webscreenshot.final_url}") print(f"Status Code: {webscreenshot.status_code}") print(f"Page Title: {webscreenshot.title}") print(f"Hostname: {webscreenshot.hostname}") print(f"Filename: {webscreenshot.filename}") # Screenshot data image_bytes = webscreenshot.blob # Raw PNG bytes image_base64 = webscreenshot.base64 # Base64-encoded string # Perception hash (useful for detecting similar pages) phash = await webscreenshot.perception_hash() print(f"Perception Hash: {phash}") # DOM capture if webscreenshot.dom: print(f"DOM length: {len(webscreenshot.dom)} characters") # webscreenshot.dom contains full HTML including dynamically-loaded content # JavaScript capture print(f"JavaScript snippets captured: {len(webscreenshot.scripts)}") for script in list(webscreenshot.scripts)[:3]: # First 3 scripts print(f" Script URL: {script.url or 'inline'}") print(f" Length: {len(script.body)} bytes") # Network history print(f"Navigation history entries: {len(webscreenshot.navigation_history)}") for entry in webscreenshot.navigation_history: print(f" [{entry['status']}] {entry['mimeType']} - {entry['url']}") # HTTP Requests (when requests=True) print(f"HTTP Requests captured: {len(webscreenshot.requests)}") for req in webscreenshot.requests[:3]: print(f" {req.get('method', 'GET')} {req.get('url', '')}") # HTTP Responses (when responses=True) print(f"HTTP Responses captured: {len(webscreenshot.responses)}") for resp in webscreenshot.responses[:3]: print(f" [{resp['status']}] {resp['mimeType']} - {resp['url']}") if resp.get('responseBody'): print(f" Body: {len(resp['responseBody'])} bytes") # OCR text extraction (requires tesseract-ocr) ocr_text = await webscreenshot.ocr() print(f"OCR extracted text: {ocr_text[:200]}...") # Export everything as JSON json_data = await webscreenshot.json() # Returns dict with: url, final_url, title, status_code, navigation_history, # perception_hash, dom, javascript, requests, responses, image_base64, ocr await browser.stop() asyncio.run(examine_screenshot()) ``` -------------------------------- ### GET /screenshots/index.json Source: https://context7.com/blacklanternsecurity/webcap/llms.txt Retrieves the index file containing metadata for all captured screenshots. ```APIDOC ## GET /screenshots/index.json ### Description Returns a JSON object mapping screenshot filenames to their respective metadata, including URL, status code, and page title. ### Method GET ### Endpoint /screenshots/index.json ### Response #### Success Response (200) - **index** (object) - A dictionary where keys are filenames and values contain metadata (url, final_url, hash, status, title). #### Response Example { "http---example.com.png": { "url": "http://example.com", "final_url": "https://example.com/", "hash": "c3c3c3c3c3c3c3c3", "status": 200, "title": "Example Domain" } } ``` -------------------------------- ### GET /screenshots/ Source: https://context7.com/blacklanternsecurity/webcap/llms.txt Retrieves the raw image file for a captured screenshot. ```APIDOC ## GET /screenshots/{file} ### Description Downloads the binary PNG screenshot file associated with the capture. ### Method GET ### Endpoint /screenshots/{file} ### Parameters #### Path Parameters - **file** (string) - Required - The filename of the PNG screenshot (e.g., http---example.com.png). ### Response #### Success Response (200) - **image/png** (binary) - The raw image data. ``` -------------------------------- ### Capture Web Data via CLI Source: https://github.com/blacklanternsecurity/webcap/blob/master/README.md Examples of using the webcap scan command to capture screenshots, DOM, network logs, JavaScript, and OCR text, outputting results to JSON. ```bash # Capture screenshots of all URLs in urls.txt webcap scan urls.txt -o ./my_screenshots # Output to JSON, and include the fully-rendered DOM webcap scan urls.txt --json --dom | jq # Capture requests and responses webcap scan urls.txt --json --requests --responses | jq # Capture javascript webcap scan urls.txt --json --javascript | jq # Extract text from screenshots webcap scan urls.txt --json --ocr | jq ``` -------------------------------- ### Screenshot Multiple URLs Concurrently with Python Source: https://context7.com/blacklanternsecurity/webcap/llms.txt Demonstrates how to use the `screenshot_urls` method from the webcap library to process a list of URLs concurrently. It utilizes an async generator pattern for efficient batch processing, allowing results to be yielded as they complete. The example shows how to configure browser threads, timeouts, and access various data points from the `WebScreenshot` object, including status codes, titles, perception hashes, navigation history, and saving the screenshot and JSON data. ```python import asyncio import orjson from webcap import Browser async def scan_urls(): # List of URLs to screenshot (can also be read from a file) urls = [ "https://example.com", "https://httpbin.org/get", "https://httpbin.org/headers", ] browser = Browser( threads=10, # Process 10 URLs concurrently timeout=15, dom=True, responses=True ) await browser.start() # Iterate through screenshots as they complete async for url, webscreenshot in browser.screenshot_urls(urls): if webscreenshot is None: print(f"Failed: {url}") continue # Process each screenshot print(f"[{webscreenshot.status_code}] {webscreenshot.title[:40]:<40} {webscreenshot.final_url}") # Get perception hash for image comparison phash = await webscreenshot.perception_hash() print(f" Perception hash: {phash}") # Access network history print(f" Navigation steps: {len(webscreenshot.navigation_history)}") for nav in webscreenshot.navigation_history: print(f" -> [{nav['status']}] {nav['url']}") # Save individual screenshot filename = f"screenshot_{webscreenshot.hostname}.png" with open(filename, "wb") as f: f.write(webscreenshot.blob) # Export full data as JSON json_output = await webscreenshot.json() with open(f"{webscreenshot.hostname}.json", "wb") as f: f.write(orjson.dumps(json_output, option=orjson.OPT_INDENT_2)) await browser.stop() asyncio.run(scan_urls()) ``` -------------------------------- ### GET /screenshots/json/.json Source: https://context7.com/blacklanternsecurity/webcap/llms.txt Retrieves the full captured data for a specific screenshot, including DOM, JavaScript, and network logs. ```APIDOC ## GET /screenshots/json/{id}.json ### Description Fetches detailed capture data for a specific URL, including the full DOM, navigation history, and network request/response logs. ### Method GET ### Endpoint /screenshots/json/{id}.json ### Parameters #### Path Parameters - **id** (string) - Required - The unique identifier/filename of the screenshot capture. ### Response #### Success Response (200) - **data** (object) - Full capture details including dom, javascript, requests, and responses. #### Response Example { "url": "http://example.com", "status_code": 200, "dom": "...", "javascript": [{"url": "...", "body": "..."}], "requests": [], "responses": [] } ``` -------------------------------- ### Capture Web Page Data with WebCap Browser Class (Python) Source: https://context7.com/blacklanternsecurity/webcap/llms.txt Demonstrates how to use the `Browser` class in WebCap to capture screenshots and associated data like DOM, JavaScript, and network requests. It initializes the browser with various options, takes a screenshot, accesses the captured data, saves the image, and retrieves JSON output. Requires a Chrome installation and the webcap library. ```python import asyncio from webcap import Browser async def main(): # Create a browser instance with custom options browser = Browser( threads=15, # Number of concurrent tabs resolution="1440x900", # Screenshot resolution timeout=10, # Request timeout in seconds delay=3.0, # Delay before capturing (wait for page load) full_page=False, # Capture full scrollable page dom=True, # Capture fully-rendered DOM javascript=True, # Capture all JavaScript requests=True, # Capture HTTP request bodies responses=True, # Capture HTTP response bodies base64=True, # Include base64 image in JSON output ocr=False, # Extract text from screenshots proxy=None, # HTTP proxy (e.g., "http://proxy:8080") user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" ) # Start the browser (launches Chrome and connects via CDP) await browser.start() # Take a single screenshot webscreenshot = await browser.screenshot("https://example.com") # Access screenshot data print(f"URL: {webscreenshot.url}") print(f"Final URL: {webscreenshot.final_url}") print(f"Status Code: {webscreenshot.status_code}") print(f"Title: {webscreenshot.title}") print(f"DOM length: {len(webscreenshot.dom) if webscreenshot.dom else 0}") print(f"JavaScript snippets: {len(webscreenshot.scripts)}") # Save screenshot to file with open("screenshot.png", "wb") as f: f.write(webscreenshot.blob) # Get JSON output with all captured data json_data = await webscreenshot.json() # json_data contains: url, final_url, title, status_code, navigation_history, # perception_hash, dom, javascript, requests, responses, image_base64, ocr # Stop the browser await browser.stop() asyncio.run(main()) ``` -------------------------------- ### Use WebCap as a Python Library Source: https://github.com/blacklanternsecurity/webcap/blob/master/README.md Demonstrates how to programmatically control the browser instance to capture screenshots using the WebCap Python API. ```python import base64 from webcap import Browser async def main(): # create a browser instance browser = Browser() # start the browser await browser.start() # take a screenshot webscreenshot = await browser.screenshot("http://example.com") # save the screenshot to a file with open("screenshot.png", "wb") as f: f.write(webscreenshot.blob) # stop the browser await browser.stop() if __name__ == "__main__": import asyncio asyncio.run(main()) ``` -------------------------------- ### Track HTTP Redirects and Navigation History with WebCap Source: https://context7.com/blacklanternsecurity/webcap/llms.txt Demonstrates how to use the WebCap Browser class to record full navigation chains, including status codes, MIME types, and final destination URLs. This is useful for analyzing redirect behavior in phishing or URL shortening scenarios. ```python import asyncio from webcap import Browser async def track_redirects(): browser = Browser(responses=True) await browser.start() # Example URL that redirects webscreenshot = await browser.screenshot("http://httpbin.org/redirect/3") # Navigation history captures every redirect print("Navigation History:") for i, nav in enumerate(webscreenshot.navigation_history): status = nav['status'] url = nav['url'] mime = nav['mimeType'] location = nav.get('location', '') if location: print(f" {i+1}. [{status}] {url}") print(f" -> Redirects to: {location}") else: print(f" {i+1}. [{status}] {url} (final)") # Final URL after all redirects print(f"\nOriginal URL: {webscreenshot.url}") print(f"Final URL: {webscreenshot.final_url}") print(f"Final Status: {webscreenshot.status_code}") # Full response data for each hop (when responses=True) print(f"\nDetailed responses: {len(webscreenshot.responses)}") for resp in webscreenshot.responses: if resp['type'] == 'document': print(f" [{resp['status']}] {resp['url']}") print(f" Protocol: {resp.get('protocol', 'unknown')}") print(f" Remote: {resp.get('remoteIPAddress', '')}:{resp.get('remotePort', '')}") await browser.stop() asyncio.run(track_redirects()) ``` -------------------------------- ### Configure WebCap Browser Defaults Source: https://context7.com/blacklanternsecurity/webcap/llms.txt Explains how to access and override default browser settings such as user agents, timeouts, and resource filtering. These settings can be modified globally or per-instance in the Browser constructor. ```python # Default configuration values (webcap/defaults.py) from webcap import defaults print(f"User Agent: {defaults.user_agent}") print(f"Resolution: {defaults.resolution}") print(f"Threads: {defaults.threads}") print(f"Timeout: {defaults.timeout} seconds") print(f"Delay: {defaults.delay} seconds") print(f"Ignored Types: {defaults.ignored_types}") # Override in Browser constructor from webcap import Browser browser = Browser( threads=20, resolution="1920x1080", timeout=30, delay=5.0, user_agent="CustomBot/2.0", ignore_types=["Image", "Media"] ) ``` -------------------------------- ### WebCap Server API and Data Structure Source: https://context7.com/blacklanternsecurity/webcap/llms.txt The WebCap server exposes a FastAPI application serving a React-based frontend and screenshot files. It expects an output directory (via environment variable or flag) and provides endpoints for the web interface, direct file access, and static assets. The documentation details the structure of `index.json` and individual JSON files for captured screenshots. ```python # Server internals (webcap/server/server.py) # The server expects OUTPUT_DIR environment variable or -d flag # API Endpoints: # GET / - Web interface (React app) # GET /screenshots/ - Direct access to screenshot files # GET /static/ - Static assets (React, CSS) # Screenshot directory structure created by webcap scan: # output/ # index.json - Index of all screenshots with metadata # json/ # .json - Full JSON data for each screenshot # .png - Screenshot image files # index.json format: # { # "http---example.com.png": { # "url": "http://example.com", # "final_url": "https://example.com/", # "hash": "c3c3c3c3c3c3c3c3", # Perception hash # "status": 200, # "title": "Example Domain" # } # } # Individual JSON file format (.json): # { # "url": "http://example.com", # "final_url": "https://example.com/", # "title": "Example Domain", # "status_code": 200, # "navigation_history": [...], # "perception_hash": "c3c3c3c3c3c3c3c3", # "dom": "...", # "javascript": [{"url": "...", "body": "..."}], # "requests": [...], # "responses": [...] # } # Access via curl: curl http://localhost:8000/screenshots/index.json | jq curl http://localhost:8000/screenshots/json/http---example.com.png.json | jq curl -o screenshot.png http://localhost:8000/screenshots/http---example.com.png ``` -------------------------------- ### WebCap CLI: Batch Screenshot Capture Source: https://context7.com/blacklanternsecurity/webcap/llms.txt The `webcap scan` command facilitates batch screenshotting of URLs from files or direct arguments. It supports various output formats including JSON with optional DOM, JavaScript, request/response capture, and custom resolutions. Screenshots are saved to a specified directory with an index.json. ```bash # Basic screenshot capture - URLs directly or from file webcap scan https://example.com https://httpbin.org/get webcap scan urls.txt -o ./my_screenshots # Full-page capture at custom resolution webcap scan urls.txt --full-page --resolution 1920x1080 -o ./fullpage # JSON output with all captured data webcap scan urls.txt --json --dom --javascript --requests --responses | jq # JSON output with base64 image and OCR text extraction webcap scan https://example.com --json --base64 --ocr | jq # Performance tuning webcap scan urls.txt -t 20 --timeout 30 --delay 5.0 -o ./output # Custom user agent and proxy webcap scan urls.txt \ --user-agent "CustomBot/1.0" \ --proxy http://proxy.example.com:8080 \ -o ./output # Add custom headers webcap scan urls.txt \ -H "Authorization: Bearer token123" \ -H "X-Custom-Header: value" \ -o ./output # Skip screenshot files, only output JSON (useful for data extraction) webcap scan urls.txt --no-screenshots --json --dom --responses | jq # Ignore specific resource types in network capture webcap scan urls.txt --json --responses --ignore-types Image --ignore-types Font | jq ``` -------------------------------- ### Extract JavaScript Snippets from Web Pages Source: https://context7.com/blacklanternsecurity/webcap/llms.txt Shows how to capture and inspect both inline and external JavaScript executed on a page. This functionality is critical for security analysis and detecting malicious script execution. ```python import asyncio from webcap import Browser async def extract_javascript(): browser = Browser(javascript=True) await browser.start() webscreenshot = await browser.screenshot("https://example.com") print(f"Captured {len(webscreenshot.scripts)} JavaScript snippets") for i, script in enumerate(webscreenshot.scripts): print(f"\n--- Script {i+1} ---") print(f"URL: {script.url or 'inline'}") print(f"Size: {len(script.body)} bytes") print(f"Preview: {script.body[:200]}...") # JSON representation for export script_json = script.json # Full JSON export includes all scripts json_data = await webscreenshot.json() await browser.stop() asyncio.run(extract_javascript()) ``` -------------------------------- ### React Pagination Component Source: https://github.com/blacklanternsecurity/webcap/blob/master/webcap/server/templates/index.html A React component for handling pagination. It displays current page, total pages, and navigation buttons (First, Last, page numbers). It also includes a PageSizeSelector for controlling items per page. Dependencies: React. ```javascript function PageSizeSelector({ onPageSizeChange }) { return ( ); } function Pagination({ currentPage, totalPages, onPageChange, onPageSizeChange }) { const pageNumbers = []; const maxPageButtons = 5; // Number of page buttons to display let startPage = Math.max(1, currentPage - Math.floor(maxPageButtons / 2)); let endPage = Math.min(totalPages, startPage + maxPageButtons - 1); if (endPage - startPage < maxPageButtons - 1) { startPage = Math.max(1, endPage - maxPageButtons + 1); } for (let i = startPage; i <= endPage; i++) { pageNumbers.push(i); } return (
{pageNumbers.map(number => ( ))}
); } ``` -------------------------------- ### React App Component for Screenshot Management Source: https://github.com/blacklanternsecurity/webcap/blob/master/webcap/server/templates/index.html The main App component in React manages the state for screenshots, search queries, pagination, sorting, and modal visibility. It fetches screenshot data from an API, filters and sorts the results based on user input, and renders pagination controls and detail modals. ```javascript const React = window.React; const { useState, useEffect } = React; const ReactDOM = window.ReactDOM; function App() { const [screenshots, setScreenshots] = useState([]); const [searchQuery, setSearchQuery] = useState(''); const [currentPage, setCurrentPage] = useState(1); const [screenshotsPerPage, setScreenshotsPerPage] = useState(12); const [sortConfig, setSortConfig] = useState({ key: 'hash', direction: 'descending' }); const [showHashModal, setShowHashModal] = useState(false); const [selectedHashes, setSelectedHashes] = useState(new Set()); const [selectedScreenshot, setSelectedScreenshot] = useState(null); useEffect(() => { fetch("/screenshots/index.json") .then(response => response.json()) .then(data => setScreenshots(data)); }, []); useEffect(() => { setCurrentPage(1); }, [selectedHashes, searchQuery]); const filteredScreenshots = Object.entries(screenshots) .filter(([id, value]) => (selectedHashes.size === 0 || selectedHashes.has(value.hash)) && (value.title.toLowerCase().includes(searchQuery.toLowerCase()) || value.url.toLowerCase().includes(searchQuery.toLowerCase())) ); const sortedScreenshots = [...filteredScreenshots].sort((a, b) => { const [, screenshotA] = a; const [, screenshotB] = b; if (screenshotA.hash < screenshotB.hash) { return sortConfig.direction === 'ascending' ? -1 : 1; } if (screenshotA.hash > screenshotB.hash) { return sortConfig.direction === 'ascending' ? 1 : -1; } return 0; }); const indexOfLastScreenshot = currentPage * screenshotsPerPage; const indexOfFirstScreenshot = indexOfLastScreenshot - screenshotsPerPage; const currentScreenshots = sortedScreenshots.slice( indexOfFirstScreenshot, screenshotsPerPage === -1 ? sortedScreenshots.length : indexOfLastScreenshot ); const totalPages = Math.ceil(filteredScreenshots.length / screenshotsPerPage); const handlePageChange = (pageNumber) => { setCurrentPage(pageNumber); }; const handlePageSizeChange = (event) => { const newSize = Number(event.target.value); setScreenshotsPerPage(newSize); setCurrentPage(1); // Reset to first page on page size change }; const handleSort = (key) => { let direction = 'ascending'; if (sortConfig && sortConfig.key === key && sortConfig.direction === 'ascending') { direction = 'descending'; } setSortConfig({ key, direction }); }; const showScreenshotDetail = (id) => { setSelectedScreenshot(id); }; return (
Webcap Logo
setShowHashModal(true)} />
{showHashModal && ( setShowHashModal(false)} selectedHashes={selectedHashes} setSelectedHashes={setSelectedHashes} /> )} {selectedScreenshot && ( setSelectedScreenshot(null)} /> )}
); } ``` -------------------------------- ### React ScreenshotList Component Source: https://github.com/blacklanternsecurity/webcap/blob/master/webcap/server/templates/index.html Renders a list of screenshot items. Each screenshot is displayed with its status, title, and URL. The component supports sorting and allows users to click on a screenshot to view its details. It uses a grid layout for responsive display. ```javascript function ScreenshotList({ screenshots, onSort, onScreenshotClick }) { return (
{screenshots.map(([id, screenshot]) => (
onScreenshotClick(id)} > {screenshot.url}
= 200 && screenshot.status < 300 ? 'bg-green-900 text-green-300' : screenshot.status >= 300 && screenshot.status < 400 ? 'bg-blue-900 text-blue-300' : screenshot.status >= 400 ? 'bg-red-900 text-red-300' : 'bg-gray-700 text-gray-300' }`}> {screenshot.status}
{screenshot.title}
onSearch(e.target.value)} /> ); } ``` -------------------------------- ### React Perception Widget Component Source: https://github.com/blacklanternsecurity/webcap/blob/master/webcap/server/templates/index.html A React component that displays a preview of the currently selected perceptual hash group and provides navigation controls. It allows users to cycle through hash groups and clear the selection. Dependencies: React, CSS for styling. ```javascript function PerceptionWidget({ screenshots, selectedHashes, setSelectedHashes, onShowModal }) { // Group and sort hashes like in the modal const hashGroups = Object.entries(screenshots) .reduce((acc, [id, value]) => { if (!acc[value.hash]) { acc[value.hash] = []; } acc[value.hash].push({ id, ...value }); return acc; }, {}); const sortedHashes = Object.entries(hashGroups) .sort(([, groupA], [, groupB]) => groupB.length - groupA.length) .map(([hash]) => hash); // Get current hash index const currentHashIndex = selectedHashes.size === 1 ? sortedHashes.indexOf(Array.from(selectedHashes)[0]) : -1; const navigateHash = (direction) => { let newIndex; if (currentHashIndex === -1) { newIndex = direction === 'next' ? 0 : sortedHashes.length - 1; } else { newIndex = direction === 'next' ? (currentHashIndex + 1) % sortedHashes.length : (currentHashIndex - 1 + sortedHashes.length) % sortedHashes.length; } setSelectedHashes(new Set([sortedHashes[newIndex]])); }; const clearSelection = () => { setSelectedHashes(new Set()); }; // Get current group info const currentHash = currentHashIndex !== -1 ? sortedHashes[currentHashIndex] : null; const currentGroup = currentHash ? hashGroups[currentHash] : null; return (
{currentGroup ? (
Preview
{currentGroup[0].title || 'Untitled'}
{`${currentGroup.length} similar`}
) : (
No perception filter active
)}
{sortedHashGroups.map(([hash, group]) => (
toggleHash(hash)} >
{ e.stopPropagation(); toggleHash(hash); }} className="hash-checkbox" /> Representative thumbnail
{group[0].title || 'Untitled'}
{`${group.length} similar`}
))}
); } ``` -------------------------------- ### Render Screenshot Detail Modal Component Source: https://github.com/blacklanternsecurity/webcap/blob/master/webcap/server/templates/index.html A React functional component that fetches and displays detailed screenshot information, including navigation history and status codes. It uses the useEffect hook to retrieve JSON data and renders a modal interface with URL links and status-coded badges. ```javascript function ScreenshotDetailModal({ screenshotId, onClose }) { const [details, setDetails] = useState(null); useEffect(() => { fetch(`/screenshots/json/${screenshotId}.json`) .then(response => response.json()) .then(data => setDetails(data)); }, [screenshotId]); if (!details) return null; const finalNavigation = details.navigation_history .filter(nav => nav.status === 200) .pop(); const httpTitle = finalNavigation?.title || details.title || 'Untitled'; return ( ); } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.