File size: 4,512 Bytes
ec675f2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import { Page } from "playwright";

export async function extractPageData(page: Page) {
  return await page.evaluate(() => {
    const title = document.title;
    const description =
      document.querySelector('meta[name="description"]')?.getAttribute("content") ||
      document.querySelector('meta[property="og:description"]')?.getAttribute("content") ||
      "";

    const faviconEl =
      document.querySelector('link[rel="icon"]') ||
      document.querySelector('link[rel="shortcut icon"]') ||
      document.querySelector('link[rel="apple-touch-icon"]');
    const favicon = faviconEl
      ? new URL(faviconEl.getAttribute("href") || "", location.href).href
      : new URL("/favicon.ico", location.href).href;

    const headings: { level: number; text: string }[] = [];
    const seenHeadings = new Set<string>();
    document.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach((el) => {
      const t = el.textContent?.trim();
      const key = `${el.tagName}-${t}`;
      if (t && !seenHeadings.has(key)) {
        seenHeadings.add(key);
        headings.push({ level: Number(el.tagName[1]), text: t });
      }
    });

    const images = [...new Set(
      Array.from(document.querySelectorAll("img[src]")).map((img) =>
        new URL(img.getAttribute("src") || "", location.href).href
      )
    )].slice(0, 100);

    const links = [...new Set(
      Array.from(document.querySelectorAll("a[href]")).map((a) =>
        new URL(a.getAttribute("href") || "", location.href).href
      )
    )].slice(0, 150);

    const buttons = [...new Set(
      Array.from(document.querySelectorAll(
        "button, input[type='submit'], input[type='button'], a[class*='btn'], a[role='button']"
      )).map((el) => (el.textContent || (el as HTMLInputElement).value || "").trim())
        .filter((t) => t.length > 0 && t.length <= 60)
    )].slice(0, 50);

    const css = [...new Set(
      Array.from(document.querySelectorAll('link[rel="stylesheet"]')).map((l) =>
        new URL(l.getAttribute("href") || "", location.href).href
      )
    )];

    document.querySelectorAll("nav, header, footer, aside, script, style, noscript").forEach((el) => el.remove());

    const colors = new Set<string>();
    const fonts = new Set<string>();
    const fontSizes = new Set<string>();
    const spacing = new Set<string>();
    const borderRadius = new Set<string>();
    const animations = new Set<string>();
    const gradients = new Set<string>();

    const elements = Array.from(document.querySelectorAll("*")).slice(0, 800);
    for (const el of elements) {
      const cs = getComputedStyle(el);

      [cs.color, cs.backgroundColor, cs.borderTopColor, cs.borderRightColor, cs.borderBottomColor, cs.borderLeftColor]
        .forEach((c) => {
          if (c && c !== "rgba(0, 0, 0, 0)" && c !== "transparent") colors.add(c);
        });

      if (cs.fontFamily) fonts.add(cs.fontFamily.split(",")[0].replace(/['"]/g, "").trim());
      if (cs.fontSize) fontSizes.add(cs.fontSize);

      [cs.marginTop, cs.marginBottom, cs.marginLeft, cs.marginRight,
       cs.paddingTop, cs.paddingBottom, cs.paddingLeft, cs.paddingRight].forEach((v) => {
        if (v && v !== "0px") spacing.add(v);
      });

      if (cs.borderRadius && cs.borderRadius !== "0px") borderRadius.add(cs.borderRadius);

      if (cs.animationName && cs.animationName !== "none") {
        animations.add(`${cs.animationName} ${cs.animationDuration} ${cs.animationTimingFunction}`);
      }
      if (cs.transitionProperty && cs.transitionProperty !== "all" && cs.transitionDuration !== "0s") {
        animations.add(`transition: ${cs.transitionProperty} ${cs.transitionDuration}`);
      }

      if (cs.backgroundImage && cs.backgroundImage.includes("gradient")) {
        gradients.add(cs.backgroundImage);
      }
    }

    const text = (document.body.innerText || "")
      .replace(/\n{3,}/g, "\n\n")
      .replace(/[ \t]{2,}/g, " ")
      .trim();

    return {
      title, description, favicon, headings, images, links, buttons, css,
      design: {
        colors: Array.from(colors).slice(0, 30),
        fonts: Array.from(fonts).slice(0, 15),
        fontSizes: Array.from(fontSizes).sort((a, b) => parseFloat(a) - parseFloat(b)),
        spacing: Array.from(spacing).slice(0, 30),
        borderRadius: Array.from(borderRadius).slice(0, 15),
        animations: Array.from(animations).slice(0, 30),
        gradients: Array.from(gradients).slice(0, 15),
      },
      text: text.slice(0, 8000),
    };
  });
}