21 Commits

Author SHA1 Message Date
imsyy
36802252c0 Merge branch 'master' of github.com:imsyy/DailyHotApi 2024-07-30 11:50:31 +08:00
imsyy
7f5449089d 🔧 build: 更新版本号 2024-07-30 11:49:59 +08:00
底层用户
53c68a3d69 Merge pull request #72 from Kataick/fix-weibo-hot-field
fix(weibo): 修复微博热度字段
2024-07-26 18:30:24 +08:00
Kataick
447e0c6153 fix(weibo): 修复微博热度字段 2024-07-25 21:24:39 +08:00
imsyy
55f4e22693 feat: 新增 NodeSeek by @JianBing77 2024-07-22 18:07:03 +08:00
底层用户
c791e29a59 Merge pull request #71 from JianBing77/master
feat: 新增NodeSeek接口
2024-07-22 17:49:19 +08:00
JianBing77
91775a0763 feat: 新增NodeSeek接口 2024-07-19 11:41:15 +08:00
imsyy
a742f9df78 🐞 fix: 修复米游社系列接口出错 2024-07-09 09:35:27 +08:00
底层用户
f213d9810b Merge pull request #68 from Kataick/fix-zhihu-hot
fix(zhihu): fix hot error
2024-07-08 15:10:29 +08:00
底层用户
250a90cf2e Merge pull request #67 from Kataick/flx-douyin-url
fix(douyin): remove encodeURIComponent from url
2024-07-08 15:10:18 +08:00
Kataick
a4a1055604 fix(zhihu): hot error 2024-07-07 12:48:11 +08:00
Kataick
fd3d911b04 fix(douyin): remove encodeURIComponent from url 2024-07-06 13:24:54 +08:00
imsyy
bf80a2e22c feat: 新增 虎扑 & 新浪网 & 新浪新闻
- 修复历史上的今天 #63
2024-06-25 13:47:40 +08:00
imsyy
6988df58f1 🐞 fix: 修复历史上的今天 2024-06-24 16:02:06 +08:00
imsyy
bcff976a4d feat: 新增 CSDN 2024-06-17 14:06:22 +08:00
imsyy
62a8880ae4 feat: 输出美化 2024-06-13 17:51:44 +08:00
imsyy
e6f89c4868 🔧 build: 依赖更新 2024-06-13 16:53:59 +08:00
imsyy
fa80a29772 feat: 新增 历史上的今天 2024-06-13 14:09:16 +08:00
imsyy
b8f7c1ad23 🐞 fix: 修复错误包含 ts 文件 2024-06-13 10:12:06 +08:00
imsyy
d5217c3dff 🔧 build: 修正类型错误 2024-06-12 17:37:37 +08:00
imsyy
e1beb5b534 🐞 fix: 修复 hellogithub #60 2024-06-11 09:19:41 +08:00
49 changed files with 850 additions and 1056 deletions

View File

@@ -6,8 +6,7 @@ on:
jobs: jobs:
publish-npm: publish-npm:
runs-on: runs-on: ubuntu-latest
ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [20] node-version: [20]

View File

@@ -29,6 +29,9 @@
> 🟢 状态正常 / 🟠 可能失效 / ❌ 无法使用 / ⚠️ 需要科学上网 > 🟢 状态正常 / 🟠 可能失效 / ❌ 无法使用 / ⚠️ 需要科学上网
<details>
<summary>查看全部接口状态</summary>
| **站点** | **类别** | **调用名称** | **状态** | | **站点** | **类别** | **调用名称** | **状态** |
| ---------------- | ------------ | -------------- | -------- | | ---------------- | ------------ | -------------- | -------- |
| 哔哩哔哩 | 热门榜 | bilibili | 🟢 | | 哔哩哔哩 | 热门榜 | bilibili | 🟢 |
@@ -49,12 +52,17 @@
| 今日头条 | 热榜 | toutiao | 🟢 | | 今日头条 | 热榜 | toutiao | 🟢 |
| 36 氪 | 热榜 | 36kr | 🟢 | | 36 氪 | 热榜 | 36kr | 🟢 |
| 51CTO | 推荐榜 | 51cto | 🟢 | | 51CTO | 推荐榜 | 51cto | 🟢 |
| CSDN | 排行榜 | csdn | 🟢 |
| NodeSeek | 最新动态 | nodeseek | 🟢 |
| 稀土掘金 | 热榜 | juejin | 🟢 | | 稀土掘金 | 热榜 | juejin | 🟢 |
| 腾讯新闻 | 热点榜 | qq-news | 🟢 | | 腾讯新闻 | 热点榜 | qq-news | 🟢 |
| 新浪网 | 热榜 | sina | 🟢 |
| 新浪新闻 | 热点榜 | sina-news | 🟢 |
| 网易新闻 | 热点榜 | netease-news | 🟢 | | 网易新闻 | 热点榜 | netease-news | 🟢 |
| 吾爱破解 | 榜单 | 52pojie | ❌ | | 吾爱破解 | 榜单 | 52pojie | ❌ |
| 全球主机交流 | 榜单 | hostloc | ❌ | | 全球主机交流 | 榜单 | hostloc | ❌ |
| 虎嗅 | 24小时 | huxiu | 🟢 | | 虎嗅 | 24小时 | huxiu | 🟢 |
| 虎扑 | 步行街热帖 | hupu | 🟢 |
| 爱范儿 | 快讯 | ifanr | 🟢 | | 爱范儿 | 快讯 | ifanr | 🟢 |
| 英雄联盟 | 更新公告 | lol | 🟢 | | 英雄联盟 | 更新公告 | lol | 🟢 |
| 原神 | 最新消息 | genshin | 🟢 | | 原神 | 最新消息 | genshin | 🟢 |
@@ -66,6 +74,9 @@
| HelloGitHub | Trending | hellogithub | 🟢 | | HelloGitHub | Trending | hellogithub | 🟢 |
| 中央气象台 | 全国气象预警 | weatheralarm | 🟢 | | 中央气象台 | 全国气象预警 | weatheralarm | 🟢 |
| 中国地震台 | 地震速报 | earthquake | 🟢 | | 中国地震台 | 地震速报 | earthquake | 🟢 |
| 历史上的今天 | 月-日 | history | 🟢 |
</details>
## ⚙️ 使用 ## ⚙️ 使用

View File

@@ -1,6 +1,6 @@
{ {
"name": "dailyhot-api", "name": "dailyhot-api",
"version": "2.0.0", "version": "2.0.4",
"description": "An Api on Today's Hot list", "description": "An Api on Today's Hot list",
"keywords": [ "keywords": [
"API", "API",
@@ -17,7 +17,6 @@
"license": "MIT", "license": "MIT",
"author": "imsyy", "author": "imsyy",
"main": "dist/index.js", "main": "dist/index.js",
"types": "src/types.d.ts",
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,
"sourceMap": true, "sourceMap": true,
@@ -25,7 +24,6 @@
"LICENSE", "LICENSE",
"README.md", "README.md",
"dist/**/*", "dist/**/*",
"src/types.d.ts",
"!dist/logs/**/*" "!dist/logs/**/*"
], ],
"scripts": { "scripts": {
@@ -38,29 +36,30 @@
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@hono/node-server": "^1.11.2", "@hono/node-server": "^1.12.0",
"axios": "^1.7.2", "axios": "^1.7.2",
"chalk": "^5.3.0",
"cheerio": "1.0.0-rc.12", "cheerio": "1.0.0-rc.12",
"dayjs": "^1.11.11", "dayjs": "^1.11.12",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"feed": "^4.2.2", "feed": "^4.2.2",
"hono": "^4.4.3", "hono": "^4.5.1",
"md5": "^2.3.0", "md5": "^2.3.0",
"node-cache": "^5.1.2", "node-cache": "^5.1.2",
"puppeteer": "^22.10.0",
"puppeteer-cluster": "^0.24.0",
"rss-parser": "^3.13.0", "rss-parser": "^3.13.0",
"winston": "^3.13.0" "winston": "^3.13.1",
"xml2js": "^0.6.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.14.1", "@types/node": "^20.14.11",
"@typescript-eslint/eslint-plugin": "^7.12.0", "@types/xml2js": "^0.4.14",
"@typescript-eslint/parser": "^7.12.0", "@typescript-eslint/eslint-plugin": "^7.16.1",
"@typescript-eslint/parser": "^7.16.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"prettier": "^3.3.0", "prettier": "^3.3.3",
"tsx": "^3.14.0", "tsx": "^3.14.0",
"typescript": "^5.4.5" "typescript": "^5.5.3"
}, },
"engines": { "engines": {
"node": ">=20" "node": ">=20"

1084
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@ import logger from "./utils/logger.js";
import app from "./app.js"; import app from "./app.js";
// 启动服务器 // 启动服务器
const serveHotApi = (port: number = config.PORT) => { const serveHotApi: (port?: number) => void = (port: number = config.PORT) => {
try { try {
const apiServer = serve({ const apiServer = serve({
fetch: app.fetch, fetch: app.fetch,

View File

@@ -32,7 +32,11 @@ const findTsFiles = (dirPath: string, allFiles: string[] = [], basePath: string
if (stat.isDirectory()) { if (stat.isDirectory()) {
// 如果是文件夹,递归查找 // 如果是文件夹,递归查找
findTsFiles(fullPath, allFiles, relativePath); findTsFiles(fullPath, allFiles, relativePath);
} else if (stat.isFile() && (item.endsWith(".ts") || item.endsWith(".js"))) { } else if (
stat.isFile() &&
(item.endsWith(".ts") || item.endsWith(".js")) &&
!item.endsWith(".d.ts")
) {
// 符合条件 // 符合条件
allFiles.push(relativePath.replace(/\.(ts|js)$/, "")); allFiles.push(relativePath.replace(/\.(ts|js)$/, ""));
} }

55
src/router.types.d.ts vendored
View File

@@ -85,8 +85,8 @@ export type RouterType = {
word: string; word: string;
word_scheme: string; word_scheme: string;
note: string; note: string;
category: string; flag_desc: string;
raw_hot: number; num: number;
onboard_time: number; onboard_time: number;
}; };
zhihu: { zhihu: {
@@ -126,9 +126,7 @@ export type RouterType = {
content: string; content: string;
cover: string; cover: string;
created_at: number; created_at: number;
}; view_status: number;
stat: {
view_num: number;
}; };
user: { user: {
nickname: string; nickname: string;
@@ -260,4 +258,51 @@ export type RouterType = {
comment_count: number; comment_count: number;
created_at: number; created_at: number;
}; };
csdn: {
nickName: string;
articleTitle: string;
articleDetailUrl: string;
picList: [string];
hotRankScore: string;
period: string;
productId: string;
};
history: {
year: string;
title: string;
link: string;
desc: string;
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;
};
}; };

View File

@@ -1,7 +1,7 @@
import type { RouterData, ListContext, Options } from "../types.js"; import type { RouterData, ListContext, Options } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { post } from "../utils/getData.js"; import { post } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (c: ListContext, noCache: boolean) => { export const handleRoute = async (c: ListContext, noCache: boolean) => {
const type = c.req.query("type") || "hot"; const type = c.req.query("type") || "hot";
@@ -10,7 +10,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => {
name: "36kr", name: "36kr",
title: "36氪", title: "36氪",
type: "热榜", type: "热榜",
parameData: { params: {
type: { type: {
name: "热榜分类", name: "热榜分类",
type: { type: {

View File

@@ -2,7 +2,7 @@ import type { RouterData } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { getToken, sign } from "../utils/getToken/51cto.js"; import { getToken, sign } from "../utils/getToken/51cto.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (_: undefined, noCache: boolean) => { export const handleRoute = async (_: undefined, noCache: boolean) => {
const { fromCache, data, updateTime } = await getList(noCache); const { fromCache, data, updateTime } = await getList(noCache);

View File

@@ -2,7 +2,7 @@ import type { RouterData, ListContext, Options } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { web } from "../utils/getData.js"; import { web } from "../utils/getData.js";
import { extractRss, parseRSS } from "../utils/parseRSS.js"; import { extractRss, parseRSS } from "../utils/parseRSS.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (c: ListContext, noCache: boolean) => { export const handleRoute = async (c: ListContext, noCache: boolean) => {
const type = c.req.query("type") || "hot"; const type = c.req.query("type") || "hot";
@@ -11,7 +11,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => {
name: "52pojie", name: "52pojie",
title: "吾爱破解", title: "吾爱破解",
type: "榜单", type: "榜单",
parameData: { params: {
type: { type: {
name: "榜单分类", name: "榜单分类",
type: { type: {

View File

@@ -1,7 +1,7 @@
import type { RouterData, ListContext, Options } from "../types.js"; import type { RouterData, ListContext, Options } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (c: ListContext, noCache: boolean) => { export const handleRoute = async (c: ListContext, noCache: boolean) => {
const type = c.req.query("type") || "-1"; const type = c.req.query("type") || "-1";
@@ -12,7 +12,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => {
title: "AcFun", title: "AcFun",
type: "排行榜", type: "排行榜",
description: "AcFun是一家弹幕视频网站致力于为每一个人带来欢乐。", description: "AcFun是一家弹幕视频网站致力于为每一个人带来欢乐。",
parameData: { params: {
type: { type: {
name: "频道", name: "频道",
type: { type: {

View File

@@ -9,7 +9,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => {
name: "baidu", name: "baidu",
title: "百度", title: "百度",
type: "热搜榜", type: "热搜榜",
parameData: { params: {
type: { type: {
name: "热搜类别", name: "热搜类别",
type: { type: {

View File

@@ -2,7 +2,7 @@ import type { RouterData, ListContext, Options } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getBiliWbi from "../utils/getToken/bilibili.js"; import getBiliWbi from "../utils/getToken/bilibili.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (c: ListContext, noCache: boolean) => { export const handleRoute = async (c: ListContext, noCache: boolean) => {
const type = c.req.query("type") || "0"; const type = c.req.query("type") || "0";
@@ -12,7 +12,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => {
title: "哔哩哔哩", title: "哔哩哔哩",
type: "热门榜", type: "热门榜",
description: "你所热爱的,就是你的生活", description: "你所热爱的,就是你的生活",
parameData: { params: {
type: { type: {
name: "排行榜分区", name: "排行榜分区",
type: { type: {

41
src/routes/csdn.ts Normal file
View File

@@ -0,0 +1,41 @@
import type { RouterData } from "../types.js";
import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js";
import { getTime } from "../utils/getTime.js";
export const handleRoute = async (_: undefined, noCache: boolean) => {
const { fromCache, data, updateTime } = await getList(noCache);
const routeData: RouterData = {
name: "csdn",
title: "CSDN",
type: "排行榜",
description: "专业开发者社区",
link: "https://www.csdn.net/",
total: data?.length || 0,
updateTime,
fromCache,
data,
};
return routeData;
};
const getList = async (noCache: boolean) => {
const url = "https://blog.csdn.net/phoenix/web/blog/hot-rank?page=0&pageSize=30";
const result = await get({ url, noCache });
const list = result.data.data;
return {
fromCache: result.fromCache,
updateTime: result.updateTime,
data: list.map((v: RouterType["csdn"]) => ({
id: v.productId,
title: v.articleTitle,
cover: v.picList?.[0] || null,
desc: null,
author: v.nickName,
timestamp: getTime(v.period),
hot: Number(v.hotRankScore),
url: v.articleDetailUrl,
mobileUrl: v.articleDetailUrl,
})),
};
};

View File

@@ -18,14 +18,14 @@ export const handleRoute = async (_: undefined, noCache: boolean) => {
}; };
// 数据处理 // 数据处理
const getNumbers = (text: string | undefined) => { const getNumbers = (text: string | undefined): number => {
if (!text) return 10000000; if (!text) return 0;
const regex = /\d+/; const regex = /\d+/;
const match = text.match(regex); const match = text.match(regex);
if (match) { if (match) {
return Number(match[0]); return Number(match[0]);
} else { } else {
return 10000000; return 0;
} }
}; };
@@ -44,7 +44,8 @@ const getList = async (noCache: boolean) => {
const listData = listDom.toArray().map((item) => { const listData = listDom.toArray().map((item) => {
const dom = $(item); const dom = $(item);
const url = dom.find("a").attr("href") || undefined; 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 { return {
id: getNumbers(url), id: getNumbers(url),
title: `${score}${dom.find("a").attr("title")}`, title: `${score}${dom.find("a").attr("title")}`,

View File

@@ -1,7 +1,7 @@
import type { RouterData } from "../types.js"; import type { RouterData } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (_: undefined, noCache: boolean) => { export const handleRoute = async (_: undefined, noCache: boolean) => {
const { fromCache, data, updateTime } = await getList(noCache); const { fromCache, data, updateTime } = await getList(noCache);
@@ -54,8 +54,8 @@ const getList = async (noCache: boolean) => {
title: v.word, title: v.word,
timestamp: getTime(v.event_time), timestamp: getTime(v.event_time),
hot: v.hot_value, hot: v.hot_value,
url: `https://www.douyin.com/hot/${encodeURIComponent(v.sentence_id)}`, url: `https://www.douyin.com/hot/${v.sentence_id}`,
mobileUrl: `https://www.douyin.com/hot/${encodeURIComponent(v.sentence_id)}`, mobileUrl: `https://www.douyin.com/hot/${v.sentence_id}`,
})), })),
}; };
}; };

View File

@@ -1,7 +1,7 @@
import type { RouterData, ListContext, Options } from "../types.js"; import type { RouterData, ListContext, Options } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
const mappings = { const mappings = {
O_TIME: "发震时刻(UTC+8)", O_TIME: "发震时刻(UTC+8)",
@@ -33,7 +33,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => {
name: "earthquake", name: "earthquake",
title: "中国地震台", title: "中国地震台",
type: "地震速报", type: "地震速报",
parameData: { params: {
type: { type: {
name: "速报分类", name: "速报分类",
type: { type: {

View File

@@ -1,7 +1,7 @@
import type { RouterData, ListContext, Options } from "../types.js"; import type { RouterData, ListContext, Options } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (c: ListContext, noCache: boolean) => { export const handleRoute = async (c: ListContext, noCache: boolean) => {
const type = c.req.query("type") || "1"; const type = c.req.query("type") || "1";
@@ -10,7 +10,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => {
name: "genshin", name: "genshin",
title: "原神", title: "原神",
type: "最新动态", type: "最新动态",
parameData: { params: {
type: { type: {
name: "榜单分类", name: "榜单分类",
type: { type: {
@@ -44,9 +44,9 @@ const getList = async (options: Options, noCache: boolean) => {
title: data.subject, title: data.subject,
desc: data.content, desc: data.content,
cover: data.cover, cover: data.cover,
author: v.user.nickname, author: v.user?.nickname || null,
timestamp: getTime(data.created_at), timestamp: getTime(data.created_at),
hot: v.stat.view_num, hot: data.view_status,
url: `https://www.miyoushe.com/ys/article/${data.post_id}`, url: `https://www.miyoushe.com/ys/article/${data.post_id}`,
mobileUrl: `https://m.miyoushe.com/ys/#/article/${data.post_id}`, mobileUrl: `https://m.miyoushe.com/ys/#/article/${data.post_id}`,
}; };

View File

@@ -1,22 +1,22 @@
import type { RouterData, ListContext, Options } from "../types.js"; import type { RouterData, ListContext, Options } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (c: ListContext, noCache: boolean) => { export const handleRoute = async (c: ListContext, noCache: boolean) => {
const sort = c.req.query("sort") || "hot"; const sort = c.req.query("sort") || "featured";
const { fromCache, data, updateTime } = await getList({ sort }, noCache); const { fromCache, data, updateTime } = await getList({ sort }, noCache);
const routeData: RouterData = { const routeData: RouterData = {
name: "hellogithub", name: "hellogithub",
title: "HelloGitHub", title: "HelloGitHub",
type: "热门仓库", type: "热门仓库",
description: "分享 GitHub 上有趣、入门级的开源项目", description: "分享 GitHub 上有趣、入门级的开源项目",
parameData: { params: {
sort: { sort: {
name: "排行榜分区", name: "排行榜分区",
type: { type: {
hot: "热门", featured: "精选",
last: "最新", all: "全部",
}, },
}, },
}, },

56
src/routes/history.ts Normal file
View File

@@ -0,0 +1,56 @@
import type { RouterData, ListContext, Options } from "../types.js";
import type { RouterType } from "../router.types.js";
import { load } from "cheerio";
import { get } from "../utils/getData.js";
import { getCurrentDateTime } from "../utils/getTime.js";
export const handleRoute = async (c: ListContext, noCache: boolean) => {
// 获取日期
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}`,
params: {
month: "月份",
day: "日期",
},
link: "https://baike.baidu.com/calendar",
total: data?.length || 0,
updateTime,
fromCache,
data,
};
return routeData;
};
const getList = async (options: Options, noCache: boolean) => {
const { month, day } = options;
const monthStr = month.toString().padStart(2, "0");
const dayStr = day.toString().padStart(2, "0");
const url = `https://baike.baidu.com/cms/home/eventsOnHistory/${monthStr}.json`;
const result = await get({
url,
noCache,
params: {
_: new Date().getTime(),
},
});
const list = result.data[monthStr][monthStr + dayStr];
return {
fromCache: result.fromCache,
updateTime: result.updateTime,
data: list.map((v: RouterType["history"], index: number) => ({
id: index,
title: load(v.title).text().trim(),
cover: v.cover ? v.pic_share : null || null,
desc: load(v.desc).text().trim(),
year: v.year,
timestamp: null,
hot: null,
url: v.link,
mobileUrl: v.link,
})),
};
};

View File

@@ -1,7 +1,7 @@
import type { RouterData, ListContext, Options } from "../types.js"; import type { RouterData, ListContext, Options } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (c: ListContext, noCache: boolean) => { export const handleRoute = async (c: ListContext, noCache: boolean) => {
const type = c.req.query("type") || "1"; const type = c.req.query("type") || "1";
@@ -10,7 +10,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => {
name: "honkai", name: "honkai",
title: "崩坏3", title: "崩坏3",
type: "最新动态", type: "最新动态",
parameData: { params: {
type: { type: {
name: "榜单分类", name: "榜单分类",
type: { type: {
@@ -43,10 +43,10 @@ const getList = async (options: Options, noCache: boolean) => {
id: data.post_id, id: data.post_id,
title: data.subject, title: data.subject,
desc: data.content, desc: data.content,
cover: data.cover || v.image_list[0].url, cover: data.cover,
author: v.user.nickname, author: v.user?.nickname || null,
timestamp: getTime(data.created_at), timestamp: getTime(data.created_at),
hot: v.stat.view_num, hot: data.view_status,
url: `https://www.miyoushe.com/bh3/article/${data.post_id}`, url: `https://www.miyoushe.com/bh3/article/${data.post_id}`,
mobileUrl: `https://m.miyoushe.com/bh3/#/article/${data.post_id}`, mobileUrl: `https://m.miyoushe.com/bh3/#/article/${data.post_id}`,
}; };

View File

@@ -2,7 +2,7 @@ import type { RouterData, ListContext, Options } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { web } from "../utils/getData.js"; import { web } from "../utils/getData.js";
import { extractRss, parseRSS } from "../utils/parseRSS.js"; import { extractRss, parseRSS } from "../utils/parseRSS.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (c: ListContext, noCache: boolean) => { export const handleRoute = async (c: ListContext, noCache: boolean) => {
const type = c.req.query("type") || "hot"; const type = c.req.query("type") || "hot";
@@ -11,7 +11,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => {
name: "hostloc", name: "hostloc",
title: "全球主机交流", title: "全球主机交流",
type: "榜单", type: "榜单",
parameData: { params: {
type: { type: {
name: "榜单分类", name: "榜单分类",
type: { type: {

51
src/routes/hupu.ts Normal file
View File

@@ -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,
})),
};
};

View File

@@ -1,7 +1,7 @@
import type { RouterData } from "../types.js"; import type { RouterData } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (_: undefined, noCache: boolean) => { export const handleRoute = async (_: undefined, noCache: boolean) => {
const { fromCache, data, updateTime } = await getList(noCache); const { fromCache, data, updateTime } = await getList(noCache);

View File

@@ -1,7 +1,7 @@
import type { RouterData } from "../types.js"; import type { RouterData } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (_: undefined, noCache: boolean) => { export const handleRoute = async (_: undefined, noCache: boolean) => {
const { fromCache, data, updateTime } = await getList(noCache); const { fromCache, data, updateTime } = await getList(noCache);

View File

@@ -1,7 +1,7 @@
import type { RouterData } from "../types.js"; import type { RouterData } from "../types.js";
import { load } from "cheerio"; import { load } from "cheerio";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (_: undefined, noCache: boolean) => { export const handleRoute = async (_: undefined, noCache: boolean) => {
const { fromCache, data, updateTime } = await getList(noCache); const { fromCache, data, updateTime } = await getList(noCache);

View File

@@ -1,7 +1,7 @@
import type { RouterData } from "../types.js"; import type { RouterData } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (_: undefined, noCache: boolean) => { export const handleRoute = async (_: undefined, noCache: boolean) => {
const { fromCache, data, updateTime } = await getList(noCache); const { fromCache, data, updateTime } = await getList(noCache);

View File

@@ -1,7 +1,7 @@
import type { RouterData } from "../types.js"; import type { RouterData } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (_: undefined, noCache: boolean) => { export const handleRoute = async (_: undefined, noCache: boolean) => {
const { fromCache, data, updateTime } = await getList(noCache); const { fromCache, data, updateTime } = await getList(noCache);

View File

@@ -1,7 +1,7 @@
import type { RouterData } from "../types.js"; import type { RouterData } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { post } from "../utils/getData.js"; import { post } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (_: undefined, noCache: boolean) => { export const handleRoute = async (_: undefined, noCache: boolean) => {
const { fromCache, data, updateTime } = await getList(noCache); const { fromCache, data, updateTime } = await getList(noCache);

50
src/routes/nodeseek.ts Normal file
View File

@@ -0,0 +1,50 @@
import type { RouterData } from "../types.js";
import { get } from "../utils/getData.js";
import { parseStringPromise } from "xml2js";
export const handleRoute = async (_: undefined, noCache: boolean) => {
const { fromCache, data, updateTime } = await getList(noCache);
const routeData: RouterData = {
name: "nodeseek",
title: "NodeSeek",
type: "最新",
params: {
type: {
name: "分类",
type: {
all: "所有",
},
},
},
link: "https://www.nodeseek.com/",
total: data?.length || 0,
updateTime,
fromCache,
data,
};
return routeData;
};
const getList = async (noCache: boolean) => {
const url = `https://rss.nodeseek.com/`;
const result = await get({ url, noCache });
const rssData = await parseStringPromise(result.data);
const list = rssData.rss.channel[0].item;
return {
fromCache: result.fromCache,
updateTime: result.updateTime,
data: list.map((v: any) => ({
id: v.guid[0]._,
title: v.title[0],
desc: v.description ? v.description[0] : "",
author: v["dc:creator"] ? v["dc:creator"][0] : "unknown",
timestamp: new Date(v.pubDate[0]).getTime(),
hot: null, // NodeSeek RSS 中没有类似于hot的字段
url: v.link[0],
mobileUrl: v.link[0],
})),
};
};

View File

@@ -1,7 +1,7 @@
import type { RouterData } from "../types.js"; import type { RouterData } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (_: undefined, noCache: boolean) => { export const handleRoute = async (_: undefined, noCache: boolean) => {
const { fromCache, data, updateTime } = await getList(noCache); const { fromCache, data, updateTime } = await getList(noCache);

137
src/routes/sina-news.ts Normal file
View File

@@ -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,
})),
};
};

62
src/routes/sina.ts Normal file
View File

@@ -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,
};
}),
};
};

View File

@@ -1,7 +1,7 @@
import type { RouterData, ListContext, Options } from "../types.js"; import type { RouterData, ListContext, Options } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (c: ListContext, noCache: boolean) => { export const handleRoute = async (c: ListContext, noCache: boolean) => {
const type = c.req.query("type") || "热门文章"; const type = c.req.query("type") || "热门文章";
@@ -10,7 +10,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => {
name: "sspai", name: "sspai",
title: "少数派", title: "少数派",
type: "热榜", type: "热榜",
parameData: { params: {
type: { type: {
name: "分类", name: "分类",
type: ["热门文章", "应用推荐", "生活方式", "效率技巧", "少数派播客"], type: ["热门文章", "应用推荐", "生活方式", "效率技巧", "少数派播客"],

View File

@@ -1,7 +1,7 @@
import type { RouterData, ListContext, Options } from "../types.js"; import type { RouterData, ListContext, Options } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (c: ListContext, noCache: boolean) => { export const handleRoute = async (c: ListContext, noCache: boolean) => {
const type = c.req.query("type") || "1"; const type = c.req.query("type") || "1";
@@ -10,7 +10,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => {
name: "starrail", name: "starrail",
title: "崩坏:星穹铁道", title: "崩坏:星穹铁道",
type: "最新动态", type: "最新动态",
parameData: { params: {
type: { type: {
name: "榜单分类", name: "榜单分类",
type: { type: {
@@ -44,9 +44,9 @@ const getList = async (options: Options, noCache: boolean) => {
title: data.subject, title: data.subject,
desc: data.content, desc: data.content,
cover: data.cover, cover: data.cover,
author: v.user.nickname, author: v.user?.nickname || null,
timestamp: getTime(data.created_at), timestamp: getTime(data.created_at),
hot: v.stat.view_num, hot: data.view_status,
url: `https://www.miyoushe.com/sr/article/${data.post_id}`, url: `https://www.miyoushe.com/sr/article/${data.post_id}`,
mobileUrl: `https://m.miyoushe.com/sr/#/article/${data.post_id}`, mobileUrl: `https://m.miyoushe.com/sr/#/article/${data.post_id}`,
}; };

View File

@@ -1,7 +1,7 @@
import type { RouterData } from "../types.js"; import type { RouterData } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (_: undefined, noCache: boolean) => { export const handleRoute = async (_: undefined, noCache: boolean) => {
const { fromCache, data, updateTime } = await getList(noCache); const { fromCache, data, updateTime } = await getList(noCache);

View File

@@ -1,7 +1,7 @@
import type { RouterData } from "../types.js"; import type { RouterData } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (_: undefined, noCache: boolean) => { export const handleRoute = async (_: undefined, noCache: boolean) => {
const { fromCache, data, updateTime } = await getList(noCache); const { fromCache, data, updateTime } = await getList(noCache);

View File

@@ -1,7 +1,7 @@
import type { RouterData } from "../types.js"; import type { RouterData } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (_: undefined, noCache: boolean) => { export const handleRoute = async (_: undefined, noCache: boolean) => {
const { fromCache, data, updateTime } = await getList(noCache); const { fromCache, data, updateTime } = await getList(noCache);

View File

@@ -9,7 +9,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => {
name: "v2ex", name: "v2ex",
title: "V2EX", title: "V2EX",
type: "主题榜", type: "主题榜",
parameData: { params: {
type: { type: {
name: "榜单分类", name: "榜单分类",
type: { type: {

View File

@@ -1,7 +1,7 @@
import type { RouterData, ListContext, Options } from "../types.js"; import type { RouterData, ListContext, Options } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (c: ListContext, noCache: boolean) => { export const handleRoute = async (c: ListContext, noCache: boolean) => {
const province = c.req.query("province") || ""; const province = c.req.query("province") || "";
@@ -10,7 +10,7 @@ export const handleRoute = async (c: ListContext, noCache: boolean) => {
name: "weatheralarm", name: "weatheralarm",
title: "中央气象台", title: "中央气象台",
type: type || "全国气象预警", type: type || "全国气象预警",
parameData: { params: {
province: { province: {
name: "预警区域", name: "预警区域",
value: "省份名称( 例如:广东省 ", value: "省份名称( 例如:广东省 ",

View File

@@ -1,7 +1,7 @@
import type { RouterData } from "../types.js"; import type { RouterData } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (_: undefined, noCache: boolean) => { export const handleRoute = async (_: undefined, noCache: boolean) => {
const { fromCache, data, updateTime } = await getList(noCache); const { fromCache, data, updateTime } = await getList(noCache);
@@ -32,9 +32,9 @@ const getList = async (noCache: boolean) => {
id: v.mid, id: v.mid,
title: v.word, title: v.word,
desc: v.note || key, desc: v.note || key,
author: v.category, author: v.flag_desc,
timestamp: getTime(v.onboard_time), timestamp: getTime(v.onboard_time),
hot: v.raw_hot, hot: v.num,
url: `https://s.weibo.com/weibo?q=${encodeURIComponent(key)}&t=31&band_rank=1&Refer=top`, url: `https://s.weibo.com/weibo?q=${encodeURIComponent(key)}&t=31&band_rank=1&Refer=top`,
mobileUrl: `https://s.weibo.com/weibo?q=${encodeURIComponent( mobileUrl: `https://s.weibo.com/weibo?q=${encodeURIComponent(
key, key,

View File

@@ -2,7 +2,7 @@ import type { RouterData } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getWereadID from "../utils/getToken/weread.js"; import getWereadID from "../utils/getToken/weread.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (_: undefined, noCache: boolean) => { export const handleRoute = async (_: undefined, noCache: boolean) => {
const { fromCache, data, updateTime } = await getList(noCache); const { fromCache, data, updateTime } = await getList(noCache);

View File

@@ -1,7 +1,7 @@
import type { RouterData } from "../types.js"; import type { RouterData } from "../types.js";
import type { RouterType } from "../router.types.js"; import type { RouterType } from "../router.types.js";
import { get } from "../utils/getData.js"; import { get } from "../utils/getData.js";
import getTime from "../utils/getTime.js"; import { getTime } from "../utils/getTime.js";
export const handleRoute = async (_: undefined, noCache: boolean) => { export const handleRoute = async (_: undefined, noCache: boolean) => {
const { fromCache, data, updateTime } = await getList(noCache); const { fromCache, data, updateTime } = await getList(noCache);
@@ -33,7 +33,7 @@ const getList = async (noCache: boolean) => {
desc: data.excerpt, desc: data.excerpt,
cover: v.children[0].thumbnail, cover: v.children[0].thumbnail,
timestamp: getTime(data.created), timestamp: getTime(data.created),
hot: parseInt(v.detail_text.replace(/[^\d]/g, "")) * 10000, hot: parseFloat(v.detail_text.split(" ")[0]) * 10000,
url: `https://www.zhihu.com/question/${data.id}`, url: `https://www.zhihu.com/question/${data.id}`,
mobileUrl: `https://www.zhihu.com/question/${data.id}`, mobileUrl: `https://www.zhihu.com/question/${data.id}`,
}; };

7
src/types.d.ts vendored
View File

@@ -22,7 +22,7 @@ export type RouterData = {
title: string; title: string;
type: string; type: string;
description?: string; description?: string;
parameData?: Record<string, string | object>; params?: Record<string, string | object>;
total: number; total: number;
link?: string; link?: string;
updateTime: string; updateTime: string;
@@ -61,8 +61,5 @@ export type Web = {
// 参数类型 // 参数类型
export type Options = { export type Options = {
[key: string]: string | undefined; [key: string]: string | number | undefined;
}; };
// serveHotApi
export default function serveHotApi(port?: number): unknown;

View File

@@ -1,7 +1,7 @@
import type { Get, Post, Web } from "../types.ts"; import type { Get, Post, Web } from "../types.ts";
import { config } from "../config.js"; import { config } from "../config.js";
import { getCache, setCache, delCache } from "./cache.js"; import { getCache, setCache, delCache } from "./cache.js";
import { Cluster } from "puppeteer-cluster"; // import { Cluster } from "puppeteer-cluster";
import logger from "./logger.js"; import logger from "./logger.js";
import axios from "axios"; import axios from "axios";
@@ -13,25 +13,26 @@ const request = axios.create({
}); });
// puppeteer-cluster // puppeteer-cluster
export const createCluster = async () => { // export const createCluster = async () => {
return await Cluster.launch({ // return await Cluster.launch({
concurrency: Cluster.CONCURRENCY_BROWSER, // concurrency: Cluster.CONCURRENCY_BROWSER,
maxConcurrency: 5, // maxConcurrency: 5,
}); // });
}; // };
// Cluster // Cluster
const cluster = await createCluster(); // const cluster = await createCluster();
const cluster = null;
// Cluster configuration // Cluster configuration
cluster.task(async ({ page, data: { url, userAgent } }) => { // cluster.task(async ({ page, data: { url, userAgent } }) => {
if (userAgent) { // if (userAgent) {
await page.setUserAgent(userAgent); // await page.setUserAgent(userAgent);
} // }
await page.goto(url, { waitUntil: 'networkidle0' }); // await page.goto(url, { waitUntil: "networkidle0" });
const pageContent = await page.content(); // const pageContent = await page.content();
return pageContent; // return pageContent;
}); // });
// 请求拦截 // 请求拦截
request.interceptors.request.use( request.interceptors.request.use(

20
src/utils/getNum.ts Normal file
View File

@@ -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);
};

View File

@@ -1,21 +1,61 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
const getTime = (timeInput: string | number): number => { interface CurrentDateTime {
year: string;
month: string;
day: string;
hour: string;
minute: string;
second: string;
}
export const getTime = (timeInput: string | number): number | null => {
try { try {
let num: number | string; let num: number;
// 尝试将输入转换为数字
// 处理字符串的情况
if (typeof timeInput === "string") { if (typeof timeInput === "string") {
// 尝试将字符串直接转换为数字
num = Number(timeInput); num = Number(timeInput);
// 检查转换结果是否为有效数字
if (isNaN(num)) { if (isNaN(num)) {
// 处理为字符串的日期时间 // 将各种分隔符替换为标准格式
return dayjs(timeInput).valueOf(); let standardizedInput = timeInput
.replace(/(\d{4})-(\d{2})-(\d{2})-(\d{2})/, "$1-$2-$3 $4") // "YYYY-MM-DD-HH" -> "YYYY-MM-DD HH"
.replace(/(\d{4})-(\d{2})-(\d{2})[T\s](\d{2}):?(\d{2})?:?(\d{2})?/, "$1-$2-$3 $4:$5:$6") // "YYYY-MM-DDTHH:mm:ss" -> "YYYY-MM-DD HH:mm:ss"
.replace(/(\d{4})[-/](\d{2})[-/](\d{2})/, "$1-$2-$3"); // "YYYY/MM/DD" or "YYYY-MM-DD" -> "YYYY-MM-DD"
// 减少解析过程中可能的多余空格
standardizedInput = standardizedInput.replace(/\s+/, " ").trim();
// 处理标准化后的日期时间字符串
const formatPatterns = [
"YYYY-MM-DD HH:mm:ss",
"YYYY-MM-DD HH:mm",
"YYYY-MM-DD HH",
"YYYY-MM-DD",
];
let parsedDate: dayjs.Dayjs | null = null;
for (const pattern of formatPatterns) {
parsedDate = dayjs(standardizedInput, pattern, true);
if (parsedDate.isValid()) {
break;
}
}
if (parsedDate && parsedDate.isValid()) {
return parsedDate.valueOf();
} else {
return null;
}
} }
} else { } else {
num = timeInput; num = timeInput;
} }
// 是否为毫秒级时间戳 // 是否为毫秒级时间戳
if (num > 946684800000) { if (num > 946684800000) {
// 以2000年作为毫秒时间戳参考点
return num; return num;
} else { } else {
return num * 1000; return num * 1000;
@@ -25,4 +65,18 @@ const getTime = (timeInput: string | number): number => {
} }
}; };
export default getTime; export const getCurrentDateTime = (padZero: boolean = false): CurrentDateTime => {
const now = dayjs();
// 补零
const pad = (num: number): string => (num < 10 ? `0${num}` : `${num}`);
return {
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(),
};
};

View File

@@ -1,11 +1,13 @@
import { config } from "../config.js"; import { config } from "../config.js";
import { createLogger, format, transports } from "winston"; import { createLogger, format, transports } from "winston";
import path from "path"; import path from "path";
import chalk from "chalk";
let pathOption: (typeof transports.File)[] = []; let pathOption: (typeof transports.File)[] = [];
// 日志输出目录 // 日志输出目录
if (config.USE_LOG_FILE) { if (config.USE_LOG_FILE) {
try {
pathOption = [ pathOption = [
new transports.File({ new transports.File({
filename: path.resolve("logs/error.log"), filename: path.resolve("logs/error.log"),
@@ -19,7 +21,33 @@ if (config.USE_LOG_FILE) {
maxFiles: 1, maxFiles: 1,
}), }),
]; ];
} catch (error) {
console.error("Failed to initialize log files. Logging to a file will be skipped.", error);
pathOption = [];
} }
}
// 定义不同日志级别的彩色块
const levelColors: { [key: string]: string } = {
error: chalk.bgRed(" ERROR "),
warn: chalk.bgYellow(" WARN "),
info: chalk.bgBlue(" INFO "),
debug: chalk.bgGreen(" DEBUG "),
default: chalk.bgWhite(" LOG "),
};
// 自定义控制台日志输出格式
const consoleFormat = format.printf(({ level, message, timestamp, stack }) => {
// 获取原始日志级别
const originalLevel = Object.keys(levelColors).find((lvl) => level.includes(lvl)) || "default";
const colorLevel = levelColors[originalLevel] || levelColors.default;
let logMessage = `${colorLevel} [${timestamp}] ${message}`;
if (stack) {
logMessage += `\n${stack}`;
}
return logMessage;
});
// logger // logger
const logger = createLogger({ const logger = createLogger({
@@ -39,11 +67,15 @@ const logger = createLogger({
// 控制台输出 // 控制台输出
if (process.env.NODE_ENV !== "production") { if (process.env.NODE_ENV !== "production") {
try {
logger.add( logger.add(
new transports.Console({ new transports.Console({
format: format.combine(format.colorize(), format.simple()), format: format.combine(format.colorize(), consoleFormat),
}), }),
); );
} catch (error) {
console.error("Failed to add console transport. Console logging will be skipped.", error);
}
} }
export default logger; export default logger;

View File

@@ -3,11 +3,13 @@
"target": "ESNext", "target": "ESNext",
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Bundler", "moduleResolution": "Bundler",
"declaration": true,
"declarationMap": true,
"strict": false, "strict": false,
"types": ["node"], "types": ["node"],
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "hono/jsx", "jsxImportSource": "hono/jsx",
"outDir": "./dist" "outDir": "./dist"
}, },
"exclude": ["node_modules", "*.test.*"] "exclude": ["node_modules", "*.test.*", "./dist/**/*"]
} }