// 使用 IIFE 避免重复注入时的变量冲突 (function () { // 检查是否已经注入过 if (window.__PROXYCAST_CONTENT_SCRIPT_LOADED__) { return; } window.__PROXYCAST_CONTENT_SCRIPT_LOADED__ = true; let refCounter = 0; const REF_ATTR = "proxycast-id"; function nextRefId() { refCounter += 1; return `proxycast-${refCounter}`; } function resetRefs() { refCounter = 0; document.querySelectorAll(`[${REF_ATTR}]`).forEach((el) => { el.removeAttribute(REF_ATTR); }); } function normalizeText(value) { return String(value || "").replace(/\s+/g, " ").trim(); } function isElementVisible(element) { if (!element) { return false; } const style = window.getComputedStyle(element); if ( style.display === "none" || style.visibility === "hidden" || style.opacity === "0" ) { return false; } const rect = element.getBoundingClientRect(); return rect.width > 0 && rect.height > 0; } function isInteractiveElement(element) { if (!element || !isElementVisible(element)) { return false; } const tag = element.tagName.toLowerCase(); const role = element.getAttribute("role"); if (["a", "button", "input", "textarea", "select", "option"].includes(tag)) { return true; } if ( role && [ "button", "link", "checkbox", "radio", "menuitem", "tab", "switch", "option", "searchbox", "textbox", "combobox", ].includes(role) ) { return true; } if (element.hasAttribute("onclick")) { return true; } const style = window.getComputedStyle(element); return style.cursor === "pointer"; } function interactiveLabel(element) { const tag = element.tagName.toLowerCase(); const role = element.getAttribute("role"); const text = normalizeText( element.innerText || element.value || element.placeholder || element.getAttribute("aria-label") || element.title || element.name || element.id, ); if (tag === "a") { return `链接: ${text || "无标题链接"}`; } if (tag === "button" || role === "button") { return `按钮: ${text || "无标题按钮"}`; } if (tag === "input" || tag === "textarea") { return `输入框: ${text || "未命名输入框"}`; } if (tag === "select") { return `下拉框: ${text || "未命名下拉框"}`; } return `可交互元素: ${text || tag}`; } function buildMarkdown() { resetRefs(); const lines = []; lines.push(`# ${document.title || "Untitled"}`); lines.push(`URL: ${window.location.href}`); lines.push(""); const bodyText = normalizeText(document.body ? document.body.innerText : ""); if (bodyText) { lines.push("## 页面文本"); lines.push(bodyText.slice(0, 6000)); lines.push(""); } lines.push("## 可交互元素"); const allElements = Array.from(document.querySelectorAll("*")).filter(isInteractiveElement); for (const element of allElements.slice(0, 300)) { const refId = nextRefId(); element.setAttribute(REF_ATTR, refId); lines.push(`- [${interactiveLabel(element)}](${refId})`); } return lines.join("\n").trim(); } function findElement(target) { if (!target || typeof target !== "string") { return null; } const trimmed = target.trim(); if (!trimmed) { return null; } let element = document.querySelector(`[${REF_ATTR}="${CSS.escape(trimmed)}"]`); if (element) { return element; } try { element = document.querySelector(trimmed); if (element) { return element; } } catch (_) {} element = Array.from(document.querySelectorAll("button,a,input,textarea,select,[role='button']")).find( (candidate) => normalizeText(candidate.innerText || candidate.value || candidate.placeholder || "") === trimmed, ); if (element) { return element; } element = Array.from(document.querySelectorAll("[aria-label]")).find( (candidate) => normalizeText(candidate.getAttribute("aria-label")) === trimmed, ); return element || null; } function scrollPage(text) { const input = normalizeText(text); let direction = "down"; let amount = 500; if (input.includes(":")) { const parts = input.split(":"); direction = normalizeText(parts[0]) || "down"; const parsed = Number(parts[1]); if (!Number.isNaN(parsed) && parsed > 0) { amount = parsed; } } if (direction === "up") { window.scrollBy(0, -amount); } else if (direction === "left") { window.scrollBy(-amount, 0); } else if (direction === "right") { window.scrollBy(amount, 0); } else { window.scrollBy(0, amount); } } async function executeCommand(commandData) { const command = String(commandData.command || "").trim(); const target = commandData.target; const text = commandData.text; switch (command) { case "click": { const element = findElement(target); if (!element) { return { status: "error", error: `未找到点击目标: ${target}` }; } element.click(); return { status: "success", message: "click 执行成功" }; } case "type": { const element = findElement(target); if (!element) { return { status: "error", error: `未找到输入目标: ${target}` }; } const value = text == null ? "" : String(text); if ("value" in element) { element.focus(); element.value = value; element.dispatchEvent(new Event("input", { bubbles: true })); element.dispatchEvent(new Event("change", { bubbles: true })); } else { element.textContent = value; } return { status: "success", message: "type 执行成功" }; } case "scroll": case "scroll_page": { scrollPage(text); return { status: "success", message: "scroll 执行成功" }; } case "get_page_info": { await sendPageInfo("get_page_info"); return { status: "success", message: "页面信息已回传" }; } case "refresh_page": { window.location.reload(); return { status: "success", message: "页面刷新中" }; } case "go_back": { window.history.back(); return { status: "success", message: "执行后退" }; } case "go_forward": { window.history.forward(); return { status: "success", message: "执行前进" }; } default: return { status: "error", error: `不支持的命令: ${command}` }; } } async function sendPageInfo(reason) { const markdown = buildMarkdown(); const payload = { type: "PAGE_INFO_UPDATE", data: { reason, title: document.title || "", url: window.location.href, markdown, }, }; await chrome.runtime.sendMessage(payload); } chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => { const type = request?.type; if (type === "REQUEST_PAGE_CAPTURE") { sendPageInfo(request?.data?.reason || "manual") .then(() => sendResponse({ status: "success" })) .catch((error) => sendResponse({ status: "error", error: error?.message || String(error) }), ); return true; } if (type === "EXECUTE_COMMAND") { executeCommand(request?.data || {}) .then(async (result) => { if (request?.data?.wait_for_page_info === true) { setTimeout(() => { sendPageInfo("wait_for_page_info"); }, 400); } sendResponse(result); }) .catch((error) => sendResponse({ status: "error", error: error?.message || String(error) }), ); return true; } return true; }); setTimeout(() => { sendPageInfo("content_script_ready").catch(() => {}); }, 800); })(); // 结束 IIFE