diff --git a/src/router.types.d.ts b/src/router.types.d.ts index 720daab..3693e77 100644 --- a/src/router.types.d.ts +++ b/src/router.types.d.ts @@ -385,4 +385,29 @@ export type RouterType = { desc: string; timestamp: string; }; + hackernews: { + id: string; + title: string; + hot: number | undefined; + timestamp: number | undefined; + url: string; + mobileUrl: string; + }; + github: { + id: string; + title: string; + desc?: string; + hot: number | undefined; + timestamp: number | undefined; + url: string; + mobileUrl: string; + }; + producthunt: { + id: string; + title: string; + hot: number | undefined; + timestamp: number | undefined; + url: string; + mobileUrl: string; + }; }; diff --git a/src/routes/github.ts b/src/routes/github.ts new file mode 100644 index 0000000..e2c1ee5 --- /dev/null +++ b/src/routes/github.ts @@ -0,0 +1,61 @@ +import type { RouterData } from "../types.js"; +import { get } from "../utils/getData.js"; +import { load } from "cheerio"; +import type { RouterType } from "../router.types.js"; + +export const handleRoute = async (_: undefined, noCache: boolean) => { + const listData = await getList(noCache); + const routeData: RouterData = { + name: "github", + title: "GitHub", + type: "Trending", + description: "See what the GitHub community is most excited about today", + link: "https://github.com/trending", + total: listData.data?.length || 0, + ...listData, + }; + return routeData; +}; + +const getList = async (noCache: boolean) => { + const baseUrl = "https://github.com"; + const result = await get({ + url: `${baseUrl}/trending?spoken_language_code=`, + noCache, + headers: { + userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" + } + }); + + try { + const $ = load(result.data); + const stories: RouterType["github"][] = []; + + $("main .Box div[data-hpc] > article").each((_, el) => { + const a = $(el).find(">h2 a"); + const title = a.text().replace(/\n+/g, "").trim(); + const path = a.attr("href"); + const star = $(el).find("[href$=stargazers]").text().replace(/\s+/g, "").trim(); + const desc = $(el).find(">p").text().replace(/\n+/g, "").trim(); + + if (path && title) { + stories.push({ + id: path.slice(1), // 移除开头的 / + title, + desc, + hot: parseInt(star.replace(/,/g, "")) || undefined, + timestamp: undefined, + url: `${baseUrl}${path}`, + mobileUrl: `${baseUrl}${path}`, + }); + } + }); + + return { + ...result, + data: stories, + }; + } catch (error) { + throw new Error(`Failed to parse GitHub Trending HTML: ${error}`); + } +}; \ No newline at end of file diff --git a/src/routes/hackernews.ts b/src/routes/hackernews.ts new file mode 100644 index 0000000..95bd88f --- /dev/null +++ b/src/routes/hackernews.ts @@ -0,0 +1,63 @@ +import type { RouterData } from "../types.js"; +import { get } from "../utils/getData.js"; +import { load } from "cheerio"; +import type { RouterType } from "../router.types.js"; + +export const handleRoute = async (_: undefined, noCache: boolean) => { + const listData = await getList(noCache); + const routeData: RouterData = { + name: "hackernews", + title: "Hacker News", + type: "Popular", + description: "News about hacking and startups", + link: "https://news.ycombinator.com/", + total: listData.data?.length || 0, + ...listData, + }; + return routeData; +}; + +const getList = async (noCache: boolean) => { + const baseUrl = "https://news.ycombinator.com"; + const result = await get({ + url: baseUrl, + noCache, + headers: { + userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" + } + }); + + try { + const $ = load(result.data); + const stories: RouterType["hackernews"][] = []; + + $(".athing").each((_, el) => { + const item = $(el); + const id = item.attr("id") || ""; + const title = item.find(".titleline a").first().text().trim(); + const url = item.find(".titleline a").first().attr("href"); + + // 获取分数并转换为数字 + const scoreText = $(`#score_${id}`).text().match(/\d+/)?.[0]; + const hot = scoreText ? parseInt(scoreText, 10) : undefined; + + if (id && title) { + stories.push({ + id, + title, + hot, + timestamp: undefined, + url: url || `${baseUrl}/item?id=${id}`, + mobileUrl: url || `${baseUrl}/item?id=${id}`, + }); + } + }); + + return { + ...result, + data: stories, + }; + } catch (error) { + throw new Error(`Failed to parse HackerNews HTML: ${error}`); + } +}; \ No newline at end of file diff --git a/src/routes/producthunt.ts b/src/routes/producthunt.ts new file mode 100644 index 0000000..bd190af --- /dev/null +++ b/src/routes/producthunt.ts @@ -0,0 +1,60 @@ +import type { RouterData } from "../types.js"; +import { get } from "../utils/getData.js"; +import { load } from "cheerio"; +import type { RouterType } from "../router.types.js"; + +export const handleRoute = async (_: undefined, noCache: boolean) => { + const listData = await getList(noCache); + const routeData: RouterData = { + name: "producthunt", + title: "Product Hunt", + type: "Today", + description: "The best new products, every day", + link: "https://www.producthunt.com/", + total: listData.data?.length || 0, + ...listData, + }; + return routeData; +}; + +const getList = async (noCache: boolean) => { + const baseUrl = "https://www.producthunt.com"; + const result = await get({ + url: baseUrl, + noCache, + headers: { + userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" + } + }); + + try { + const $ = load(result.data); + const stories: RouterType["producthunt"][] = []; + + $("[data-test=homepage-section-0] [data-test^=post-item]").each((_, el) => { + const a = $(el).find("a").first(); + const path = a.attr("href"); + const title = $(el).find("a[data-test^=post-name]").text().trim(); + const id = $(el).attr("data-test")?.replace("post-item-", ""); + const vote = $(el).find("[data-test=vote-button]").text().trim(); + + if (path && id && title) { + stories.push({ + id, + title, + hot: parseInt(vote) || undefined, + timestamp: undefined, + url: `${baseUrl}${path}`, + mobileUrl: `${baseUrl}${path}`, + }); + } + }); + + return { + ...result, + data: stories, + }; + } catch (error) { + throw new Error(`Failed to parse Product Hunt HTML: ${error}`); + } +}; \ No newline at end of file