char-slop/berry

multipurpose self-modifiable agentic harness

git clone https://git.t4t.associates/char-slop/berry

Charlotte Somweb: use regular duckduckgo for search queriesac59009

main
7.2 KiB171 linesraw
1// Functions that run inside the loaded page: serialized with toString(), so each is self-contained.
2
3export type SearchResult = { title: string; url: string; snippet: string };
4
5/** The page's own value of `fn()`, as json text for the browser to hand back. */
6export function expressionFor(fn: () => unknown): string {
7  return `Promise.resolve((${fn.toString()})()).then((result) => JSON.stringify(result))`;
8}
9
10/** Results of a duckduckgo search page, without its ads. */
11export async function searchPage(): Promise<SearchResult[]> {
12  const results = () => document.querySelectorAll<HTMLElement>('article[data-testid="result"]');
13  // the page arrives empty and its own script fills in the results, so wait for them; a query
14  // that matches nothing renders a message instead of any
15  const deadline = Date.now() + 15_000;
16  while (
17    Date.now() < deadline &&
18    results().length === 0 &&
19    !/no results found for/i.test(document.body.textContent ?? "")
20  ) {
21    await new Promise((resolve) => setTimeout(resolve, 100));
22  }
23  if (document.querySelector('[data-area="mainline"]') === null) {
24    throw new Error("DuckDuckGo did not render a results page");
25  }
26
27  // innerText glues adjacent spans, such as a result's date, to the text after them
28  const text = (element: HTMLElement): string =>
29    [...element.childNodes]
30      .map((node) => (node instanceof HTMLElement ? text(node) : node.textContent ?? ""))
31      .join(element.children.length > 1 ? " " : "")
32      .replace(/\s+/g, " ")
33      .trim();
34
35  return [...results()]
36    .map((result) => {
37      const anchor = result.querySelector<HTMLAnchorElement>('a[data-testid="result-title-a"]');
38      if (!anchor) return undefined;
39      const redirect = new URL(anchor.href);
40      const url = redirect.searchParams.get("uddg") ?? anchor.href;
41      const snippet = result.querySelector<HTMLElement>('[data-result="snippet"]');
42      return { title: text(anchor), url, snippet: snippet ? text(snippet) : "" };
43    })
44    .filter((result): result is SearchResult => Boolean(result?.title && result.url));
45}
46
47/** The response body as served, whatever its type. */
48export async function pageAsRaw(): Promise<string> {
49  document.querySelector<HTMLElement>("#rawdata-tab")?.click();
50  const rawJson = document.querySelector<HTMLElement>("#rawdata-panel .data")?.textContent;
51  if (rawJson !== undefined && rawJson !== null) return rawJson;
52  try {
53    return await (await fetch(location.href)).text();
54  } catch {
55    return document.querySelector<HTMLElement>("body > pre")?.textContent ??
56      document.documentElement.outerHTML;
57  }
58}
59
60/** The page's main content as markdown; other documents as their text. */
61export async function pageAsMarkdown(): Promise<string> {
62  if (!/^(?:text\/html|application\/xhtml\+xml)$/i.test(document.contentType)) {
63    document.querySelector<HTMLElement>("#rawdata-tab")?.click();
64    const rawJson = document.querySelector<HTMLElement>("#rawdata-panel .data")?.textContent;
65    if (rawJson !== undefined && rawJson !== null) return rawJson;
66    try {
67      return await (await fetch(location.href)).text();
68    } catch {
69      return document.querySelector<HTMLElement>("body > pre")?.textContent ??
70        document.documentElement.textContent ?? "";
71    }
72  }
73
74  const source = document.querySelector<HTMLElement>("article") ??
75    document.querySelector<HTMLElement>("main, [role=main]") ??
76    document.body;
77  const root = source.cloneNode(true) as HTMLElement;
78  const sourceElements = source.querySelectorAll("*");
79  const clonedElements = root.querySelectorAll("*");
80  sourceElements.forEach((element, index) => {
81    const style = getComputedStyle(element);
82    if (style.display === "none" || style.contentVisibility === "hidden") {
83      clonedElements[index]?.remove();
84    }
85  });
86  root
87    .querySelectorAll(
88      "script, style, noscript, template, svg, canvas, nav, header, footer, form, button, input, select, textarea, dialog, [hidden], [aria-hidden=true]",
89    )
90    .forEach((element) => element.remove());
91
92  const escapeText = (text: string) => text.replace(/\s+/g, " ").replace(/([\\`*_[\]])/g, "\\$1");
93  const children = (element: Element) => [...element.childNodes].map(render).join("");
94  const block = (text: string) => `\n\n${text.trim()}\n\n`;
95
96  function render(node: Node): string {
97    if (node.nodeType === Node.TEXT_NODE) return escapeText(node.textContent ?? "");
98    if (!(node instanceof Element)) return "";
99
100    const tag = node.tagName.toLowerCase();
101    if (tag === "br") return "\n";
102    if (tag === "hr") return "\n\n---\n\n";
103    if (/^h[1-6]$/.test(tag)) {
104      return block(`${"#".repeat(Number(tag[1]))} ${children(node).trim()}`);
105    }
106    if (tag === "p") return block(children(node));
107    if (tag === "strong" || tag === "b") return `**${children(node).trim()}**`;
108    if (tag === "em" || tag === "i") return `*${children(node).trim()}*`;
109    if (tag === "del" || tag === "s") return `~~${children(node).trim()}~~`;
110    if (tag === "code" && node.parentElement?.tagName.toLowerCase() !== "pre") {
111      const text = node.textContent ?? "";
112      const fence = text.includes("`") ? "``" : "`";
113      return `${fence}${text}${fence}`;
114    }
115    if (tag === "pre") {
116      return `\n\n\`\`\`\n${node.textContent?.replace(/^\n|\n$/g, "") ?? ""}\n\`\`\`\n\n`;
117    }
118    if (tag === "a") {
119      const text = children(node).trim();
120      const href = (node as HTMLAnchorElement).href;
121      if (!href || href.startsWith("javascript:")) return text;
122      return text ? `[${text}](<${href.replace(/>/g, "%3E")}>)` : `<${href}>`;
123    }
124    if (tag === "img") {
125      const image = node as HTMLImageElement;
126      if (!image.src || image.src.startsWith("data:")) return "";
127      return `![${escapeText(image.alt)}](<${image.src.replace(/>/g, "%3E")}>)`;
128    }
129    if (tag === "blockquote") {
130      return block(children(node).trim().split("\n").map((line) => `> ${line}`).join("\n"));
131    }
132    if (tag === "ul" || tag === "ol") {
133      const items = [...node.children]
134        .filter((child) => child.tagName.toLowerCase() === "li")
135        .map((item, index) =>
136          `${tag === "ol" ? `${index + 1}.` : "-"} ${children(item).trim().replace(/\n/g, "\n  ")}`
137        );
138      return block(items.join("\n"));
139    }
140    if (tag === "table") {
141      const rows = [...node.querySelectorAll("tr")].map((row) =>
142        [...row.querySelectorAll(":scope > th, :scope > td")].map((cell) =>
143          children(cell).trim().replace(/\|/g, "\\|").replace(/\n+/g, " ")
144        )
145      );
146      if (!rows.length) return "";
147      const width = Math.max(...rows.map((row) => row.length));
148      const line = (row: string[]) =>
149        `| ${[...row, ...Array(width - row.length).fill("")].join(" | ")} |`;
150      return block(
151        [line(rows[0]), line(Array(width).fill("---")), ...rows.slice(1).map(line)].join("\n"),
152      );
153    }
154    if (tag === "dt") return block(`**${children(node).trim()}**`);
155    if (tag === "dd") return block(children(node));
156    if (
157      ["article", "aside", "details", "div", "figcaption", "figure", "main", "section", "summary"]
158        .includes(tag)
159    ) {
160      return block(children(node));
161    }
162    return children(node);
163  }
164
165  const markdown = render(root)
166    .replace(/[ \t]+\n/g, "\n")
167    .replace(/\n[ \t]+/g, "\n")
168    .replace(/\n{3,}/g, "\n\n")
169    .trim();
170  return markdown || document.body.innerText.trim();
171}