diff --git a/README.md b/README.md index 5d225cd..7104286 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,9 @@ > 🟢 状态正常 / 🟠 可能失效 / ❌ 无法使用 / ⚠️ 需要科学上网 +
+查看全部接口状态 + | **站点** | **类别** | **调用名称** | **状态** | | ---------------- | ------------ | -------------- | -------- | | 哔哩哔哩 | 热门榜 | bilibili | 🟢 | @@ -52,10 +55,13 @@ | CSDN | 排行榜 | csdn | 🟢 | | 稀土掘金 | 热榜 | juejin | 🟢 | | 腾讯新闻 | 热点榜 | qq-news | 🟢 | +| 新浪网 | 热榜 | sina | 🟢 | +| 新浪新闻 | 热点榜 | sina-news | 🟢 | | 网易新闻 | 热点榜 | netease-news | 🟢 | | 吾爱破解 | 榜单 | 52pojie | ❌ | | 全球主机交流 | 榜单 | hostloc | ❌ | | 虎嗅 | 24小时 | huxiu | 🟢 | +| 虎扑 | 步行街热帖 | hupu | 🟢 | | 爱范儿 | 快讯 | ifanr | 🟢 | | 英雄联盟 | 更新公告 | lol | 🟢 | | 原神 | 最新消息 | genshin | 🟢 | @@ -69,6 +75,8 @@ | 中国地震台 | 地震速报 | earthquake | 🟢 | | 历史上的今天 | 月-日 | history | 🟢 | +
+ ## ⚙️ 使用 本项目支持 `Node.js` 调用,可在安装完成后调用 `serveHotApi` 来开启服务器 diff --git a/package.json b/package.json index c763052..d399b06 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dailyhot-api", - "version": "2.0.2", + "version": "2.0.3", "description": "An Api on Today's Hot list", "keywords": [ "API", diff --git a/src/router.types.d.ts b/src/router.types.d.ts index f644399..144ead0 100644 --- a/src/router.types.d.ts +++ b/src/router.types.d.ts @@ -277,4 +277,34 @@ export type RouterType = { cover: string; pic_share: string; }; + hupu: { + tid: number; + title: string; + replies: number; + username: string; + time: string; + url: string; + }; + sina: { + base: { + base: { + uniqueId: string; + url: string; + }; + }; + info: { + hotValue: string; + title: string; + }; + }; + "sina-news": { + id: string; + title: string; + media: string; + url: string; + create_date: string; + create_time: string; + top_num: string; + time: string; + }; }; diff --git a/src/routes/36kr.ts b/src/routes/36kr.ts index cc5bbcf..0286c68 100644 --- a/src/routes/36kr.ts +++ b/src/routes/36kr.ts @@ -10,7 +10,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => { name: "36kr", title: "36氪", type: "热榜", - parame: { + params: { type: { name: "热榜分类", type: { diff --git a/src/routes/52pojie.ts b/src/routes/52pojie.ts index d30c73a..ec3bf79 100644 --- a/src/routes/52pojie.ts +++ b/src/routes/52pojie.ts @@ -11,7 +11,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => { name: "52pojie", title: "吾爱破解", type: "榜单", - parame: { + params: { type: { name: "榜单分类", type: { diff --git a/src/routes/acfun.ts b/src/routes/acfun.ts index 0466c0a..d38fe81 100644 --- a/src/routes/acfun.ts +++ b/src/routes/acfun.ts @@ -12,7 +12,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => { title: "AcFun", type: "排行榜", description: "AcFun是一家弹幕视频网站,致力于为每一个人带来欢乐。", - parame: { + params: { type: { name: "频道", type: { diff --git a/src/routes/baidu.ts b/src/routes/baidu.ts index 18f6ea3..43646f7 100644 --- a/src/routes/baidu.ts +++ b/src/routes/baidu.ts @@ -9,7 +9,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => { name: "baidu", title: "百度", type: "热搜榜", - parame: { + params: { type: { name: "热搜类别", type: { diff --git a/src/routes/bilibili.ts b/src/routes/bilibili.ts index 4175599..0ad9c42 100644 --- a/src/routes/bilibili.ts +++ b/src/routes/bilibili.ts @@ -12,7 +12,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => { title: "哔哩哔哩", type: "热门榜", description: "你所热爱的,就是你的生活", - parame: { + params: { type: { name: "排行榜分区", type: { diff --git a/src/routes/douban-movie.ts b/src/routes/douban-movie.ts index f0ba724..3e7f5ca 100644 --- a/src/routes/douban-movie.ts +++ b/src/routes/douban-movie.ts @@ -18,14 +18,14 @@ export const handleRoute = async (_: undefined, noCache: boolean) => { }; // 数据处理 -const getNumbers = (text: string | undefined) => { - if (!text) return 10000000; +const getNumbers = (text: string | undefined): number => { + if (!text) return 0; const regex = /\d+/; const match = text.match(regex); if (match) { return Number(match[0]); } else { - return 10000000; + return 0; } }; @@ -44,7 +44,8 @@ const getList = async (noCache: boolean) => { const listData = listDom.toArray().map((item) => { const dom = $(item); const url = dom.find("a").attr("href") || undefined; - const score = dom.find(".rating_nums").text() ?? "0.0"; + const scoreDom = dom.find(".rating_nums"); + const score = scoreDom.length > 0 ? scoreDom.text() : "0.0"; return { id: getNumbers(url), title: `【${score}】${dom.find("a").attr("title")}`, diff --git a/src/routes/earthquake.ts b/src/routes/earthquake.ts index d96b61a..de0b606 100644 --- a/src/routes/earthquake.ts +++ b/src/routes/earthquake.ts @@ -33,7 +33,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => { name: "earthquake", title: "中国地震台", type: "地震速报", - parame: { + params: { type: { name: "速报分类", type: { diff --git a/src/routes/genshin.ts b/src/routes/genshin.ts index d0b9bb0..41b5a4f 100644 --- a/src/routes/genshin.ts +++ b/src/routes/genshin.ts @@ -10,7 +10,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => { name: "genshin", title: "原神", type: "最新动态", - parame: { + params: { type: { name: "榜单分类", type: { diff --git a/src/routes/hellogithub.ts b/src/routes/hellogithub.ts index 08b054d..2c34420 100644 --- a/src/routes/hellogithub.ts +++ b/src/routes/hellogithub.ts @@ -11,7 +11,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => { title: "HelloGitHub", type: "热门仓库", description: "分享 GitHub 上有趣、入门级的开源项目", - parame: { + params: { sort: { name: "排行榜分区", type: { diff --git a/src/routes/history.ts b/src/routes/history.ts index 92e0397..36b4580 100644 --- a/src/routes/history.ts +++ b/src/routes/history.ts @@ -6,14 +6,14 @@ import { getCurrentDateTime } from "../utils/getTime.js"; export const handleRoute = async (c: ListContext, noCache: boolean) => { // 获取日期 - const day = c.req.query("day") || getCurrentDateTime().day; - const month = c.req.query("month") || getCurrentDateTime().month; + const day = c.req.query("day") || getCurrentDateTime(true).day; + const month = c.req.query("month") || getCurrentDateTime(true).month; const { fromCache, data, updateTime } = await getList({ month, day }, noCache); const routeData: RouterData = { name: "history", title: "历史上的今天", type: `${month}-${day}`, - parame: { + params: { month: "月份", day: "日期", }, diff --git a/src/routes/honkai.ts b/src/routes/honkai.ts index 37c7f56..0be7dc3 100644 --- a/src/routes/honkai.ts +++ b/src/routes/honkai.ts @@ -10,7 +10,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => { name: "honkai", title: "崩坏3", type: "最新动态", - parame: { + params: { type: { name: "榜单分类", type: { diff --git a/src/routes/hostloc.ts b/src/routes/hostloc.ts index 7c26054..84d4635 100644 --- a/src/routes/hostloc.ts +++ b/src/routes/hostloc.ts @@ -11,7 +11,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => { name: "hostloc", title: "全球主机交流", type: "榜单", - parame: { + params: { type: { name: "榜单分类", type: { diff --git a/src/routes/hupu.ts b/src/routes/hupu.ts new file mode 100644 index 0000000..fc1c333 --- /dev/null +++ b/src/routes/hupu.ts @@ -0,0 +1,51 @@ +import type { RouterData, ListContext, Options } from "../types.js"; +import type { RouterType } from "../router.types.js"; +import { get } from "../utils/getData.js"; + +export const handleRoute = async (c: ListContext, noCache: boolean) => { + const type = c.req.query("type") || "1"; + const { fromCache, data, updateTime } = await getList({ type }, noCache); + const routeData: RouterData = { + name: "hupu", + title: "虎扑", + type: "步行街热帖", + params: { + type: { + name: "榜单分类", + type: { + 1: "主干道", + 6: "恋爱区", + 11: "校园区", + 12: "历史区", + 612: "摄影区", + }, + }, + }, + link: "https://bbs.hupu.com/all-gambia", + total: data?.length || 0, + updateTime, + fromCache, + data, + }; + return routeData; +}; + +const getList = async (options: Options, noCache: boolean) => { + const { type } = options; + const url = `https://m.hupu.com/api/v2/bbs/topicThreads?topicId=${type}&page=1`; + const result = await get({ url, noCache }); + const list = result.data.data.topicThreads; + return { + fromCache: result.fromCache, + updateTime: result.updateTime, + data: list.map((v: RouterType["hupu"]) => ({ + id: v.tid, + title: v.title, + author: v.username, + hot: v.replies, + timestamp: null, + url: `https://bbs.hupu.com/${v.tid}.html`, + mobileUrl: v.url, + })), + }; +}; diff --git a/src/routes/sina-news.ts b/src/routes/sina-news.ts new file mode 100644 index 0000000..720d9a7 --- /dev/null +++ b/src/routes/sina-news.ts @@ -0,0 +1,137 @@ +import type { RouterData, ListContext, Options } from "../types.js"; +import type { RouterType } from "../router.types.js"; +import { getTime, getCurrentDateTime } from "../utils/getTime.js"; +import { get } from "../utils/getData.js"; + +// 榜单类别 +const listType = { + "1": { + name: "总排行", + www: "news", + params: "www_www_all_suda_suda", + }, + "2": { + name: "视频排行", + www: "news", + params: "video_news_all_by_vv", + }, + "3": { + name: "图片排行", + www: "news", + params: "total_slide_suda", + }, + "4": { + name: "国内新闻", + www: "news", + params: "news_china_suda", + }, + "5": { + name: "国际新闻", + www: "news", + params: "news_world_suda", + }, + "6": { + name: "社会新闻", + www: "news", + params: "news_society_suda", + }, + "7": { + name: "体育新闻", + www: "sports", + params: "sports_suda", + }, + "8": { + name: "财经新闻", + www: "finance", + params: "finance_0_suda", + }, + "9": { + name: "娱乐新闻", + www: "ent", + params: "ent_suda", + }, + "10": { + name: "科技新闻", + www: "tech", + params: "tech_news_suda", + }, + "11": { + name: "军事新闻", + www: "news", + params: "news_mil_suda", + }, +}; + +export const handleRoute = async (c: ListContext, noCache: boolean) => { + const type = c.req.query("type") || "1"; + const { fromCache, data, updateTime } = await getList({ type }, noCache); + const routeData: RouterData = { + name: "sina-news", + title: "新浪新闻", + type: listType[type].name, + params: { + type: { + name: "榜单分类", + type: Object.fromEntries(Object.entries(listType).map(([key, value]) => [key, value.name])), + }, + }, + link: "https://sinanews.sina.cn/", + total: data?.length || 0, + updateTime, + fromCache, + data, + }; + return routeData; +}; + +// JSONP 处理 +const parseData = (data: string) => { + // 移除前后多余空白 + if (!data) throw new Error("Input data is empty or invalid"); + // 提取 JSON 字符串部分 + const prefix = "var data = "; + if (!data.startsWith(prefix)) + throw new Error("Input data does not start with the expected prefix"); + let jsonString = data.slice(prefix.length).trim(); + // 确保字符串以 ';' 结尾并移除它 + if (jsonString.endsWith(";")) { + jsonString = jsonString.slice(0, -1).trim(); + } else { + throw new Error("Input data does not end with a semicolon"); + } + // 格式是否正确 + if (jsonString.startsWith("{") && jsonString.endsWith("}")) { + // 解析为 JSON 对象 + try { + const jsonData = JSON.parse(jsonString); + return jsonData; + } catch (error) { + throw new Error("Failed to parse JSON: " + error.message); + } + } else { + throw new Error("Invalid JSON format"); + } +}; + +const getList = async (options: Options, noCache: boolean) => { + const { type } = options; + // 必要数据 + const { params, www } = listType[type]; + const { year, month, day } = getCurrentDateTime(true); + const url = `https://top.${www}.sina.com.cn/ws/GetTopDataList.php?top_type=day&top_cat=${params}&top_time=${year + month + day}&top_show_num=50`; + const result = await get({ url, noCache }); + const list = parseData(result.data).data; + return { + fromCache: result.fromCache, + updateTime: result.updateTime, + data: list.map((v: RouterType["sina-news"]) => ({ + id: v.id, + title: v.title, + author: v.media || null, + hot: parseFloat(v.top_num.replace(/,/g, "")), + timestamp: getTime(v.create_date + " " + v.create_time), + url: v.url, + mobileUrl: v.url, + })), + }; +}; diff --git a/src/routes/sina.ts b/src/routes/sina.ts new file mode 100644 index 0000000..2f08654 --- /dev/null +++ b/src/routes/sina.ts @@ -0,0 +1,62 @@ +import type { RouterData, ListContext, Options } from "../types.js"; +import type { RouterType } from "../router.types.js"; +import { parseChineseNumber } from "../utils/getNum.js"; +import { get } from "../utils/getData.js"; + +export const handleRoute = async (c: ListContext, noCache: boolean) => { + const type = c.req.query("type") || "1"; + const { fromCache, data, updateTime } = await getList({ type }, noCache); + const routeData: RouterData = { + name: "sina", + title: "新浪网", + type: "热榜太多,一个就够", + params: { + type: { + name: "榜单分类", + type: { + all: "新浪热榜", + hotcmnt: "热议榜", + minivideo: "视频热榜", + ent: "娱乐热榜", + ai: "AI热榜", + auto: "汽车热榜", + mother: "育儿热榜", + fashion: "时尚热榜", + travel: "旅游热榜", + esg: "ESG热榜", + }, + }, + }, + link: "https://sinanews.sina.cn/", + total: data?.length || 0, + updateTime, + fromCache, + data, + }; + return routeData; +}; + +const getList = async (options: Options, noCache: boolean) => { + const { type } = options; + const url = `https://newsapp.sina.cn/api/hotlist?newsId=HB-1-snhs%2Ftop_news_list-${type}`; + const result = await get({ url, noCache }); + const list = result.data.data.hotList; + return { + fromCache: result.fromCache, + updateTime: result.updateTime, + data: list.map((v: RouterType["sina"]) => { + const base = v.base; + const info = v.info; + return { + id: base.base.uniqueId, + title: info.title, + desc: null, + author: null, + timestamp: null, + hot: parseChineseNumber(info.hotValue), + url: base.base.url, + mobileUrl: base.base.url, + }; + }), + }; +}; diff --git a/src/routes/sspai.ts b/src/routes/sspai.ts index adf519d..58f1344 100644 --- a/src/routes/sspai.ts +++ b/src/routes/sspai.ts @@ -10,7 +10,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => { name: "sspai", title: "少数派", type: "热榜", - parame: { + params: { type: { name: "分类", type: ["热门文章", "应用推荐", "生活方式", "效率技巧", "少数派播客"], diff --git a/src/routes/starrail.ts b/src/routes/starrail.ts index ca0bfa0..c63ddda 100644 --- a/src/routes/starrail.ts +++ b/src/routes/starrail.ts @@ -10,7 +10,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => { name: "starrail", title: "崩坏:星穹铁道", type: "最新动态", - parame: { + params: { type: { name: "榜单分类", type: { diff --git a/src/routes/v2ex.ts b/src/routes/v2ex.ts index 706501b..e0f875b 100644 --- a/src/routes/v2ex.ts +++ b/src/routes/v2ex.ts @@ -9,7 +9,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => { name: "v2ex", title: "V2EX", type: "主题榜", - parame: { + params: { type: { name: "榜单分类", type: { diff --git a/src/routes/weatheralarm.ts b/src/routes/weatheralarm.ts index 2c4d749..83063de 100644 --- a/src/routes/weatheralarm.ts +++ b/src/routes/weatheralarm.ts @@ -10,7 +10,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => { name: "weatheralarm", title: "中央气象台", type: type || "全国气象预警", - parame: { + params: { province: { name: "预警区域", value: "省份名称( 例如:广东省 )", diff --git a/src/types.d.ts b/src/types.d.ts index 6102234..ca851f5 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -22,7 +22,7 @@ export type RouterData = { title: string; type: string; description?: string; - parame?: Record; + params?: Record; total: number; link?: string; updateTime: string; diff --git a/src/utils/getNum.ts b/src/utils/getNum.ts new file mode 100644 index 0000000..1b2bad7 --- /dev/null +++ b/src/utils/getNum.ts @@ -0,0 +1,20 @@ +export const parseChineseNumber = (chineseNumber: string): number => { + // 单位对照表 + const units: { [key: string]: number } = { + 亿: 1e8, + 万: 1e4, + 千: 1e3, + 百: 1e2, + }; + + // 遍历单位对照表 + for (const unit in units) { + if (chineseNumber.includes(unit)) { + // 转换为数字 + const numberPart = parseFloat(chineseNumber.replace(unit, "")); + return numberPart * units[unit]; + } + } + + return parseFloat(chineseNumber); +}; diff --git a/src/utils/getTime.ts b/src/utils/getTime.ts index 1df59c4..6ebe4d0 100644 --- a/src/utils/getTime.ts +++ b/src/utils/getTime.ts @@ -1,12 +1,12 @@ import dayjs from "dayjs"; interface CurrentDateTime { - year: number; - month: number; - day: number; - hour: number; - minute: number; - second: number; + year: string; + month: string; + day: string; + hour: string; + minute: string; + second: string; } export const getTime = (timeInput: string | number): number | null => { try { @@ -65,15 +65,18 @@ export const getTime = (timeInput: string | number): number | null => { } }; -export const getCurrentDateTime = (): CurrentDateTime => { +export const getCurrentDateTime = (padZero: boolean = false): CurrentDateTime => { const now = dayjs(); + // 补零 + const pad = (num: number): string => (num < 10 ? `0${num}` : `${num}`); + return { - year: now.year(), - month: now.month() + 1, - day: now.date(), - hour: now.hour(), - minute: now.minute(), - second: now.second(), + year: now.year().toString(), + month: padZero ? pad(now.month() + 1) : (now.month() + 1).toString(), + day: padZero ? pad(now.date()) : now.date().toString(), + hour: padZero ? pad(now.hour()) : now.hour().toString(), + minute: padZero ? pad(now.minute()) : now.minute().toString(), + second: padZero ? pad(now.second()) : now.second().toString(), }; };