Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions lib/routes/hex2077/daily.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { load } from 'cheerio';

Check failure

Code scanning / oxlint

simple-import-sort(imports) Error

Run autofix to sort these imports!
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
import type { CheerioAPI } from 'cheerio';
import type { Route, DataItem } from '@/types';
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';

const BASE = 'https://hex2077.dev';

const SECTION_IDS = [
'产品与功能更新',
'前沿研究',
'行业展望与社会影响',
'开源top项目',
'社媒分享',
];

const SECTION_NAMES = [
'产品与功能更新',
'前沿研究',
'行业展望与社会影响',
'开源TOP项目',
'社媒分享',
];

function extractSection($: CheerioAPI, sectionName: string): string[] {
const ol = $(`h3[id="${sectionName}"]`).nextAll('ol').first();
if (!ol.length) {
return [];
}

const items: string[] = [];
ol.find('> li').each((_, liEl) => {
const text = $(liEl).text().trim().replace(/\s+/g, ' ');

Check failure

Code scanning / oxlint

unicorn(prefer-string-replace-all) Error

Prefer String#replaceAll() over String#replace() when using a regex with the global flag.
Replace replace with replaceAll.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
if (text) {
items.push(text);
}
});
return items;
}

export const route: Route = {
name: 'AI 日报',
categories: ['programming'],
path: '/daily/:section?',
example: '/hex2077/daily',
maintainers: ['fc525260'],
parameters: {
section: {
description:
'栏目序号(可选):1=产品与功能更新,2=前沿研究,3=行业展望与社会影响,4=开源TOP项目,5=社媒分享。留空返回全文。',
},
},
handler: async (ctx) => {
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
const sectionParam = ctx.req.param('section');
const sectionIndex = sectionParam ? parseInt(sectionParam, 10) - 1 : -1;

Check failure

Code scanning / oxlint

unicorn(prefer-number-properties) Error

Use Number.parseInt instead of the global parseInt
Replace it with Number.parseInt
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed

// ── Step 1: 从 listing 页获取最新文章 ─────────────────
const listingHtml = await ofetch<string>(BASE + '/docs/');
const $ = load(listingHtml);

const paths = $('a[href^="/docs/20"]')

Check failure

Code scanning / oxlint

eslint-js(no-restricted-syntax) Error

Please use .toArray() instead.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
.map((_, el) => $(el).attr('href') || '')
.filter((_, href) => /^\/docs\/\d{4}-\d{2}\/\d{4}-\d{2}-\d{2}\/$/.test(href))
.get()
.sort((a, b) => b.localeCompare(a));

Check warning

Code scanning / oxlint

unicorn(no-array-sort) Warning

Use Array#toSorted() instead of Array#sort().
Array#sort() mutates the original array. Use Array#toSorted() to return a new sorted array without modifying the original.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
const latestPath = paths[0];
if (!latestPath) {
throw new Error('未找到日报文章');
}

const dateLabel = latestPath.match(/\d{4}-\d{2}-\d{2}/)?.[0] || '';
const articleUrl = BASE + latestPath;

// ── Step 2: 抓取详情页 ─────────────────────────────
const detailHtml = await ofetch<string>(articleUrl);
const $d = load(detailHtml);

// ── Step 3: 解析并组装 RSS item ────────────────────
const items: DataItem[] = [];

if (sectionIndex >= 0 && sectionIndex < SECTION_IDS.length) {
// 单栏目模式
const sectionName = SECTION_IDS[sectionIndex];
const sectionDisplay = SECTION_NAMES[sectionIndex];
const sectionItems = extractSection($d, sectionName);

for (const [i, text] of sectionItems.entries()) {
items.push({
title: text,
description: text,
link: articleUrl,
guid: `${latestPath}${sectionName}-${i}`,
pubDate: parseDate(dateLabel),
});
}

return {
title: `hex2077 AI日报 · ${sectionDisplay} (${dateLabel})`,
link: BASE + '/docs/',
description: `hex2077 每日 AI 资讯日报 - ${sectionDisplay}`,
language: 'zh-CN',
item: items,
};
} else {
// 全文模式:遍历所有栏目
const allItems = SECTION_IDS.flatMap((sectionName, si) => {
const sectionDisplay = SECTION_NAMES[si];
return extractSection($d, sectionName).map((text, i) => ({
title: `[${sectionDisplay}] ${text}` as const,
description: text,
link: articleUrl,
guid: `${latestPath}${sectionName}-${i}`,
pubDate: parseDate(dateLabel),
}));
});

return {
title: `hex2077 AI日报 · 全文 (${dateLabel})`,
link: BASE + '/docs/',
description: 'hex2077 每日 AI 资讯日报 - 全 5 个栏目',
language: 'zh-CN',
item: allItems,
};
}
},
};
9 changes: 9 additions & 0 deletions lib/routes/hex2077/namespace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { Namespace } from '@/types';

export const namespace: Namespace = {
name: 'hex2077 AI 日报',
url: 'hex2077.dev/docs',
lang: 'zh-CN',
description:
'hex2077.dev 每日发布的 AI 资讯日报,涵盖产品功能、前沿研究、行业影响、开源项目等。',
};