newsnow-新闻聚合平台-开源项目


这个仓库 newsnow 是一个专注于提供实时和热门新闻优雅阅读体验的项目。以下是对该仓库的详细介绍:

项目概述

NewsNow 目前是一个仅支持中文的演示版本,后续会推出功能更丰富、支持更好定制化和英文内容的完整版本。其目标是为用户提供简洁优雅的界面,以实现对实时和热门新闻的高效阅读。

主要特性

  1. 界面设计:拥有简洁优雅的 UI 设计,优化阅读体验。
  2. 实时更新:能够实时更新热门新闻。
  3. 登录与同步:支持 GitHub OAuth 登录,并实现数据同步。
  4. 缓存机制:默认缓存时长为 30 分钟,登录用户可强制刷新。
  5. 自适应抓取:根据数据源更新频率,采用自适应抓取间隔(最小 2 分钟),优化资源使用并防止 IP 被封禁。
  6. 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 设置

  1. 创建 GitHub App
  2. 无需特殊权限
  3. 设置回调 URL 为:https://your-domain.com/api/oauth/github(将 your-domain 替换为实际域名)
  4. 获取 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/sourcesserver/sources 目录,项目提供了完整的类型定义和清晰的架构。具体添加新数据源的详细说明可查看 CONTRIBUTING.md

依赖版本

pnpm-lock.yaml 文件记录了项目所依赖的各个包及其版本信息,例如 archiver@7.0.1unstorage@1.16.0 等。

代码片段示例

仓库中包含多个代码文件,以下是部分示例: - 数据源处理server/sources 目录下有多个数据源处理文件,如 github.tsproducthunt.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 文件。

github