camoufox-mcp-server
Version:
MCP server for browser automation using Camoufox - a privacy-focused Firefox fork with advanced anti-detection features
90 lines (89 loc) • 3.63 kB
JavaScript
import { redactUrl } from "../utils.js";
export async function buildOutlinePayload(page, response, maxItems, selector) {
const extracted = await page.evaluate(({ maxOutlineItems, cssSelector }) => {
const root = cssSelector
? document.querySelector(cssSelector)
: document.body ?? document.documentElement;
if (!root) {
return { headings: [], landmarks: [], description: undefined, truncated: false, found: false };
}
function cssIdent(value) {
if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
return CSS.escape(value);
}
return value.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
}
function selectorFor(element) {
if (element.id) {
return `#${cssIdent(element.id)}`;
}
const path = [];
let current = element;
while (current && current !== document.documentElement && path.length < 8) {
let part = current.tagName.toLowerCase();
const parent = current.parentElement;
if (parent) {
const currentTagName = current.tagName;
const sameTagSiblings = Array.from(parent.children).filter((child) => child.tagName === currentTagName);
if (sameTagSiblings.length > 1) {
part += `:nth-of-type(${sameTagSiblings.indexOf(current) + 1})`;
}
}
path.unshift(part);
current = parent;
}
return path.join(" > ");
}
const headingCandidates = Array.from(root.querySelectorAll("h1, h2, h3, h4, h5, h6"));
const headings = [];
let truncated = false;
for (const heading of headingCandidates) {
const text = (heading.textContent ?? "").replace(/\s+/g, " ").trim();
if (!text) {
continue;
}
if (headings.length >= maxOutlineItems) {
truncated = true;
break;
}
headings.push({
level: Number.parseInt(heading.tagName.slice(1), 10),
text: text.slice(0, 500),
selector: selectorFor(heading),
});
}
const landmarkCandidates = Array.from(root.querySelectorAll("[role], header, nav, main, aside, footer, form"));
const landmarks = [];
for (const landmark of landmarkCandidates) {
if (landmarks.length >= maxOutlineItems) {
truncated = true;
break;
}
const role = landmark.getAttribute("role") ?? landmark.tagName.toLowerCase();
if (role && !landmarks.includes(role)) {
landmarks.push(role);
}
}
const description = document.querySelector("meta[name='description']")?.content;
return {
headings,
landmarks,
description: description?.slice(0, 1000),
truncated,
found: true,
};
}, { maxOutlineItems: maxItems, cssSelector: selector });
return {
url: redactUrl(page.url()),
title: await page.title(),
status: response?.status(),
contentType: response?.headers()["content-type"],
description: extracted.description,
selector,
selectorFound: extracted.found,
headings: extracted.headings,
landmarks: extracted.landmarks,
truncated: extracted.truncated,
maxItems,
};
}