feat(route): add perplexity changelog route (#20919)

* feat(route): add perplexity changelog route

* fix: address review comments

- Use ^= instead of *= for href selector
- Remove CSS-in-JS class selector (framer-styles-preset-16bzrdu)
- Use map instead of each + push
- Remove HTML caching (per review feedback)
- Fix date format parsing (MM.DD.YY not YY.MM.DD)
- Fix summary selector fallback logic
- Update maintainer to xbot
This commit is contained in:
Donie Leigh 2026-01-20 03:13:50 +08:00 committed by GitHub
parent dd5e1ffcf8
commit 77b7763cd7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 141 additions and 0 deletions

View File

@ -0,0 +1,133 @@
import { load } from 'cheerio';
import type { Context } from 'hono';
import type { Data, DataItem, Route } from '@/types';
import { ViewType } from '@/types';
import logger from '@/utils/logger';
import { parseDate } from '@/utils/parse-date';
import { getPuppeteerPage } from '@/utils/puppeteer';
export const handler = async (ctx: Context): Promise<Data> => {
const limit = Number.parseInt(ctx.req.query('limit') ?? '20', 10);
const baseUrl = 'https://www.perplexity.ai';
const targetUrl = `${baseUrl}/changelog`;
logger.http(`Fetching Perplexity changelog from ${targetUrl}`);
const { page, destory } = await getPuppeteerPage(targetUrl, {
onBeforeLoad: async (page) => {
await page.setRequestInterception(true);
page.on('request', (request) => {
request.resourceType() === 'document' ? request.continue() : request.abort();
});
},
});
const html = await page.evaluate(() => document.documentElement.innerHTML);
await destory();
const $ = load(html);
const language = $('html').attr('lang') ?? 'en';
const seenLinks = new Set<string>();
const items = $('a[href^="./changelog/"]')
.toArray()
.map((elem) => {
const $link = $(elem);
const href = $link.attr('href');
if (!href || !href.startsWith('./changelog/')) {
return null;
}
const fullLink = href.startsWith('http') ? href : `${baseUrl}${href.replace('./', '/')}`;
if (seenLinks.has(fullLink)) {
return null;
}
const $title = $link.find('[data-framer-name="Title"] p').first();
const title = $title.text().trim();
if (!title) {
return null;
}
const $category = $link.find('[data-framer-name="Category"] p').first();
const dateText = $category.text().trim();
let $summary = $link.find('[data-framer-name="Description"] p, [data-framer-name="Summary"] p').first();
if (!$summary.length) {
$summary = $link.find('p.framer-text').not($title).not($category).first();
}
const summary = $summary.text().trim();
seenLinks.add(fullLink);
let pubDate: Date | undefined;
if (dateText) {
// Format: MM.DD.YY or MM.DD.YYYY (e.g., 12.12.24 = December 12, 2024)
const dateMatch = dateText.match(/(\d{2})\.(\d{2})\.(\d{2,4})/);
if (dateMatch) {
const [, month, day, year] = dateMatch;
const fullYear = year.length === 2 ? `20${year}` : year;
pubDate = parseDate(`${fullYear}-${month}-${day}`);
} else {
pubDate = parseDate(dateText);
}
} else {
const dateMatch = title.match(/(January|February|March|April|May|June|July|August|September|October|November|December)\s+\d{1,2}(?:st|nd|rd|th)?,\s*\d{4}/);
if (dateMatch) {
pubDate = parseDate(dateMatch[0]);
}
}
return {
title,
description: summary,
link: fullLink,
pubDate,
guid: `perplexity-changelog-${fullLink}`,
id: `perplexity-changelog-${fullLink}`,
} as DataItem;
})
.filter((item): item is DataItem => item !== null);
return {
title: $('title').text() || 'Perplexity Changelog',
description: $('meta[name="description"], meta[property="og:description"]').first().attr('content') || 'Latest updates and changes from Perplexity',
link: targetUrl,
item: items.slice(0, limit),
allowEmpty: true,
image: $('meta[property="og:image"]').attr('content'),
language: language as 'en',
};
};
export const route: Route = {
path: '/changelog',
name: 'Changelog',
url: 'www.perplexity.ai',
maintainers: ['xbot'],
handler,
example: '/perplexity/changelog',
description: 'Subscribe to Perplexity changelog for latest updates and releases.',
categories: ['program-update'],
features: {
requireConfig: false,
requirePuppeteer: true,
antiCrawler: false,
supportRadar: true,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['www.perplexity.ai/changelog'],
target: '/changelog',
},
],
view: ViewType.Articles,
};

View File

@ -0,0 +1,8 @@
import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'Perplexity',
url: 'www.perplexity.ai',
description: 'Perplexity - AI-powered search and discovery engine',
lang: 'en',
};