Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div align="center">
<a href="https://doc.redviewer.nyc.mn" target="_blank">
<img src="https://img.comicguispider.nyc.mn/file/rv/1769934512064_logo.png" alt="logo" height="156">
<a href="https://rv.101114105.xyz" target="_blank">
<img src="https://img-cgs.101114105.xyz/file/rv/1769934512064_logo.png" alt="logo" height="156">
</a>
<h1 id="logo">redViewer(rV)</h1>
<img src="https://img.shields.io/badge/Platform-Win%20|%20macOS%20|%20linux-blue?color=red" alt="tag">
Expand All @@ -17,15 +17,17 @@

轻简风格的漫画阅读器。

> [26/02/18] nyc.mn 旧域名已废弃,新域名后缀 xyz

<p>
<a href="https://doc.redviewer.nyc.mn/">
<img src="https://img.comicguispider.nyc.mn/file/rv/1769934428726_btn-home.svg" alt="主页" height="48">
<a href="https://rv.101114105.xyz/">
<img src="https://img-cgs.101114105.xyz/file/rv/1769934428726_btn-home.svg" alt="主页" height="48">
</a>
<a href="https://demo.redviewer.nyc.mn/">
<img src="https://img.comicguispider.nyc.mn/file/rv/1769934434211_btn-demo.svg" alt="体验" height="48">
<a href="https://demo-rv.101114105.xyz/">
<img src="https://img-cgs.101114105.xyz/file/rv/1769934434211_btn-demo.svg" alt="体验" height="48">
</a>
<a href="https://github.com/jasoneri/redViewer/releases">
<img src="https://img.comicguispider.nyc.mn/file/rv/1769934610180_btn-dl.svg" alt="下载" height="48">
<img src="https://img-cgs.101114105.xyz/file/rv/1769934610180_btn-dl.svg" alt="下载" height="48">
</a>
</p>

Expand Down
3 changes: 2 additions & 1 deletion backend/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from core import lib_mgr
from storage import StorageBackendFactory
from api.routes.comic import index_router
from api.routes.root import root_router
from api.routes.root import root_router, api_config_router
from utils.cbz_cache import close_cbz_cache

staticFiles = None
Expand Down Expand Up @@ -60,6 +60,7 @@ def register_router(app: FastAPI) -> None:
if kemono_path and kemono_path.exists():
from api.routes.kemono import index_router as kemono_index_router
app.include_router(kemono_index_router, prefix="", tags=['kemono'])
app.include_router(api_config_router, prefix="", tags=['api'])
app.include_router(root_router, prefix="", tags=['root'])


Expand Down
51 changes: 50 additions & 1 deletion backend/api/routes/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
"""Root Router - 认证和配置管理 API"""

import time
import httpx
from functools import wraps
from urllib.parse import urlsplit
from fastapi import APIRouter, HTTPException, Header
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from typing import Optional

from infra import backend
from core.crypto import decrypt

root_router = APIRouter(prefix='/root')
api_config_router = APIRouter(prefix='/api')


# ===== 鉴权相关 =====
Expand Down Expand Up @@ -69,6 +73,12 @@ class WhitelistUpdate(BaseModel):
whitelist: list[str]


class ApiConfigUpdate(BaseModel):
backendUrl: str | None = None
currentBackend: str | None = None
secret: str | None = None
Comment on lines +76 to +79
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The currentBackend field in ApiConfigUpdate is defined but never used in the endpoint logic.

Since only backendUrl and secret are read in update_api_config, consider removing currentBackend to keep the model minimal. If you plan to use it later, either wire it into the current logic now or introduce a dedicated DTO when that logic is added.

Suggested implementation:

class ApiConfigUpdate(BaseModel):
    backendUrl: str | None = None
    secret: str | None = None
  1. Search the codebase for any remaining references to currentBackend (e.g. in request bodies, tests, or frontend code) and remove or adjust them accordingly.
  2. If this API is consumed externally, consider whether this is a breaking change; if so, update API documentation and any client DTOs to match.



@root_router.get("/")
async def root_status():
return {"status": "ok", "has_secret": is_auth_required()}
Expand Down Expand Up @@ -130,4 +140,43 @@ async def update_whitelist(req: WhitelistUpdate, x_secret: Optional[str] = Heade
if is_auth_required() and not verify_secret(x_secret or ''):
raise HTTPException(401, "鉴权失败")
backend.config.set('root_whitelist', req.whitelist)
return {"success": True}
return {"success": True}


@api_config_router.get('/config')
async def get_api_config():
return {
'backendUrl': backend.config.get('frontend_backend_url'),
'bgGif': None
}


@api_config_router.post('/config')
async def update_api_config(req: ApiConfigUpdate):
if not req.secret:
return JSONResponse({'error': '需要密钥'}, status_code=401)
if not is_auth_required():
return JSONResponse({'error': '请先设置密钥'}, status_code=403)
if not verify_secret(req.secret):
return JSONResponse({'error': '密钥验证失败'}, status_code=401)

target = (req.backendUrl or '').strip().rstrip('/')
Comment on lines +154 to +163
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Authentication failures against the target backend are treated as connectivity failures, which can mislead users.

The connectivity check currently treats any r.status_code >= 400 as a network error and returns "无法连接目标后端". For backends that require their own credentials, a 401/403 still proves the URL is reachable, but the message implies a connectivity problem instead of an auth issue. Consider either treating any HTTP response as a successful connectivity check and only handling transport-level failures, or special-casing 401/403 (and possibly other 4xx) with a more accurate error message to avoid confusing users for protected backends.

if not target:
return JSONResponse({'error': '需要目标后端地址'}, status_code=400)
try:
parts = urlsplit(target)
except ValueError:
return JSONResponse({'error': '地址格式不正确'}, status_code=400)
if parts.scheme not in ('http', 'https') or not parts.netloc:
return JSONResponse({'error': '地址必须以 http:// 或 https:// 开头'}, status_code=400)

try:
async with httpx.AsyncClient(timeout=5.0) as client:
r = await client.get(f'{target}/root/')
if r.status_code >= 400:
return JSONResponse({'error': '无法连接目标后端'}, status_code=502)
except httpx.HTTPError:
return JSONResponse({'error': '无法连接目标后端'}, status_code=502)

backend.config.set('frontend_backend_url', target)
return {'success': True}
2 changes: 1 addition & 1 deletion deploy/tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ toml = "0.8"
parking_lot = "0.12"

# Tauri dependencies (src-tauri only)
tauri = { version = "2.9.5", features = ["tray-icon"] }
tauri = { version = "2.9.5", features = ["tray-icon", "macos-private-api"] }
tauri-plugin-opener = "2"
tauri-plugin-single-instance = "2"
notify-rust = "4"
Expand Down
9 changes: 3 additions & 6 deletions deploy/tauri/lib/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,9 @@ pub fn resolve_uv() -> anyhow::Result<PathBuf> {

#[cfg(target_os = "macos")]
{
// On macOS, uv is bundled in Contents/Resources alongside other resources
let uv_path = exe_dir
.parent() // Contents
.map(|p| p.join("Resources").join("uv"))
.ok_or_else(|| anyhow::anyhow!("cannot resolve macOS resources path"))?;
return Ok(uv_path);
// externalBin 打包后位于 Contents/MacOS/ 目录(与主程序同目录)
// 见: https://v2.tauri.app/distribute/macos-application-bundle
Ok(exe_dir.join("uv"))
}

#[cfg(target_os = "linux")]
Expand Down
2 changes: 1 addition & 1 deletion deploy/tauri/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ tauri-build = { workspace = true }

[dependencies]
rv_lib = { path = "../lib", package = "lib" }
tauri = { workspace = true }
tauri = { workspace = true, features = ["tray-icon", "macos-private-api"] }
tauri-plugin-opener = { workspace = true }
tauri-plugin-single-instance = { workspace = true }
notify-rust = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions deploy/tauri/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"frontendDist": "../dist"
},
"app": {
"macOSPrivateApi": true,
"windows": [],
"security": {
"csp": null
Expand Down
7 changes: 7 additions & 0 deletions deploy/tauri/src-tauri/windows/hooks.nsh
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ FunctionEnd
; Handles config saving and dependency installation
;------------------------------------------------------------------------------
!macro NSIS_HOOK_POSTINSTALL
; Add firewall rules for ports 8080 and 12345
DetailPrint "Adding firewall rules..."
nsExec::ExecToLog 'netsh advfirewall firewall add rule name="rV" dir=in action=allow protocol=TCP localport=8080,12345'

; Create config directory
CreateDirectory "${CONFIG_DIR}"

Expand Down Expand Up @@ -265,6 +269,9 @@ FunctionEnd
; POSTUNINSTALL Hook
;------------------------------------------------------------------------------
!macro NSIS_HOOK_POSTUNINSTALL
; Remove firewall rule
nsExec::ExecToLog 'netsh advfirewall firewall delete rule name="rV"'

; Clean up runtime-generated files only on full uninstall (not upgrade)
${If} $UpdateMode <> 1
Delete /REBOOTOK "$INSTDIR\res\src\uv.lock"
Expand Down
2 changes: 1 addition & 1 deletion docs/.vitepress/theme/MyLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { Layout } = DefaultTheme
<Layout>
<template #home-hero-info-after>
<div class="home-demo">
<a class="home-demo__btn" href="https://demo.redviewer.nyc.mn" target="_blank" rel="noreferrer">
<a class="home-demo__btn" href="https://demo-rv.101114105.xyz" target="_blank" rel="noreferrer">
🎮 线上体验 Demo
</a>
</div>
Expand Down
2 changes: 1 addition & 1 deletion docs/_github/preset.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

---

[🚀快速开始](https://doc.redviewer.nyc.mn/deploy/) | [❓常见问题](https://doc.redviewer.nyc.mn/faq/)
[🚀快速开始](https://rv.101114105.xyz/deploy/) | [❓常见问题](https://rv.101114105.xyz/faq/)
4 changes: 3 additions & 1 deletion docs/_github/release_notes.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@

## 🍢 Feat/Fix

✨ 桌面应用,当前win(mac/linux很快就有
+ nyc.mn 旧域名已废弃,新域名后缀 xyz
+ win安装自动开防火墙
+ 文档更新内容,例如个人更方便的 Tunnel 方案
4 changes: 2 additions & 2 deletions docs/contribute/feed.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
<div v-if="showDialog" @click="showDialog = false" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 9999;">
<div @click.stop style="background: var(--vp-c-bg); border-radius: 8px; padding: 24px; max-width: 500px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);">
<div style="display: flex; gap: 16px; align-items: center;"><div style="flex: 1; line-height: 1.6;">
【Linux.do】给 CGS 和 rv 点 star ,<br>(注册不到半年的gh号不受理)<br>
然后加群备注 <code>lxd注册-{github用户名}</code>, <br>阅读 lxd 注册群公告后根据指示操作
【Linux.do】<br>要求有30star以上的repo<br>或一年50contributions,<br>没达标的请回<br><hr>
加群时备注一下,然后私我即可
</div>
<a href="https://qm.qq.com/q/T2SONVQmiW" target="_blank" style="display: inline-flex; align-items: center; justify-content: center; padding: 8px 16px; border-radius: 4px; border: 1px solid var(--vp-c-brand); background: var(--vp-c-brand); color: white; text-decoration: none; white-space: nowrap; cursor: pointer;"
><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="width: 20px; height: 20px; fill: currentColor;"><title>QQ</title><path d="M21.395 15.035a40 40 0 0 0-.803-2.264l-1.079-2.695c.001-.032.014-.562.014-.836C19.526 4.632 17.351 0 12 0S4.474 4.632 4.474 9.241c0 .274.013.804.014.836l-1.08 2.695a39 39 0 0 0-.802 2.264c-1.021 3.283-.69 4.643-.438 4.673.54.065 2.103-2.472 2.103-2.472 0 1.469.756 3.387 2.394 4.771-.612.188-1.363.479-1.845.835-.434.32-.379.646-.301.778.343.578 5.883.369 7.482.189 1.6.18 7.14.389 7.483-.189.078-.132.132-.458-.301-.778-.483-.356-1.233-.646-1.846-.836 1.637-1.384 2.393-3.302 2.393-4.771 0 0 1.563 2.537 2.103 2.472.251-.03.581-1.39-.438-4.673"/></svg></a>
Expand Down
61 changes: 32 additions & 29 deletions docs/deploy/adv.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,58 +5,61 @@
<a href="https://git.io/typing-svg"><img src="https://readme-typing-svg.demolab.com?font=Fira+Code&size=31&duration=800&pause=500&color=F71313&vCenter=true&repeat=false&width=150&height=32&lines=CF%E5%A4%A7%E5%96%84%E4%BA%BA%EF%BC%81" alt="Typing SVG" /></a>
</span>

## 后端
## 一、统一用 Tunnel 方案

1. 参考 [一命(令)部署](/deploy/) 的 单独部署/运行`后端`命令 部分
2. `backend.Dockerfile` 仅为在线体验服务而设,用在本地时请自测

::: info 运行容器/平台

1. 用服务器 和 公网 ip,就这样

> [!warning] 确保你的后端防火墙开放 12345 端口,或已用 nginx 反代,外部自测可访问

2. 市面上的内网穿透 ~~I don't know~~
:::

::: details 3. 大善人的内网穿透方式(Cloudflare Tunnel)

> [!warning] 必须手上有域名(事前 cf 上设置域),否则用其他部署平台方式
> 适合个人简单使用

### 一步到位流程
**准备一个 cf 账号,已挂 cf 的域名,**
**已[下载 rv](https://github.com/jasoneri/redViewer/releases) 并安装 并本地局域网已测通**

1. 登录 [Cloudflare Dashboard](https://dash.cloudflare.com/)
2. 打开左侧菜单的 "Zero Trust"
3. 选择左侧菜单的 "网络" > "连接器" > "添加/创建隧道"
4. 选择 "Cloudflare" > "起名后保存隧道"
5. 按指示在 pc/后端 安装连接器运行命令,直到下方 Connectors 状态出现 "已连接",点击下一步

### 收尾(添加已发布应用程序路由)

> [!Tip] 只有事前在 cf 设置了域,并成功接通,这步才能选择 域

6. 添加路由:子域与域自己选择
6. 添加路由:子域与域自己选择(举例是`backend.bb.cc`)
7. 服务:类型选 `http`,URL 填 `localhost:12345`,点完成设置
8. 进这个tunnel的配置点`添加已发布应用程序路由`,仿照第6,7步,填另一个子域名(举例是`frontend.bb.cc`),对照`localhost:8080`
9. 开了 rv 的 pc 浏览器进`localhost:8080`,进入超管,将`后端配置`的地址改为`http://backend.bb.cc`,点保存并刷新
10. 手机进`frontend.bb.cc`测试一下
11. 可选,在域名左侧栏安全性增加安全规则,把非 CN 的 IP 全部禁掉

## 二、前后端分离

> 本地直接用安装包的话可跳过后端

### 后端

1. 参考 [一命(令)部署](/deploy/) 的 单独部署/运行`后端`命令 部分
2. `backend.Dockerfile` 仅为在线体验服务而设,用在本地时请自测

::: info 运行容器/平台

1. 用服务器 和 公网 ip,就这样

> [!warning] 确保你的后端防火墙开放 12345 端口,或已用 nginx 反代,外部自测可访问
:::

::: details 有关 `rV在线体验` 的部署
使用的 Railway 部署后端, r2 放图片,不过r2免费储存才`10G`,
不建议日常使用,所以静态文件托管部分估摸不会扩展开发
:::

## 前端
### 前端

### 🎿 Step-1:Fork 项目
#### 🎿 Step-1:Fork 项目

[![Fork this repo](https://img.shields.io/badge/Fork-redViewer-ff3333?logo=github)](https://github.com/jasoneri/redViewer/fork)

1. ☝️ 点上面图标进行Fork
2. 点击 `Create fork` 按钮

### 🏗️ Step-2:创建 Pages 项目
#### 🏗️ Step-2:创建 Pages 项目

#### 2.1 访问 Cloudflare Dashboard
##### 2.1 访问 Cloudflare Dashboard

[![Deploy on Cloudflare Pages](https://deploy.workers.cloudflare.com/button)](https://dash.cloudflare.com/?to=/:account/pages/new/provider/github)

Expand All @@ -65,7 +68,7 @@
这种的返回到 github 上),access 权限增加 redViewer 或 All
3. 返回 cf 选 redViewer 点击 "开始设置"

#### 2.2 配置项目设置
##### 2.2 配置项目设置

| 配置项 | 值 | 说明 |
| -------- | ---- | ---- |
Expand All @@ -86,27 +89,27 @@

点击 **"保存并部署"**,等待构建完成,如无意外可以验证去耍了

### 🗄️ Step-3:配置 KV 存储
#### 🗄️ Step-3:配置 KV 存储

::: warning 如需通过 `超管` 窗口切换后端 url 的话,"KV 命名空间"是必须配置的
**全局生效**:后端地址保存到 KV 后,所有用户(包括游客)刷新页面即可生效。
:::

#### 3.1 创建 KV 命名空间
##### 3.1 创建 KV 命名空间

1. 选择左侧菜单的 "储存和数据库" > "Workers KV"
2. 点击"新建实例"按钮
3. 名称填写:`RV_KV` 进行创建

#### 3.2 绑定 KV 到 Pages 项目
##### 3.2 绑定 KV 到 Pages 项目

1. 返回上面创建的 Pages > 设置 > 绑定
2. 点击 "添加" > "KV 命名空间"
3. 变量名:`RV_KV`
4. 选择刚创建的命名空间
5. 点击"保存"

#### 3.3 重试部署
##### 3.3 重试部署

1. 进入项目的 "部署" 页面
2. 找到最新的部署记录
Expand Down
7 changes: 6 additions & 1 deletion docs/guide/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 🎸功能详细预览

建议新窗口开 [![demo](https://img.comicguispider.nyc.mn/file/rv/1769934434211_btn-demo.svg)](https://demo.redviewer.nyc.mn/) 边看此文档,直观功能演示
建议新窗口开 [![demo](https://img-cgs.101114105.xyz/file/rv/1769934434211_btn-demo.svg)](https://demo-rv.101114105.xyz/) 边看此文档,直观功能演示

## 📚 列表/网格预览

Expand All @@ -18,6 +18,11 @@

## 🎲 其他说明

### 未提及

+ 章节页预览 > 点一下系列名,弹出菜单系列列表可跳转
+ 阅读 > 翻页模式点一下下方页数可跳转页首页尾

### 筛选相关

筛选状态下,按`重新加载`就能恢复原始列表。
Expand Down
Loading