HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
1"""Meta AI — blog listing (ai.meta.com/blog) → ANNOUNCEMENT events, release posts queued for LLM extraction.23The listing has no RSS (404). It is server-rendered twice: React cards (title link + "Mon D, YYYY" date + category) and a `<noscript>`4list view (h4 title, "Month DD, YYYY" date, "Learn More" link). Both are parsed; items are deduplicated by URL.56llama.com (redirects to developer.meta.com/ai) is a client-rendered Comet app: the HTML contains no model list at all (SSR disabled,7`fail_bad_preloaders`), so no Llama model target is emitted — Llama model facts come from the release posts (LLM) and other connectors.8"""9from __future__ import annotations1011import re1213from selectolax.parser import HTMLParser, Node1415from aiatlas.registry import org_ref16from aiatlas.sdk.connector import BaseConnector, Parsed, RunContext17from aiatlas.sdk.extract.dates import parse_datetime18from aiatlas.sdk.extract.feeds import FeedItem19from aiatlas.sdk.extract.html import node_text20from aiatlas.sdk.facts import Facts, Target21from aiatlas.sdk.fetch import FetchResult2223from ._common import announcement_events2425BLOG = "https://ai.meta.com/blog/"26POST_URL = re.compile(r"^https://ai\.meta\.com/blog/[^/?#]+/?$")27DATE = re.compile(r"\b(January|February|March|April|May|June|July|August|September|October|November|December|Jan|Feb|Mar|Apr|Jun|Jul|Aug|Sep|Sept|Oct|Nov|Dec)\.? \d{1,2}, \d{4}\b")28CATEGORIES = ("Research", "Open Source", "Computer Vision", "Product", "Responsible AI", "Developer", "Blog", "FEATURED", "Speech & Audio", "Large Language Models",29 "Generative AI", "Hardware", "Robotics", "Infrastructure")30NOISE = {"", "learn more", "featured", "read more"}313233class MetaAIConnector(BaseConnector):34 name = "meta_ai"35 label = "Meta AI — blog"36 description = "AI at Meta blog listing: announcements and research posts (no feed available; HTML listing parsed)."37 source_key = "ai.meta.com"38 version = "1"39 parser_version = "1"40 interval_seconds = 360041 min_interval_seconds = 180042 rate_per_min = 1043 tier = 144 priority = 145 expected_min_records = 146 concurrency = 14748 async def discover(self, ctx: RunContext) -> list[Target]:49 return [Target(url=BLOG, doc_type="listing", key="blog", min_bytes=5000)]5051 async def extract(self, ctx: RunContext, target: Target, res: FetchResult, parsed: Parsed) -> Facts:52 facts = Facts()53 org = org_ref("meta-ai")54 facts.entities.append(org)55 if (target.key or "") == "blog" and parsed.html:56 # fresh parse: the SDK's parse_html decomposes <noscript> (where the server-rendered list view lives) from the tree it keeps57 items = listing_items(HTMLParser(res.content).body)58 announcement_events(facts, org, items, source_name="ai.meta.com/blog", max_follow=15)59 facts.document_title, facts.document_entity = "AI at Meta blog", org60 return facts616263def listing_items(root: Node | None) -> list[FeedItem]:64 if root is None:65 return []66 by_url: dict[str, FeedItem] = {}67 for a in root.css("a[href]"):68 href = (a.attributes.get("href") or "").strip()69 if not POST_URL.match(href):70 continue71 url = href if href.endswith("/") else href + "/"72 title = node_text(a)73 aria = a.attributes.get("aria-label") or ""74 if title.lower() in NOISE or len(title) < 8:75 title = re.sub(r"^Read\s+", "", aria).strip() if aria else ""76 date = None77 category = None78 node: Node | None = a79 # climb to the smallest container that still describes this single post (stop before a node holding other posts)80 for _ in range(8):81 node = node.parent if node else None82 if node is None or node.tag in ("body", "html"):83 break84 others = {(x.attributes.get("href") or "").rstrip("/") for x in node.css("a[href]") if POST_URL.match(x.attributes.get("href") or "")}85 if len(others) > 1:86 break87 text = node_text(node)88 if date is None:89 m = DATE.search(text)90 if m:91 date = parse_datetime(m.group(0))92 if not title:93 h = node.css_first("h4, h3, h2")94 title = node_text(h) if h else ""95 if category is None:96 for c in CATEGORIES:97 if c not in ("FEATURED", "Blog") and re.search(rf"(^|\n| ){re.escape(c)}(\n| |$)", text):98 category = c99 break100 if date is not None and title:101 break102 if not title:103 continue104 item = by_url.get(url)105 if item is None:106 by_url[url] = FeedItem(id=url, url=url, title=title[:300], summary=None, published_at=date, updated_at=None, categories=[category] if category else [])107 else:108 if item.published_at is None and date:109 item.published_at = date110 if not item.categories and category:111 item.categories = [category]112 return sorted(by_url.values(), key=lambda it: it.published_at.timestamp() if it.published_at else 0, reverse=True)113114115CONNECTORS = [MetaAIConnector]116