SPB Git forge
7commits 1branches 0releases
229.0 KBsize
maindefault branch
12 days agolast push
TypeScript 91.8% HTML 3.2% JavaScript 3% SQL 1.4% CSS 0.7%
8.5 KB · 144 lines javascript
Raw Blame History
1// In-page DOM snapshot. Kept as plain JavaScript (not TS) so no bundler helper (`__name`) leaks into the page.2// Evaluated by PageSummarizer.ts as `(SOURCE)(MAX)`; must remain a single function expression.3(MAX) => {4  const clean = (s) => (s ?? "").replace(/\s+/g, " ").trim();5  const vh = window.innerHeight;6  const regionOf = (node) => {7    let el = node;8    let hops = 0;9    while (el && hops++ < 30) {10      const role = el.getAttribute("role") ?? "";11      const tag = el.tagName.toLowerCase();12      if (role === "dialog" || el.getAttribute("aria-modal") === "true") return "dialog";13      if (tag === "ytd-comments" || /comment/i.test(el.id) || role === "comment") return "comments";14      if (role === "feed") return "feed";15      if (role === "main" || tag === "main" || el.id === "primary" || el.id === "contents" || el.id === "main-content") return "main";16      if (role === "navigation" || tag === "nav" || el.id === "guide") return "navigation";17      if (role === "complementary" || tag === "aside" || el.id === "secondary" || el.id === "related") return "sidebar";18      if (tag === "header" || role === "banner" || el.id === "masthead") return "header";19      el = el.parentElement;20    }21    return "unknown";22  };23  const cssPath = (el) => {24    const href = el.getAttribute("href");25    const tag = el.tagName.toLowerCase();26    if (href) return `${tag}[href="${href.replace(/"/g, '\\"')}"]`;27    const aria = el.getAttribute("aria-label");28    if (aria) return `${tag}[aria-label="${aria.replace(/"/g, '\\"').slice(0, 80)}"]`;29    if (el.id) return `#${CSS.escape(el.id)}`;30    const parts = [];31    let cur = el;32    let hops = 0;33    while (cur && hops++ < 6 && cur !== document.body) {34      const p = cur.parentElement;35      const idx = p ? Array.from(p.children).indexOf(cur) + 1 : 1;36      parts.unshift(`${cur.tagName.toLowerCase()}:nth-child(${idx})`);37      cur = p;38    }39    return parts.join(">");40  };41  const isVisible = (el) => {42    const r = el.getBoundingClientRect();43    const st = getComputedStyle(el);44    return r.width > 0 && r.height > 0 && st.visibility !== "hidden" && st.display !== "none";45  };46  const cardMeta = (el) => {47    let card = el;48    let hops = 0;49    const isCard = (n) => n.tagName === "ARTICLE" || n.getAttribute("role") === "article" || n.tagName === "LI" || /-(renderer|item|lockup|post)$|^shreddit-post$|view-model$/i.test(n.tagName) || /(^|\s)(card|post|feed-item|lockup)(\s|$)/i.test(typeof n.className === "string" ? n.className : "");50    while (card && hops++ < 6 && !isCard(card)) card = card.parentElement;51    // Only trust a card scope of bounded size; a huge container would bleed another item's numbers into this one.52    const scope = card && clean(card.textContent).length < 1500 ? card : el;53    const text = clean(scope.textContent).slice(0, 1200);54    const out = {};55    const dur = text.match(/(?:^|\s)(\d{1,2}:\d{2}(?::\d{2})?)(?=\s|$)/);56    if (dur) out.duration = dur[1];57    const views = text.match(/([\d.,\s]+\s?[kKmMbB]?)\s*(vues|views|visionnements|upvotes|points|votes|likes|j'aime)/i);58    if (views) out.views = views[0];59    const comments = text.match(/([\d.,\s]+\s?[kKmMbB]?)\s*(comment|commentaire|repl|répon)/i);60    if (comments) out.comments = comments[0];61    const ago = text.match(/(il y a\s+[\w\s]+?|\d+\s+(seconds?|minutes?|hours?|days?|weeks?|months?|years?|h|min|j|sem|mois|ans?)\s+ago|\b\d+\s*(h|min|j|d|w|mo|y)\b)/i);62    if (ago) out.published = ago[0];63    const authorLink = scope.querySelector('a[href*="/@"], a[href^="/user/"], a[href^="/u/"], a[href*="/channel/"], a[href^="/c/"], a[href*="/r/"]');64    if (authorLink && authorLink !== el) out.author = clean(authorLink.textContent) || (authorLink.getAttribute("href") ?? "");65    return out;66  };6768  const seen = new Set();69  const candidates = [];70  let index = 0;71  const push = (c) => {72    const key = c.kind + "|" + (c.href ?? "") + "|" + c.text.slice(0, 80);73    if (seen.has(key)) return;74    seen.add(key);75    candidates.push({ ...c, index: index++ });76  };7778  for (const a of Array.from(document.querySelectorAll("a[href]"))) {79    if (candidates.length >= MAX) break;80    if (!isVisible(a)) continue;81    const href = a.href;82    if (!href || href.startsWith("javascript:")) continue;83    const img = a.querySelector("img");84    const text = clean(a.textContent) || clean(a.getAttribute("aria-label")) || clean(a.getAttribute("title")) || clean(img ? img.alt : "");85    if (!text && !/watch|shorts|comments|status|reel|video|\/p\//.test(href)) continue;86    const r = a.getBoundingClientRect();87    push({ kind: "link", href, text: text.slice(0, 200), aria_label: clean(a.getAttribute("aria-label")) || undefined, role: a.getAttribute("role") ?? undefined, region: regionOf(a), visible: r.top < vh && r.bottom > 0, top: Math.round(r.top + window.scrollY), meta: cardMeta(a), locator_hint: cssPath(a) });88  }89  for (const art of Array.from(document.querySelectorAll("article,[role=article],shreddit-post,ytd-rich-item-renderer,ytd-video-renderer,ytd-compact-video-renderer,ytd-reel-item-renderer,ytd-comment-thread-renderer"))) {90    if (candidates.length >= MAX) break;91    if (!isVisible(art)) continue;92    const r = art.getBoundingClientRect();93    const link = art.querySelector("a[href]");94    push({ kind: "article", href: link ? link.href : undefined, text: clean(art.textContent).slice(0, 300), role: art.getAttribute("role") ?? art.tagName.toLowerCase(), region: regionOf(art), visible: r.top < vh && r.bottom > 0, top: Math.round(r.top + window.scrollY), meta: cardMeta(art), locator_hint: cssPath(link ?? art) });95  }96  for (const h of Array.from(document.querySelectorAll("h1,h2,[role=heading]")).slice(0, 30)) {97    if (!isVisible(h)) continue;98    const r = h.getBoundingClientRect();99    const text = clean(h.textContent);100    if (!text) continue;101    push({ kind: "heading", text: text.slice(0, 200), region: regionOf(h), visible: r.top < vh && r.bottom > 0, top: Math.round(r.top + window.scrollY), locator_hint: cssPath(h) });102  }103  for (const s of Array.from(document.querySelectorAll('input[type=search],input[name*=search i],input[placeholder*=search i],input[placeholder*=recherch i],[role=searchbox],[role=combobox][aria-label*=search i],[role=combobox][aria-label*=recherch i]'))) {104    if (!isVisible(s)) continue;105    const r = s.getBoundingClientRect();106    push({ kind: "search", text: clean(s.getAttribute("placeholder") ?? s.getAttribute("aria-label")) || "search", region: regionOf(s), visible: r.top < vh, top: Math.round(r.top + window.scrollY), locator_hint: cssPath(s) });107  }108  for (const b of Array.from(document.querySelectorAll("button,[role=button],summary,[aria-expanded]"))) {109    if (candidates.length >= MAX) break;110    if (!isVisible(b)) continue;111    const label = clean(b.getAttribute("aria-label")) || clean(b.textContent);112    if (!/more|plus|show|afficher|voir|replies|réponses|comments|commentaires|expand|load|charger|read more|lire la suite|\.\.\./i.test(label)) continue;113    const r = b.getBoundingClientRect();114    push({ kind: "button", text: label.slice(0, 120), aria_label: clean(b.getAttribute("aria-label")) || undefined, role: b.getAttribute("role") ?? "button", region: regionOf(b), visible: r.top < vh && r.bottom > 0, top: Math.round(r.top + window.scrollY), locator_hint: cssPath(b) });115  }116117  const videos = Array.from(document.querySelectorAll("video")).map((v) => ({118    src: v.getAttribute("src") ?? undefined,119    current_src: v.currentSrc || undefined,120    duration: Number.isFinite(v.duration) ? v.duration : undefined,121    width: v.videoWidth,122    height: v.videoHeight,123    playing: !v.paused && !v.ended && v.readyState > 2,124    poster: v.poster || undefined,125  }));126127  const main = document.querySelector("main,[role=main],#primary,#main-content,#contents") ?? document.body;128  const landmarks = Array.from(document.querySelectorAll("[role],main,nav,aside,header,footer,form")).map((e) => e.getAttribute("role") ?? e.tagName.toLowerCase());129  const h1 = document.querySelector("h1");130  return {131    url: location.href,132    title: document.title,133    lang: document.documentElement.lang || undefined,134    h1: clean(h1 ? h1.textContent : "") || undefined,135    landmarks: Array.from(new Set(landmarks)).slice(0, 25),136    candidates,137    video_elements: videos,138    text_excerpt: clean(main.textContent).slice(0, 600),139    scroll: { y: window.scrollY, height: document.documentElement.scrollHeight, viewport: vh },140    has_login_form: !!document.querySelector("input[type=password]"),141    dialog_open: !!document.querySelector("[role=dialog]:not([aria-hidden=true]),[aria-modal=true]"),142  };143}144