const DEER_CLIPPER_DEFAULT_PROMPT = "\u8bf7\u57fa\u4e8e\u521a\u521a\u53d1\u9001\u7684\u7f51\u9875\u5185\u5bb9\uff0c\u5148\u7528\u4e2d\u6587\u603b\u7ed3\u8981\u70b9\uff0c\u5e76\u7b49\u5f85\u6211\u7ee7\u7eed\u63d0\u95ee\u3002"; const ZNCM_EXTENSION_CONFIG = globalThis.ZNCM_WEB_CLIPPER_CONFIG || {}; const ZNCM_DEFAULT_WEB_BASE_URL = String(ZNCM_EXTENSION_CONFIG.defaultWebBaseUrl || "").trim().replace(/\/+$/, ""); const ZNCM_DEFAULT_API_BASE_URL = String(ZNCM_EXTENSION_CONFIG.defaultApiBaseUrl || "").trim().replace(/\/+$/, ""); const DEER_CLIPPER_DEFAULTS = { apiBaseUrl: ZNCM_DEFAULT_API_BASE_URL, webBaseUrl: ZNCM_DEFAULT_WEB_BASE_URL, autoDiscoverServiceUrl: true, lastServiceSyncAt: "", lastServiceSyncSource: "", accessToken: "", tokenSyncEnabled: true, lastTokenSyncAt: "", lastTokenSyncSource: "", defaultPrompt: DEER_CLIPPER_DEFAULT_PROMPT, pageAssistantEnabled: true, taskPollingEnabled: true, workerId: "", taskPollIntervalSeconds: 5, taskPageLoadTimeoutSeconds: 30, closeTaskTabs: false, }; function deerNormalizeBaseUrl(value) { return String(value || "").trim().replace(/\/+$/, ""); } function deerAuthHeaders(settings) { const token = String(settings.accessToken || "").trim(); if (!token) { return {}; } return { Authorization: /^bearer\s+/i.test(token) ? token : `Bearer ${token}`, }; } function deerMakeWorkerId() { return `zncm-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`; } function deerExtractAuthTokenFromPage() { const storageKeys = ["deerflow.auth"]; const tokenFieldNames = new Set(["access_token", "accessToken", "token", "id_token", "idToken"]); function looksLikeToken(value) { const text = String(value || "").trim(); return text.length > 20 && !/\s/.test(text); } function findToken(value, depth = 0) { if (value == null || depth > 4) { return ""; } if (typeof value === "string") { const text = value.trim(); if (!text) { return ""; } if ((text.startsWith("{") && text.endsWith("}")) || (text.startsWith("[") && text.endsWith("]"))) { try { return findToken(JSON.parse(text), depth + 1); } catch { return looksLikeToken(text) ? text : ""; } } return looksLikeToken(text) ? text : ""; } if (typeof value !== "object") { return ""; } for (const [key, nested] of Object.entries(value)) { if (tokenFieldNames.has(key) && looksLikeToken(nested)) { return String(nested).trim(); } } for (const nested of Object.values(value)) { const token = findToken(nested, depth + 1); if (token) { return token; } } return ""; } function scanStorage(storage, storageName) { for (const key of storageKeys) { const token = findToken(storage.getItem(key)); if (token) { return { token, source: `${storageName}:${key}` }; } } for (let index = 0; index < storage.length; index += 1) { const key = storage.key(index); if (!key || !/(auth|token|session)/i.test(key)) { continue; } const token = findToken(storage.getItem(key)); if (token) { return { token, source: `${storageName}:${key}` }; } } return null; } try { return scanStorage(window.localStorage, "localStorage") || scanStorage(window.sessionStorage, "sessionStorage"); } catch (error) { return { token: "", source: "", error: error instanceof Error ? error.message : String(error) }; } } function deerExtractCurrentPage() { const maxLocalChars = 220000; function cleanText(value) { return String(value || "") .replace(/\s+\n/g, "\n") .replace(/\n{3,}/g, "\n\n") .trim(); } function escapeCell(value) { return cleanText(value).replace(/\|/g, "\\|").replace(/\n/g, " "); } function tableToMarkdown(table, index) { const rows = Array.from(table.rows || []).map((row) => Array.from(row.cells || []).map((cell) => escapeCell(cell.innerText)), ); if (rows.length === 0 || rows.every((row) => row.length === 0)) { return ""; } const width = Math.max(...rows.map((row) => row.length)); const normalized = rows.map((row) => { const next = row.slice(); while (next.length < width) next.push(""); return next; }); const header = normalized[0]; const divider = Array.from({ length: width }, () => "---"); const body = normalized.slice(1); return [ `### Table ${index + 1}`, "", `| ${header.join(" | ")} |`, `| ${divider.join(" | ")} |`, ...body.map((row) => `| ${row.join(" | ")} |`), ].join("\n"); } const clone = document.body ? document.body.cloneNode(true) : null; if (clone) { clone.querySelectorAll("script, style, noscript, svg, canvas, iframe").forEach((node) => node.remove()); } const title = document.title || ""; const url = location.href; const description = document.querySelector('meta[name="description"]')?.getAttribute("content") || ""; const main = clone?.querySelector("article, main, [role='main']") || clone; const text = cleanText(main?.innerText || document.body?.innerText || ""); const tables = Array.from(document.querySelectorAll("table")) .map((table, index) => tableToMarkdown(table, index)) .filter(Boolean); const parts = []; if (description.trim()) { parts.push(`## Meta Description\n\n${cleanText(description)}`); } if (text) { parts.push(`## Page Text\n\n${text}`); } if (tables.length > 0) { parts.push(`## Tables\n\n${tables.join("\n\n")}`); } let content = parts.join("\n\n"); if (content.length > maxLocalChars) { content = `${content.slice(0, maxLocalChars)}\n\n[Truncated locally by zncm Web Clipper]`; } return { title, url, content, contentType: "markdown" }; } // Injected into the page to build an indexed list of interactive elements plus // the visible page text. Each interactive element is tagged with // `data-deer-index` so a follow-up deerPerformAction call can find it by index. function deerObservePage(maxElements) { const limit = Math.max(10, Number(maxElements || 150)); const DEER_ATTR = "data-deer-index"; document.querySelectorAll(`[${DEER_ATTR}]`).forEach((node) => node.removeAttribute(DEER_ATTR)); function isVisible(el) { if (!(el instanceof Element)) return false; const rect = el.getBoundingClientRect(); if (rect.width <= 1 && rect.height <= 1) return false; const style = window.getComputedStyle(el); if (style.visibility === "hidden" || style.display === "none" || style.opacity === "0") return false; if (el.disabled) return false; return true; } function labelFor(el) { const aria = el.getAttribute("aria-label"); if (aria && aria.trim()) return aria.trim(); const placeholder = el.getAttribute("placeholder"); const title = el.getAttribute("title"); const name = el.getAttribute("name"); const text = (el.innerText || el.textContent || "").trim(); if (text) return text.replace(/\s+/g, " "); if (placeholder && placeholder.trim()) return placeholder.trim(); if (title && title.trim()) return title.trim(); if (el.value && String(el.value).trim()) return String(el.value).trim(); if (name && name.trim()) return name.trim(); const alt = el.querySelector?.("img[alt]")?.getAttribute("alt"); if (alt && alt.trim()) return alt.trim(); return ""; } const selector = [ "a[href]", "button", "input:not([type=hidden])", "select", "textarea", "[role=button]", "[role=link]", "[role=tab]", "[role=menuitem]", "[role=checkbox]", "[role=radio]", "[role=switch]", "[contenteditable=true]", "[onclick]", ].join(","); const seen = new Set(); const elements = []; let index = 0; for (const el of document.querySelectorAll(selector)) { if (index >= limit) break; if (seen.has(el) || !isVisible(el)) continue; seen.add(el); el.setAttribute(DEER_ATTR, String(index)); const tag = el.tagName.toLowerCase(); let label = labelFor(el); if (label.length > 200) label = label.slice(0, 200); const entry = { index, tag, label }; const type = el.getAttribute("type") || el.getAttribute("role"); if (type) entry.type = type; if (tag === "input" || tag === "textarea" || tag === "select") { const value = String(el.value || ""); if (value) entry.value = value.slice(0, 80); } if (tag === "a") { const href = el.getAttribute("href") || ""; if (href) entry.href = href.slice(0, 200); } elements.push(entry); index += 1; } // Inline text extraction so this function stays self-contained when injected // via chrome.scripting.executeScript (no dependency on other content-script fns). let text = ""; try { const clone = document.body ? document.body.cloneNode(true) : null; if (clone) { clone.querySelectorAll(`script, style, noscript, svg, canvas, iframe, [${DEER_ATTR}-skip]`).forEach((node) => node.remove()); } const main = clone?.querySelector("article, main, [role='main']") || clone; text = String(main?.innerText || document.body?.innerText || "") .replace(/\s+\n/g, "\n") .replace(/\n{3,}/g, "\n\n") .trim(); } catch { text = ""; } return { url: location.href, title: document.title || "", elements, text, }; } // Injected into the page to perform a single action on the element previously // tagged by deerObservePage (by index). Returns {status, message}. async function deerPerformAction(action) { const DEER_ATTR = "data-deer-index"; const type = String(action?.type || "").toLowerCase(); function findByIndex(idx) { if (idx === undefined || idx === null || idx < 0) return null; return document.querySelector(`[${DEER_ATTR}="${idx}"]`); } function fireInput(el, value) { const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype; const setter = Object.getOwnPropertyDescriptor(proto, "value")?.set; if (setter) { setter.call(el, value); } else { el.value = value; } el.dispatchEvent(new Event("input", { bubbles: true })); el.dispatchEvent(new Event("change", { bubbles: true })); } try { if (type === "observe" || type === "extract") { return { status: "ok", message: type === "extract" ? "Page text re-read." : "Observed page." }; } if (type === "scroll") { const el = findByIndex(action.index); if (el) { el.scrollIntoView({ block: "center", behavior: "instant" }); return { status: "ok", message: "Scrolled element into view." }; } window.scrollBy(0, Math.round(window.innerHeight * 0.85)); return { status: "ok", message: "Scrolled down one viewport." }; } if (type === "wait") { const selector = String(action.wait_for || "").trim(); const timeoutMs = Math.max(500, Number(action.timeout_ms || 8000)); if (!selector) { await new Promise((resolve) => setTimeout(resolve, Math.min(timeoutMs, 3000))); return { status: "ok", message: "Waited." }; } const start = Date.now(); while (Date.now() - start < timeoutMs) { if (document.querySelector(selector)) { return { status: "ok", message: `Element ${selector} appeared.` }; } await new Promise((resolve) => setTimeout(resolve, 200)); } return { status: "timeout", message: `Element ${selector} did not appear within ${timeoutMs}ms.` }; } if (type === "click") { const el = findByIndex(action.index); if (!el) return { status: "error", message: `No element with index ${action.index}. Re-observe the page.` }; el.scrollIntoView({ block: "center", behavior: "instant" }); if (typeof el.focus === "function") el.focus(); el.click(); return { status: "ok", message: `Clicked element ${action.index}.` }; } if (type === "type") { const el = findByIndex(action.index); if (!el) return { status: "error", message: `No element with index ${action.index}. Re-observe the page.` }; el.scrollIntoView({ block: "center", behavior: "instant" }); if (typeof el.focus === "function") el.focus(); const value = String(action.text ?? ""); if (el.isContentEditable) { el.textContent = value; el.dispatchEvent(new Event("input", { bubbles: true })); } else { fireInput(el, value); } if (action.submit) { const enterInit = { bubbles: true, cancelable: true, key: "Enter", code: "Enter", keyCode: 13, which: 13 }; el.dispatchEvent(new KeyboardEvent("keydown", enterInit)); el.dispatchEvent(new KeyboardEvent("keyup", enterInit)); const form = el.form || el.closest("form"); if (form && typeof form.requestSubmit === "function") { try { form.requestSubmit(); } catch { form.submit?.(); } } } return { status: "ok", message: `Typed into element ${action.index}${action.submit ? " and submitted" : ""}.` }; } return { status: "error", message: `Unsupported action type: ${type}` }; } catch (error) { return { status: "error", message: error instanceof Error ? error.message : String(error) }; } }