rsshub
Version:
Make RSS Great Again!
120 lines (104 loc) • 4.72 kB
text/typescript
import { Route, DataItem } from '@/types';
import cache from '@/utils/cache';
import ofetch from '@/utils/ofetch';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import path from 'node:path';
import logger from '@/utils/logger';
// Define the main route path
export const route: Route = {
path: '/',
categories: ['programming'],
example: '/transformer-circuits',
parameters: {},
radar: [
{
source: ['transformer-circuits.pub/'],
target: '/',
},
],
name: 'Articles',
maintainers: ['shinmohuang'],
handler,
};
async function handler() {
const rootUrl = 'https://transformer-circuits.pub';
// Fetch the main page
const response = await ofetch(rootUrl);
const $ = load(response);
// Get all article links and basic info
const articlePromises = $('.toc a')
.toArray()
.map((item) => {
const $item = $(item);
const currentElement = $item;
const dateElement = $item.prevAll('.date').first();
const currentDate = dateElement.text().trim();
// Check if this is an article (either paper or note)
if (currentElement.hasClass('paper') || currentElement.hasClass('note')) {
const articleType = currentElement.hasClass('paper') ? 'Paper' : 'Note';
// Extract title and metadata from the list page
const title = currentElement.find('h3').text().trim();
let author = '';
const byline = currentElement.find('.byline');
if (byline.length) {
author = byline.text().trim();
}
const description = currentElement.find('.description').text().trim();
const href = currentElement.attr('href');
const articleUrl = href ? (href.startsWith('http') ? href : `${rootUrl}/${href}`) : rootUrl;
// Cache the whole article object instead of just the content
return cache.tryGet(articleUrl, async () => {
const fullContent = await fetchArticleContent(articleUrl);
return {
title,
link: articleUrl,
pubDate: parseDate(currentDate, 'MMMM YYYY'),
author,
description: fullContent || `${articleType}: ${description}`,
category: ['AI', 'Machine Learning', 'Anthropic', 'Transformer Circuits'],
};
});
}
return null;
});
// Wait for all article fetches to complete
const articlesWithContent = (await Promise.all(articlePromises)).filter(Boolean) as DataItem[];
return {
title: 'Transformer Circuits Thread',
link: rootUrl,
item: articlesWithContent,
description: 'Research on reverse engineering transformer language models into human-understandable programs',
};
}
// Function to fetch and parse article content
async function fetchArticleContent(url) {
try {
const response = await ofetch(url);
const $ = load(response);
// Remove navigation and other unnecessary elements
$('.article-header, .tooltip, modal, script, style, d-front-matter, .visual-toc').remove();
// Get the main content - d-article is the custom tag used by this site
let content = $('d-article').html();
// If d-article not found, try more specific fallback selectors
if (!content) {
// Try to find content in common article containers, but avoid selecting the entire body
content = $('main article, .article-content, .post-content, .content-area').html() || $('.content, .article, .post').html() || $('main').html();
// If still no content found, log a warning
if (!content) {
logger.warn(`No suitable content container found for ${url}`);
content = `<p>Could not extract content. Please visit <a href="${url}">the original page</a>.</p>`;
}
}
// Create an HTML fragment (not a full document) for the RSS description
return art(path.join(__dirname, 'templates/article.art'), {
content,
link: url,
});
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
logger.error(`Error fetching article content from ${url}: ${errorMessage}`);
return null; // Return null on error, we'll fall back to description
}
}