这个仓库 newsnow
是一个专注于提供实时和热门新闻优雅阅读体验的项目。以下是对该仓库的详细介绍:
项目概述
NewsNow 目前是一个仅支持中文的演示版本,后续会推出功能更丰富、支持更好定制化和英文内容的完整版本。其目标是为用户提供简洁优雅的界面,以实现对实时和热门新闻的高效阅读。
主要特性
- 界面设计:拥有简洁优雅的 UI 设计,优化阅读体验。
- 实时更新:能够实时更新热门新闻。
- 登录与同步:支持 GitHub OAuth 登录,并实现数据同步。
- 缓存机制:默认缓存时长为 30 分钟,登录用户可强制刷新。
- 自适应抓取:根据数据源更新频率,采用自适应抓取间隔(最小 2 分钟),优化资源使用并防止 IP 被封禁。
- MCP 服务器支持:支持 MCP 服务器,可通过修改配置文件中的
BASE_URL
为自定义域名。
目录结构
.dockerignore
.gitignore
CONTRIBUTING.md
Dockerfile
LICENSE
README.ja-JP.md
README.md
README.zh-CN.md
docker-compose.local.yml
docker-compose.yml
eslint.config.mjs
example.env.server
example.wrangler.toml
index.html
nitro.config.ts
package.json
pnpm-lock.yaml
pwa.config.ts
test/
common.test.ts
tsconfig.app.json
tsconfig.base.json
tsconfig.json
tsconfig.node.json
uno.config.ts
vite.config.ts
vitest.config.ts
.vscode/
...
patches/
...
shared/
...
.github/
...
server/
...
src/
...
screenshots/
...
scripts/
...
public/
...
tools/
...
部署方式
基础部署
无需登录和缓存功能时,可直接进行以下操作: 1. Fork 本仓库。 2. 导入至 Cloudflare Pages 或 Vercel 等平台。
Cloudflare Page 配置
- 构建命令:
pnpm run build
- 输出目录:
dist/output/public
GitHub OAuth 设置
- 创建 GitHub App
- 无需特殊权限
- 设置回调 URL 为:
https://your-domain.com/api/oauth/github
(将your-domain
替换为实际域名) - 获取 Client ID 和 Client Secret
环境变量配置
参考 example.env.server
文件,本地开发时将其重命名为 .env.server
并进行如下配置:
# Github Client ID
G_CLIENT_ID=
# Github Client Secret
G_CLIENT_SECRET=
# JWT Secret, usually the same as Client Secret
JWT_SECRET=
# Initialize database, must be set to true on first run, can be turned off afterward
INIT_TABLE=true
# Whether to enable cache
ENABLE_CACHE=true
数据库支持
支持的数据库连接器可参考:https://db0.unjs.io/connectors ,推荐使用 Cloudflare D1 数据库,具体操作步骤如下: 1. 在 Cloudflare Worker 控制台创建 D1 数据库。 2. 在 wrangler.toml 中配置 database_id 和 database_name。 3. 若 wrangler.toml 不存在,将 example.wrangler.toml 重命名并修改配置。 4. 下次部署时配置生效。
Docker 部署
在项目根目录下执行以下命令:
docker compose up
也可在 docker-compose.yml
中设置环境变量。
开发相关
数据来源添加
可参考 shared/sources
和 server/sources
目录,项目提供了完整的类型定义和清晰的架构。具体添加新数据源的详细说明可查看 CONTRIBUTING.md。
依赖版本
pnpm-lock.yaml
文件记录了项目所依赖的各个包及其版本信息,例如 archiver@7.0.1
、unstorage@1.16.0
等。
代码片段示例
仓库中包含多个代码文件,以下是部分示例:
- 数据源处理:server/sources
目录下有多个数据源处理文件,如 github.ts
、producthunt.ts
等,通过抓取网页信息并解析,将新闻数据整理成特定格式返回。
// server/sources/github.ts
import * as cheerio from "cheerio"
import type { NewsItem } from "@shared/types"
const trending = defineSource(async () => {
const baseURL = "https://github.com"
const html: any = await myFetch("https://github.com/trending?spoken_language_code=")
const $ = cheerio.load(html)
const $main = $("main .Box div[data-hpc] > article")
const news: NewsItem[] = []
$main.each((_, el) => {
const a = $(el).find(">h2 a")
const title = a.text().replace(/\n+/g, "").trim()
const url = 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 (url && title) {
news.push({
url: `${baseURL}${url}`,
title,
id: url,
extra: {
info: `✰ ${star}`,
hover: desc,
},
})
}
})
return news
})
export default defineSource({
"github": trending,
"github-trending-today": trending,
})
- OAuth 登录处理:
server/api/oauth/github.ts
文件处理 GitHub OAuth 登录流程,包括获取访问令牌、用户信息,生成 JWT 令牌并进行重定向。
// server/api/oauth/github.ts
import process from "node:process"
import { SignJWT } from "jose"
import { UserTable } from "#/database/user"
export default defineEventHandler(async (event) => {
const db = useDatabase()
const userTable = db ? new UserTable(db) : undefined
if (!userTable) throw new Error("db is not defined")
if (process.env.INIT_TABLE !== "false") await userTable.init()
const response: {
access_token: string
token_type: string
scope: string
} = await myFetch(
`https://github.com/login/oauth/access_token`,
{
method: "POST",
body: {
client_id: process.env.G_CLIENT_ID,
client_secret: process.env.G_CLIENT_SECRET,
code: getQuery(event).code,
},
headers: {
accept: "application/json",
},
},
)
const userInfo: {
id: number
name: string
avatar_url: string
email: string
notification_email: string
} = await myFetch(`https://api.github.com/user`, {
headers: {
"Accept": "application/vnd.github+json",
"Authorization": `token ${response.access_token}`,
// 必须有 user-agent,在 cloudflare worker 会报错
"User-Agent": "NewsNow App",
},
})
const userID = String(userInfo.id)
await userTable.addUser(userID, userInfo.notification_email || userInfo.email, "github")
const jwtToken = await new SignJWT({
id: userID,
type: "github",
})
.setExpirationTime("60d")
.setProtectedHeader({ alg: "HS256" })
.sign(new TextEncoder().encode(process.env.JWT_SECRET!))
// nitro 有 bug,在 cloudflare 里没法 set cookie
// seconds
// const maxAge = 60 * 24 * 60 * 60
// setCookie(event, "user_jwt", jwtToken, { maxAge })
// setCookie(event, "user_avatar", userInfo.avatar_url, { maxAge })
// setCookie(event, "user_name", userInfo.name, { maxAge })
const params = new URLSearchParams({
login: "github",
jwt: jwtToken,
user: JSON.stringify({
avatar: userInfo.avatar_url,
name: userInfo.name,
}),
})
return sendRedirect(event, `/?${params.toString()}`)
})
贡献代码
欢迎开发者贡献代码,可通过提交 pull request 或创建 issue 来提出功能请求和报告 bug。具体可参考 CONTRIBUTING.md
文件。