5 Commits

Author SHA1 Message Date
imsyy
14bc5a1dce feat: 支持 Vercel 部署 2024-06-07 16:03:22 +08:00
imsyy
11addd20ca 🐳 chore: 修复工作流 2024-06-07 09:07:09 +08:00
imsyy
23375519ba 🐳 chore: 修复工作流 2024-06-06 17:50:06 +08:00
imsyy
2ba46c5e0e 🐳 chore: 修复工作流 2024-06-06 17:31:20 +08:00
imsyy
d4a52a6b24 🐞 fix: 修复 Docker 运行出错 2024-06-06 16:27:01 +08:00
12 changed files with 105 additions and 39 deletions

View File

@@ -3,9 +3,10 @@ name: Publish Docker image
on: on:
release: release:
types: [published] types: [published]
workflow_dispatch:
jobs: jobs:
push_to_registry: build-docker:
name: Push Docker image to multiple registries name: Push Docker image to multiple registries
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
@@ -15,6 +16,12 @@ jobs:
- name: Check out the repo - name: Check out the repo
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
@@ -36,11 +43,14 @@ jobs:
imsyy/dailyhot-api imsyy/dailyhot-api
ghcr.io/${{ github.repository }} ghcr.io/${{ github.repository }}
- name: Build and push Docker image - name: Build and push
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
platforms: |
linux/amd64
linux/arm64
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}

View File

@@ -1,8 +1,17 @@
FROM node:20-alpine AS base FROM node:20-alpine AS base
ENV NODE_ENV=docker
# 安装 Puppeteer 所需的依赖库
RUN apk add libc6-compat
# RUN apk add chromium nss freetype harfbuzz ca-certificates
# 配置 Chromium
# ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
# ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
FROM base AS builder FROM base AS builder
RUN apk add --no-cache libc6-compat
RUN npm install -g pnpm RUN npm install -g pnpm
WORKDIR /app WORKDIR /app
@@ -10,9 +19,9 @@ COPY package*json tsconfig.json pnpm-lock.yaml .env ./
COPY src ./src COPY src ./src
COPY public ./public COPY public ./public
RUN pnpm install && \ RUN pnpm install
pnpm build && \ RUN pnpm build
pnpm prune --production RUN pnpm prune --production
FROM base AS runner FROM base AS runner
WORKDIR /app WORKDIR /app

View File

@@ -2,6 +2,12 @@
<img alt="logo" height="120" src="./public/favicon.png" width="120"/> <img alt="logo" height="120" src="./public/favicon.png" width="120"/>
<h2>今日热榜</h2> <h2>今日热榜</h2>
<p>一个聚合热门数据的 API 接口</p> <p>一个聚合热门数据的 API 接口</p>
<br />
<img src="https://img.shields.io/github/last-commit/imsyy/DailyHotApi" alt="last commit"/>
<img src="https://img.shields.io/github/languages/code-size/imsyy/DailyHotApi" alt="code size"/>
<img src="https://img.shields.io/docker/image-size/imsyy/dailyhot-api" alt="docker-image-size"/>
<img src="https://github.com/imsyy/DailyHotApi/actions/workflows/docker.yml/badge.svg" alt="Publish Docker image"/>
<img src="https://github.com/imsyy/DailyHotApi/actions/workflows/npm.yml/badge.svg" alt="Publish npm package"/>
</div> </div>
## 🚩 特性 ## 🚩 特性
@@ -46,8 +52,8 @@
| 稀土掘金 | 热榜 | juejin | 🟢 | | 稀土掘金 | 热榜 | juejin | 🟢 |
| 腾讯新闻 | 热点榜 | qq-news | 🟢 | | 腾讯新闻 | 热点榜 | qq-news | 🟢 |
| 网易新闻 | 热点榜 | netease-news | 🟢 | | 网易新闻 | 热点榜 | netease-news | 🟢 |
| 吾爱破解 | 榜单 | 52pojie | 🟢 | | 吾爱破解 | 榜单 | 52pojie | |
| 全球主机交流 | 榜单 | hostloc | 🟢 | | 全球主机交流 | 榜单 | hostloc | |
| 虎嗅 | 24小时 | huxiu | 🟢 | | 虎嗅 | 24小时 | huxiu | 🟢 |
| 爱范儿 | 快讯 | ifanr | 🟢 | | 爱范儿 | 快讯 | ifanr | 🟢 |
| 英雄联盟 | 更新公告 | lol | 🟢 | | 英雄联盟 | 更新公告 | lol | 🟢 |
@@ -142,6 +148,12 @@ npm run start
成功启动后程序会在控制台输出可访问的地址 成功启动后程序会在控制台输出可访问的地址
### Vercel 部署
本项目支持通过 `Vercel` 进行一键部署,点击下方按钮或前往 [项目仓库](https://github.com/imsyy/DailyHotApi-Vercel) 进行手动部署
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/imsyys-projects/clone?repository-url=https%3A%2F%2Fgithub.com%2Fimsyy%2FDailyHotApi-Vercel)
### Railway 部署 ### Railway 部署
本项目支持使用 [Railway](https://railway.app/) 一键部署,请先将本项目 fork 到您的仓库中,即可使用一键部署。 本项目支持使用 [Railway](https://railway.app/) 一键部署,请先将本项目 fork 到您的仓库中,即可使用一键部署。
@@ -150,12 +162,6 @@ npm run start
本项目支持使用 [Zeabur](https://zeabur.com/) 一键部署,请先将本项目 fork 到您的仓库中,即可使用一键部署。 本项目支持使用 [Zeabur](https://zeabur.com/) 一键部署,请先将本项目 fork 到您的仓库中,即可使用一键部署。
### Vercel 部署
> 🚧 Vercel 部署支持正在修复中
若您目前仅能通过 `Vercel` 进行部署,那么请暂时不要使用最新版本
## ⚠️ 须知 ## ⚠️ 须知
- 本项目为了避免频繁请求官方数据,默认对数据做了缓存处理,默认为 `60` 分钟,如需更改,请自行修改配置 - 本项目为了避免频繁请求官方数据,默认对数据做了缓存处理,默认为 `60` 分钟,如需更改,请自行修改配置

View File

@@ -1,6 +1,6 @@
{ {
"name": "dailyhot-api", "name": "dailyhot-api",
"version": "2.0.0-rc.4", "version": "2.0.0",
"description": "An Api on Today's Hot list", "description": "An Api on Today's Hot list",
"keywords": [ "keywords": [
"API", "API",
@@ -17,7 +17,7 @@
"license": "MIT", "license": "MIT",
"author": "imsyy", "author": "imsyy",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/types.js", "types": "src/types.d.ts",
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,
"sourceMap": true, "sourceMap": true,
@@ -31,8 +31,8 @@
"scripts": { "scripts": {
"format": "prettier --write .", "format": "prettier --write .",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix", "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix",
"dev": "tsx watch --no-cache src/index.ts", "dev": "cross-env NODE_ENV=development tsx watch --no-cache src/index.ts",
"dev:cache": "tsx watch src/index.ts", "dev:cache": "cross-env NODE_ENV=development tsx watch src/index.ts",
"build": "tsc --project tsconfig.json", "build": "tsc --project tsconfig.json",
"start": "tsx src/index.ts" "start": "tsx src/index.ts"
}, },
@@ -56,6 +56,7 @@
"@types/node": "^20.14.1", "@types/node": "^20.14.1",
"@typescript-eslint/eslint-plugin": "^7.12.0", "@typescript-eslint/eslint-plugin": "^7.12.0",
"@typescript-eslint/parser": "^7.12.0", "@typescript-eslint/parser": "^7.12.0",
"cross-env": "^7.0.3",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"prettier": "^3.3.0", "prettier": "^3.3.0",
"tsx": "^3.14.0", "tsx": "^3.14.0",

12
pnpm-lock.yaml generated
View File

@@ -57,6 +57,9 @@ importers:
'@typescript-eslint/parser': '@typescript-eslint/parser':
specifier: ^7.12.0 specifier: ^7.12.0
version: 7.12.0(eslint@8.57.0)(typescript@5.4.5) version: 7.12.0(eslint@8.57.0)(typescript@5.4.5)
cross-env:
specifier: ^7.0.3
version: 7.0.3
eslint: eslint:
specifier: ^8.57.0 specifier: ^8.57.0
version: 8.57.0 version: 8.57.0
@@ -518,6 +521,11 @@ packages:
typescript: typescript:
optional: true optional: true
cross-env@7.0.3:
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
hasBin: true
cross-spawn@7.0.3: cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@@ -1808,6 +1816,10 @@ snapshots:
optionalDependencies: optionalDependencies:
typescript: 5.4.5 typescript: 5.4.5
cross-env@7.0.3:
dependencies:
cross-spawn: 7.0.3
cross-spawn@7.0.3: cross-spawn@7.0.3:
dependencies: dependencies:
path-key: 3.1.1 path-key: 3.1.1

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -10,14 +10,16 @@ const serveHotApi = (port: number = config.PORT) => {
fetch: app.fetch, fetch: app.fetch,
port, port,
}); });
logger.info(`🔥 DailyHot API 成功在端口 ${config.PORT} 上运行`); logger.info(`🔥 DailyHot API 成功在端口 ${port} 上运行`);
logger.info(`🔗 Local: 👉 http://localhost:${config.PORT}`); logger.info(`🔗 Local: 👉 http://localhost:${port}`);
return apiServer; return apiServer;
} catch (error) { } catch (error) {
logger.error(error); logger.error(error);
} }
}; };
serveHotApi(); if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "docker") {
serveHotApi(config.PORT);
}
export default serveHotApi; export default serveHotApi;

View File

@@ -14,6 +14,9 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
let allRoutePath: Array<string> = []; let allRoutePath: Array<string> = [];
const routersDirName: string = "routes"; const routersDirName: string = "routes";
// 排除路由
const excludeRoutes: Array<string> = ["52pojie", "hostloc"];
// 建立完整目录路径 // 建立完整目录路径
const routersDirPath = path.join(__dirname, routersDirName); const routersDirPath = path.join(__dirname, routersDirName);
@@ -47,6 +50,10 @@ if (fs.existsSync(routersDirPath) && fs.statSync(routersDirPath).isDirectory())
// 注册全部路由 // 注册全部路由
for (let index = 0; index < allRoutePath.length; index++) { for (let index = 0; index < allRoutePath.length; index++) {
const router = allRoutePath[index]; const router = allRoutePath[index];
// 是否处于排除名单
if (excludeRoutes.includes(router)) {
continue;
}
const listApp = app.basePath(`/${router}`); const listApp = app.basePath(`/${router}`);
// 返回榜单 // 返回榜单
listApp.get("/", async (c) => { listApp.get("/", async (c) => {
@@ -85,6 +92,16 @@ for (let index = 0; index < allRoutePath.length; index++) {
...listData, ...listData,
}); });
}); });
// 请求方式错误
listApp.all("*", (c) =>
c.json(
{
code: 405,
message: "Method Not Allowed",
},
405,
),
);
} }
// 获取全部路由 // 获取全部路由
@@ -93,10 +110,20 @@ app.get("/all", (c) =>
{ {
code: 200, code: 200,
count: allRoutePath.length, count: allRoutePath.length,
routes: allRoutePath.map((path) => ({ routes: allRoutePath.map((path) => {
name: path, // 是否处于排除名单
path: `/${path}`, if (excludeRoutes.includes(path)) {
})), return {
name: path,
path: null,
message: "该接口暂时下线",
};
}
return {
name: path,
path: `/${path}`,
};
}),
}, },
200, 200,
), ),

View File

@@ -31,7 +31,7 @@ const getList = async (options: Options, noCache: boolean) => {
const { type } = options; const { type } = options;
const url = `https://www.v2ex.com/api/topics/${type}.json`; const url = `https://www.v2ex.com/api/topics/${type}.json`;
const result = await get({ url, noCache }); const result = await get({ url, noCache });
const list = result.data.data.list; const list = result.data;
return { return {
fromCache: result.fromCache, fromCache: result.fromCache,
updateTime: result.updateTime, updateTime: result.updateTime,

View File

@@ -30,8 +30,6 @@ const getList = async (options: Options, noCache: boolean) => {
const url = `http://www.nmc.cn/rest/findAlarm?pageNo=1&pageSize=20&signaltype=&signallevel=&province=${encodeURIComponent(province)}`; const url = `http://www.nmc.cn/rest/findAlarm?pageNo=1&pageSize=20&signaltype=&signallevel=&province=${encodeURIComponent(province)}`;
const result = await get({ url, noCache }); const result = await get({ url, noCache });
const list = result.data.data.page.list; const list = result.data.data.page.list;
console.log(list);
return { return {
fromCache: result.fromCache, fromCache: result.fromCache,
updateTime: result.updateTime, updateTime: result.updateTime,

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import crypto from "crypto"; import crypto from "crypto";
/** /**
@@ -13,7 +14,7 @@ const getWereadID = (bookId: string) => {
// 取哈希结果的前三个字符作为初始值 // 取哈希结果的前三个字符作为初始值
let strSub = str.substring(0, 3); let strSub = str.substring(0, 3);
// 判断书籍 ID 的类型并进行转换 // 判断书籍 ID 的类型并进行转换
let fa; let fa: (string | any[])[];
if (/^\d*$/.test(bookId)) { if (/^\d*$/.test(bookId)) {
// 如果书籍 ID 只包含数字,则将其拆分成长度为 9 的子字符串,并转换为十六进制表示 // 如果书籍 ID 只包含数字,则将其拆分成长度为 9 的子字符串,并转换为十六进制表示
const chunks = []; const chunks = [];

File diff suppressed because one or more lines are too long