feat(route): add AI 博客 (#20488)

* feat(route): add AI 博客

新增了一个归档路由处理函数,用于抓取AI博客的所有文章归档。

- 定义了一个新的路由配置对象`route`,路径为`/archives`,属于`blog`类别。
- 使用`ofetch`获取归档页面内容,并通过`cheerio`解析HTML。
- 遍历每个月份分组内的每篇文章,提取标题、链接、发布时间,并存储在列表中。
- 对列表中的每个条目,使用`cache.tryGet`缓存文章详情,若无缓存则再次使用`ofetch`获取,并解析出文章的主要内容。
- 返回处理后的结果对象,包含优化后的标题、归档页面链接以及过滤无效数据后的文章列表。

新增了一个命名空间配置对象`namespace`,用于定义AI博客的基本信息。

- 定义了一个新的命名空间配置对象`namespace`,包含站点名称、URL和语言。

* refactor(handler): 优化归档页面处理逻辑

- 使用 `toArray()` 和 `flatMap()` 方法简化了遍历每个月份分组和提取文章信息的代码逻辑。
- 修正了 `link` 属性的处理,确保当链接不存在时返回空字符串。
- 添加了 `author` 和 `description` 属性到文章对象中,以便更完整地描述文章内容。
- 精简了每个文章项的 `description` 构建,去除了不必要的 HTML 结构复制。
This commit is contained in:
Liao-Ke 2025-11-16 23:48:33 +08:00 committed by GitHub
parent 92032a8c51
commit 6e5409d7eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 99 additions and 0 deletions

View File

@ -0,0 +1,92 @@
import { Route } from '@/types';
import cache from '@/utils/cache';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
import { load } from 'cheerio';
export const route: Route = {
path: '/archives',
categories: ['blog'],
example: '/aiblog-2xv/archives',
parameters: {},
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['aiblog-2xv.pages.dev/archives'],
target: '/archives',
},
],
name: '归档-全部文章',
maintainers: ['Liao-Ke'],
handler,
};
async function handler() {
const baseUrl = 'https://aiblog-2xv.pages.dev';
const response = await ofetch(`${baseUrl}/archives`);
const $ = load(response);
// 遍历每个月份分组
const list = $('#top > main > div > div.archive-month')
.toArray()
.flatMap((monthItem) =>
$(monthItem)
.find('.archive-posts .archive-entry')
.toArray()
.map((postItem) => {
const $post = $(postItem);
const $link = $post.find('a').first();
const $title = $post.find('h3').first();
const $dateMeta = $post.find('.archive-meta span');
return {
title: $title.text().trim(), // 去除首尾空格
link: $link.attr('href') || '',
// 解析发布时间和更新时间(根据页面结构调整选择器,若存在则启用)
pubDate: parseDate($dateMeta.eq(0).attr('title') || ''),
author: $post.find('.archive-meta span').last().text().trim() || '',
description: '',
};
})
);
const items = await Promise.all(
list.map((item) =>
cache.tryGet(item.link, async () => {
const response = await ofetch(item.link);
const $ = load(response);
const $main = $('main').first();
item.description = `<article>
<header>
<div class="post-description">
${$main.find('header .post-description').first().html()}
</div>
</header>
<figure class="entry-cover">
${$main.find('figure').first().html()}
</figure>
<div class="post-content">
${$main.find('.post-content').first().html()}
</div>
</article>`;
return item;
})
)
);
return {
title: '归档-全部文章 | AI Blog', // 优化标题,增加站点标识
link: `${baseUrl}/archives`,
item: items.filter((item) => item.title && item.link), // 过滤无效数据
};
}

View File

@ -0,0 +1,7 @@
import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'AI 博客',
url: 'aiblog-2xv.pages.dev',
lang: 'zh-CN',
};