162 lines
5.2 KiB
TypeScript
162 lines
5.2 KiB
TypeScript
import { load } from 'cheerio';
|
|
import type { Context } from 'hono';
|
|
|
|
import type { Data, DataItem, Route } from '@/types';
|
|
import { ViewType } from '@/types';
|
|
import cache from '@/utils/cache';
|
|
import { parseDate } from '@/utils/parse-date';
|
|
import { getPlaywrightPage } from '@/utils/playwright';
|
|
|
|
export const route: Route = {
|
|
path: '/blog',
|
|
example: '/perplexity/blog',
|
|
url: 'www.perplexity.ai',
|
|
categories: ['blog'],
|
|
parameters: {},
|
|
features: {
|
|
requireConfig: false,
|
|
requirePuppeteer: true,
|
|
antiCrawler: false,
|
|
supportBT: false,
|
|
supportPodcast: false,
|
|
supportScihub: false,
|
|
},
|
|
radar: [
|
|
{
|
|
source: ['www.perplexity.ai/hub'],
|
|
target: '/blog',
|
|
},
|
|
],
|
|
name: 'Blog',
|
|
maintainers: ['seeyangzhi'],
|
|
handler,
|
|
description: "Perplexity Blog - Explore Perplexity's blog for articles, announcements, product updates, and tips to optimize your experience. Stay informed and make the most of Perplexity.",
|
|
view: ViewType.Notifications,
|
|
};
|
|
|
|
async function handler(ctx: Context) {
|
|
const limit = Number(ctx.req.query('limit') ?? '20');
|
|
const rootUrl = 'https://www.perplexity.ai/hub';
|
|
|
|
const { page, destroy, context } = await getPlaywrightPage(rootUrl, {
|
|
onBeforeLoad: async (page) => {
|
|
await page.route('**/*', (route) => {
|
|
const request = route.request();
|
|
request.resourceType() === 'document' ? route.continue() : route.abort();
|
|
});
|
|
},
|
|
});
|
|
|
|
const html = await page.evaluate(() => document.documentElement.innerHTML);
|
|
const $ = load(html);
|
|
|
|
const items: DataItem[] = [];
|
|
|
|
const seenLinks = new Set<string>();
|
|
|
|
// Step 1: Extract featured article using data-framer-name attribute
|
|
const featuredCard = $('[data-framer-name="Featured Card"]').first();
|
|
const featuredHref = featuredCard.find('a[href^="./hub/blog/"]').first().attr('href');
|
|
const featuredTitle = featuredCard.find('h4').first().text().trim();
|
|
|
|
if (featuredHref && featuredTitle) {
|
|
const link = new URL(featuredHref, rootUrl).href;
|
|
seenLinks.add(link);
|
|
items.push({
|
|
link,
|
|
title: featuredTitle,
|
|
});
|
|
}
|
|
|
|
// Step 2: Extract regular articles using data-framer-name="Article Card"
|
|
for (const element of $('[data-framer-name="Article Card"]').toArray()) {
|
|
const $card = $(element);
|
|
const href = $card.attr('href');
|
|
const title = $card.find('h6').first().text().trim();
|
|
|
|
if (!href || !title) {
|
|
continue;
|
|
}
|
|
|
|
const link = new URL(href, rootUrl).href;
|
|
|
|
if (seenLinks.has(link)) {
|
|
continue;
|
|
}
|
|
seenLinks.add(link);
|
|
|
|
// First <p> contains the date, subsequent <p> elements are categories
|
|
const paragraphs = $card.find('p').toArray();
|
|
const dateText = paragraphs.length > 0 ? $(paragraphs[0]).text().trim() : '';
|
|
const pubDate = dateText ? parseDate(dateText) : undefined;
|
|
|
|
const category = paragraphs
|
|
.slice(1)
|
|
.map((p) => $(p).text().trim())
|
|
.filter(Boolean);
|
|
|
|
items.push({
|
|
link,
|
|
title,
|
|
pubDate,
|
|
category: category.length > 0 ? category : undefined,
|
|
});
|
|
}
|
|
|
|
// Step 3: Fetch detail pages for items missing pubDate (e.g., featured article)
|
|
// and extract article content for description
|
|
const resultItems = await Promise.all(
|
|
items.slice(0, limit).map(async (item) => {
|
|
if (!item.link) {
|
|
return item;
|
|
}
|
|
|
|
return (await cache.tryGet(item.link, async () => {
|
|
const contentPage = await context.newPage();
|
|
|
|
await contentPage.route('**/*', (route) => {
|
|
const request = route.request();
|
|
request.resourceType() === 'document' ? route.continue() : route.abort();
|
|
});
|
|
|
|
await contentPage.goto(item.link!, {
|
|
waitUntil: 'domcontentloaded',
|
|
});
|
|
|
|
const contentHtml = await contentPage.evaluate(() => document.documentElement.innerHTML);
|
|
await contentPage.close();
|
|
|
|
const $content = load(contentHtml);
|
|
|
|
let pubDate: string | number | Date | undefined = item.pubDate;
|
|
if (!pubDate) {
|
|
const timeEl = $content('time[datetime]').first();
|
|
if (timeEl.length) {
|
|
pubDate = parseDate(timeEl.attr('datetime')!);
|
|
}
|
|
}
|
|
|
|
$content('script, style, noscript').remove();
|
|
|
|
const contentArea = $content('[data-framer-name="Content"]').first();
|
|
const description = contentArea.length ? (contentArea.html() ?? undefined) : undefined;
|
|
|
|
return {
|
|
...item,
|
|
pubDate,
|
|
description,
|
|
} as DataItem;
|
|
})) as DataItem;
|
|
})
|
|
);
|
|
|
|
await destroy();
|
|
|
|
return {
|
|
title: 'Perplexity Blog',
|
|
link: rootUrl,
|
|
item: resultItems,
|
|
language: 'en',
|
|
} satisfies Data;
|
|
}
|