From d2d4955b18e6d5ac3975b236633bbe0894e2e770 Mon Sep 17 00:00:00 2001 From: Zdenek Stursa <90236729+zdenek-stursa@users.noreply.github.com> Date: Mon, 16 Mar 2026 16:20:37 +0100 Subject: [PATCH] feat(route): add Inception Labs blog route (#21386) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(routes): add Inception Labs blog route Adds RSS feed for https://www.inceptionlabs.ai/blog/ The page is built with Framer and served as SSR HTML, so no Puppeteer is required — a regular HTTP fetch with a browser User-Agent suffices. Two distinct blog card layouts are handled: - Standard card: date and category in [data-framer-name="Date"] elements - Featured card: date inside a element, category in [data-framer-name="Category"] Each item includes title, publication date, author, category, and the full article body extracted from [data-framer-name="Content"]. Route: /inceptionlabs/blog Example: /inceptionlabs/blog * refactor(routes/inceptionlabs): address review feedback - Fix categories: technology -> programming (valid Category type) - Remove redundant radar source (inceptionlabs.ai redirects to www) - Add maintainer GitHub ID - Replace custom PostMeta interface with existing DataItem type - Use [data-framer-name="Desktop"] selector to avoid SSR duplicates - Refactor card collection from .each()/push to .map()/.filter() --------- Co-authored-by: Zdenek --- lib/routes/inceptionlabs/blog.ts | 118 ++++++++++++++++++++++++++ lib/routes/inceptionlabs/namespace.ts | 9 ++ 2 files changed, 127 insertions(+) create mode 100644 lib/routes/inceptionlabs/blog.ts create mode 100644 lib/routes/inceptionlabs/namespace.ts diff --git a/lib/routes/inceptionlabs/blog.ts b/lib/routes/inceptionlabs/blog.ts new file mode 100644 index 000000000..f6e33ca3f --- /dev/null +++ b/lib/routes/inceptionlabs/blog.ts @@ -0,0 +1,118 @@ +import { load } from 'cheerio'; + +import type { DataItem, Route } from '@/types'; +import cache from '@/utils/cache'; +import ofetch from '@/utils/ofetch'; +import { parseDate } from '@/utils/parse-date'; + +const ROOT_URL = 'https://www.inceptionlabs.ai'; + +export const route: Route = { + path: '/blog', + categories: ['programming'], + example: '/inceptionlabs/blog', + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['www.inceptionlabs.ai/blog'], + }, + ], + name: 'Blog', + maintainers: ['zdenek-stursa'], + handler, + url: 'inceptionlabs.ai/blog', +}; + +async function handler() { + const response = await ofetch(`${ROOT_URL}/blog`); + const $ = load(response); + + // Use [data-framer-name="Desktop"] to target only desktop-variant cards, + // avoiding duplicate entries from SSR breakpoint rendering (desktop/mobile). + const posts = $('a[href^="./blog/"][data-framer-name="Desktop"]') + .toArray() + .map((el): DataItem | null => { + const $el = $(el); + const href = $el.attr('href') ?? ''; + + // Skip category filter links and anchor-only links + if (href.includes('categories') || href.includes('#')) { + return null; + } + + // Get title: first h6.framer-text that is not a "Read story" navigation label + const title = $el + .find('h6.framer-text') + .toArray() + .map((h6) => $(h6).text().trim()) + .find((text) => text && text !== 'Read story'); + + if (!title) { + return null; + } + + const slug = href.replace('./blog/', ''); + const link = `${ROOT_URL}/blog/${slug}`; + + // Date/category extraction – two card layouts exist: + // Standard card: [data-framer-name="Date"][0] = category (teal), + // [data-framer-name="Date"][1] = plain date text + // Featured card: [data-framer-name="Date"][0] = date inside , + // [data-framer-name="Category"] = category (teal, separate element) + const dateBlocks = $el + .find('[data-framer-name="Date"] p.framer-text') + .toArray() + .map((p) => $(p).text().trim()); + + // Category: featured cards use data-framer-name="Category"; standard cards use dateBlocks[0] + const categoryEl = $el.find('[data-framer-name="Category"]'); + const category = categoryEl.length > 0 ? categoryEl.first().text().trim() : (dateBlocks[0] ?? ''); + + // Date: featured cards have a single Date block (the date itself); standard cards have it at index 1 + const pubDate = dateBlocks.length >= 2 ? dateBlocks[1] : (dateBlocks[0] ?? ''); + + return { + title, + link, + category: category ? [category] : [], + pubDate, + }; + }) + .filter((post): post is DataItem => post !== null); + + const items = await Promise.all( + posts.map((post) => + cache.tryGet(post.link!, async () => { + const postHtml = await ofetch(post.link!); + const $post = load(postHtml); + + // Full article content + const contentHtml = $post('[data-framer-name="Content"]').first().html() ?? ''; + + // Author name from the first [data-framer-name="Author"] RichTextContainer + const author = $post('[data-framer-name="Author"] p.framer-text').first().text().trim(); + + return { + ...post, + description: contentHtml, + author, + pubDate: parseDate(post.pubDate as string), + } as DataItem; + }) + ) + ); + + return { + title: 'Inception Labs Blog', + link: `${ROOT_URL}/blog`, + description: 'Latest posts from the Inception Labs blog about diffusion LLMs and AI research', + item: items, + }; +} diff --git a/lib/routes/inceptionlabs/namespace.ts b/lib/routes/inceptionlabs/namespace.ts new file mode 100644 index 000000000..877fdcaf5 --- /dev/null +++ b/lib/routes/inceptionlabs/namespace.ts @@ -0,0 +1,9 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'Inception Labs', + url: 'inceptionlabs.ai', + categories: ['programming'], + description: 'Inception Labs - AI research company developing diffusion-based LLMs', + lang: 'en', +};