Skip to content

Commit f561ec9

Browse files
committed
feat(route): add futu post
1 parent 77b7763 commit f561ec9

2 files changed

Lines changed: 95 additions & 0 deletions

File tree

lib/routes/futu/namespace.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { Namespace } from '@/types';
2+
3+
export const namespace: Namespace = {
4+
name: 'Futu',
5+
url: 'news.futunn.com',
6+
lang: 'zh-CN',
7+
};

lib/routes/futu/post.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { load } from 'cheerio';
2+
import dayjs from 'dayjs';
3+
import customParseFormat from 'dayjs/plugin/customParseFormat.js';
4+
5+
import type { Route } from '@/types';
6+
import ofetch from '@/utils/ofetch';
7+
import { parseDate, parseRelativeDate } from '@/utils/parse-date';
8+
import timezone from '@/utils/timezone';
9+
10+
dayjs.extend(customParseFormat);
11+
12+
export const route: Route = {
13+
name: 'Futu Morning Post',
14+
categories: ['finance'],
15+
maintainers: ['raxod502'],
16+
path: '/post',
17+
example: '/futu/post',
18+
handler,
19+
radar: [
20+
{
21+
source: ['news.futunn.com/news-topics/162/futu-morning-post/'],
22+
target: '/futu/post',
23+
},
24+
],
25+
};
26+
27+
async function handler() {
28+
const baseUrl = 'https://news.futunn.com/news-topics/162/futu-morning-post/';
29+
const response = await ofetch(baseUrl);
30+
const $ = load(response);
31+
32+
const items = $('.related-news .news-item')
33+
.toArray()
34+
.map((item) => {
35+
const $item = $(item);
36+
const title = $item.find('.title').first().text();
37+
const timeText = $item.find('.time').first().text();
38+
const pubDate = parseTime(timeText);
39+
let link = $item.find('a').first().attr('href');
40+
if (link) {
41+
try {
42+
link = new URL(link, baseUrl).href;
43+
} catch {
44+
// Invalid URL, keep original
45+
}
46+
}
47+
return {
48+
title,
49+
link,
50+
pubDate,
51+
};
52+
});
53+
54+
return {
55+
title: 'Futu Morning Post',
56+
link: baseUrl,
57+
item: items,
58+
};
59+
}
60+
61+
function parseTime(timeText: string): Date | undefined {
62+
const trimmedText = timeText.trim();
63+
64+
// Handle relative time like "47分钟前"
65+
if (/^\d+\s*(|||)[]?/.test(trimmedText)) {
66+
const relativeDate = parseRelativeDate(trimmedText);
67+
return relativeDate instanceof Date ? timezone(relativeDate, +8) : undefined;
68+
}
69+
// Handle format like "01/19 08:20" (MM/DD HH:mm)
70+
if (/^\d{2}\/\d{2}\s+\d{2}:\d{2}$/.test(trimmedText)) {
71+
const currentYear = dayjs().year();
72+
const parsed = dayjs(`${currentYear}/${trimmedText}`, 'YYYY/MM/DD HH:mm', true);
73+
return parsed.isValid() ? timezone(parsed.toDate(), +8) : undefined;
74+
}
75+
// Handle format like "08:09" (HH:mm) - assume today
76+
if (/^\d{2}:\d{2}$/.test(trimmedText)) {
77+
const today = dayjs().format('YYYY-MM-DD');
78+
const parsed = dayjs(`${today} ${trimmedText}`, 'YYYY-MM-DD HH:mm', true);
79+
return parsed.isValid() ? timezone(parsed.toDate(), +8) : undefined;
80+
}
81+
// Try standard parseDate
82+
try {
83+
return timezone(parseDate(trimmedText), +8);
84+
} catch {
85+
const relativeDate = parseRelativeDate(trimmedText);
86+
return relativeDate instanceof Date ? timezone(relativeDate, +8) : undefined;
87+
}
88+
}

0 commit comments

Comments
 (0)