diff --git a/README.md b/README.md
index 6b98cac..1aa324a 100644
--- a/README.md
+++ b/README.md
@@ -180,17 +180,16 @@ networks:
## 环境变量
-| 变量 | 说明 | 可选值 | 默认值 |
-| ----------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
-| USERNAME | redis 部署时的管理员账号 | 任意字符串 | (空) |
-| PASSWORD | 默认部署时为唯一访问密码,redis 部署时为管理员密码 | 任意字符串 | (空) |
-| SITE_NAME | 站点名称 | 任意字符串 | MoonTV |
-| ANNOUNCEMENT | 站点公告 | 任意字符串 | 本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。 |
-| NEXT_PUBLIC_STORAGE_TYPE | 播放记录/收藏的存储方式 | localstorage(本地浏览器存储)、redis(仅 docker 支持) | localstorage |
-| REDIS_URL | redis 连接 url,若 NEXT_PUBLIC_STORAGE_TYPE 为 redis 则必填 | 连接 url | 空 |
-| NEXT_PUBLIC_ENABLE_REGISTER | 是否开放注册,仅在 redis 部署时生效 | true / false | false |
-| NEXT_PUBLIC_SEARCH_MAX_PAGE | 搜索接口可拉取的最大页数 | 1-50 | 5 |
-| NEXT_PUBLIC_AGGREGATE_SEARCH_RESULT | 搜索结果默认是否按标题和年份聚合 | true / false | true |
+| 变量 | 说明 | 可选值 | 默认值 |
+| --------------------------- | ----------------------------------------------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
+| USERNAME | redis 部署时的管理员账号 | 任意字符串 | (空) |
+| PASSWORD | 默认部署时为唯一访问密码,redis 部署时为管理员密码 | 任意字符串 | (空) |
+| SITE_NAME | 站点名称 | 任意字符串 | MoonTV |
+| ANNOUNCEMENT | 站点公告 | 任意字符串 | 本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。 |
+| NEXT_PUBLIC_STORAGE_TYPE | 播放记录/收藏的存储方式 | localstorage(本地浏览器存储)、redis(仅 docker 支持) | localstorage |
+| REDIS_URL | redis 连接 url,若 NEXT_PUBLIC_STORAGE_TYPE 为 redis 则必填 | 连接 url | 空 |
+| NEXT_PUBLIC_ENABLE_REGISTER | 是否开放注册,仅在 redis 部署时生效 | true / false | false |
+| NEXT_PUBLIC_SEARCH_MAX_PAGE | 搜索接口可拉取的最大页数 | 1-50 | 5 |
## 配置说明
diff --git a/package.json b/package.json
index 69669ca..6a31d5f 100644
--- a/package.json
+++ b/package.json
@@ -57,6 +57,7 @@
"@testing-library/react": "^15.0.7",
"@types/node": "24.0.3",
"@types/react": "^18.3.18",
+ "@types/react-dom": "^19.1.6",
"@types/testing-library__jest-dom": "^5.14.9",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 45f5795..20569b3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -114,6 +114,9 @@ importers:
'@types/react':
specifier: ^18.3.18
version: 18.3.23
+ '@types/react-dom':
+ specifier: ^19.1.6
+ version: 19.1.6(@types/react@18.3.23)
'@types/testing-library__jest-dom':
specifier: ^5.14.9
version: 5.14.9
@@ -1918,6 +1921,11 @@ packages:
peerDependencies:
'@types/react': ^18.0.0
+ '@types/react-dom@19.1.6':
+ resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==}
+ peerDependencies:
+ '@types/react': ^19.0.0
+
'@types/react@18.3.23':
resolution: {integrity: sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==}
@@ -8342,6 +8350,10 @@ snapshots:
dependencies:
'@types/react': 18.3.23
+ '@types/react-dom@19.1.6(@types/react@18.3.23)':
+ dependencies:
+ '@types/react': 18.3.23
+
'@types/react@18.3.23':
dependencies:
'@types/prop-types': 15.7.15
diff --git a/proxy.worker.js b/proxy.worker.js
new file mode 100644
index 0000000..b31c97f
--- /dev/null
+++ b/proxy.worker.js
@@ -0,0 +1,240 @@
+/* eslint-disable */
+
+addEventListener('fetch', (event) => {
+ event.respondWith(handleRequest(event.request));
+});
+
+async function handleRequest(request) {
+ try {
+ const url = new URL(request.url);
+
+ // 如果访问根目录,返回HTML
+ if (url.pathname === '/') {
+ return new Response(getRootHtml(), {
+ headers: {
+ 'Content-Type': 'text/html; charset=utf-8',
+ },
+ });
+ }
+
+ // 从请求路径中提取目标 URL
+ let actualUrlStr = decodeURIComponent(url.pathname.replace('/', ''));
+
+ // 判断用户输入的 URL 是否带有协议
+ actualUrlStr = ensureProtocol(actualUrlStr, url.protocol);
+
+ // 保留查询参数
+ actualUrlStr += url.search;
+
+ // 创建新 Headers 对象,排除以 'cf-' 开头的请求头
+ const newHeaders = filterHeaders(
+ request.headers,
+ (name) => !name.startsWith('cf-')
+ );
+
+ // 创建一个新的请求以访问目标 URL
+ const modifiedRequest = new Request(actualUrlStr, {
+ headers: newHeaders,
+ method: request.method,
+ body: request.body,
+ redirect: 'manual',
+ });
+
+ // 发起对目标 URL 的请求
+ const response = await fetch(modifiedRequest);
+ let body = response.body;
+
+ // 处理重定向
+ if ([301, 302, 303, 307, 308].includes(response.status)) {
+ body = response.body;
+ // 创建新的 Response 对象以修改 Location 头部
+ return handleRedirect(response, body);
+ } else if (response.headers.get('Content-Type')?.includes('text/html')) {
+ body = await handleHtmlContent(
+ response,
+ url.protocol,
+ url.host,
+ actualUrlStr
+ );
+ }
+
+ // 创建修改后的响应对象
+ const modifiedResponse = new Response(body, {
+ status: response.status,
+ statusText: response.statusText,
+ headers: response.headers,
+ });
+
+ // 添加禁用缓存的头部
+ setNoCacheHeaders(modifiedResponse.headers);
+
+ // 添加 CORS 头部,允许跨域访问
+ setCorsHeaders(modifiedResponse.headers);
+
+ return modifiedResponse;
+ } catch (error) {
+ // 如果请求目标地址时出现错误,返回带有错误消息的响应和状态码 500(服务器错误)
+ return jsonResponse(
+ {
+ error: error.message,
+ },
+ 500
+ );
+ }
+}
+
+// 确保 URL 带有协议
+function ensureProtocol(url, defaultProtocol) {
+ return url.startsWith('http://') || url.startsWith('https://')
+ ? url
+ : defaultProtocol + '//' + url;
+}
+
+// 处理重定向
+function handleRedirect(response, body) {
+ const location = new URL(response.headers.get('location'));
+ const modifiedLocation = `/${encodeURIComponent(location.toString())}`;
+ return new Response(body, {
+ status: response.status,
+ statusText: response.statusText,
+ headers: {
+ ...response.headers,
+ Location: modifiedLocation,
+ },
+ });
+}
+
+// 处理 HTML 内容中的相对路径
+async function handleHtmlContent(response, protocol, host, actualUrlStr) {
+ const originalText = await response.text();
+ const regex = new RegExp('((href|src|action)=["\'])/(?!/)', 'g');
+ let modifiedText = replaceRelativePaths(
+ originalText,
+ protocol,
+ host,
+ new URL(actualUrlStr).origin
+ );
+
+ return modifiedText;
+}
+
+// 替换 HTML 内容中的相对路径
+function replaceRelativePaths(text, protocol, host, origin) {
+ const regex = new RegExp('((href|src|action)=["\'])/(?!/)', 'g');
+ return text.replace(regex, `$1${protocol}//${host}/${origin}/`);
+}
+
+// 返回 JSON 格式的响应
+function jsonResponse(data, status) {
+ return new Response(JSON.stringify(data), {
+ status: status,
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8',
+ },
+ });
+}
+
+// 过滤请求头
+function filterHeaders(headers, filterFunc) {
+ return new Headers([...headers].filter(([name]) => filterFunc(name)));
+}
+
+// 设置禁用缓存的头部
+function setNoCacheHeaders(headers) {
+ headers.set('Cache-Control', 'no-store');
+}
+
+// 设置 CORS 头部
+function setCorsHeaders(headers) {
+ headers.set('Access-Control-Allow-Origin', '*');
+ headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
+ headers.set('Access-Control-Allow-Headers', '*');
+}
+
+// 返回根目录的 HTML
+function getRootHtml() {
+ return `
+
+
+
+
+ Proxy Everything
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
linkProxy Everything
+
+
+
+
+
+
+
+
+
+
+`;
+}
diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
index 8e4f490..9e17e5d 100644
--- a/src/app/admin/page.tsx
+++ b/src/app/admin/page.tsx
@@ -50,7 +50,6 @@ interface SiteConfig {
Announcement: string;
SearchDownstreamMaxPage: number;
SiteInterfaceCacheTime: number;
- SearchResultDefaultAggregate: boolean;
}
// 视频源数据类型
@@ -948,7 +947,6 @@ const SiteConfigComponent = ({ config }: { config: AdminConfig | null }) => {
Announcement: '',
SearchDownstreamMaxPage: 1,
SiteInterfaceCacheTime: 7200,
- SearchResultDefaultAggregate: false,
});
// 保存状态
const [saving, setSaving] = useState(false);
@@ -1094,45 +1092,6 @@ const SiteConfigComponent = ({ config }: { config: AdminConfig | null }) => {
/>
- {/* 默认按标题和年份聚合 */}
-
-
-
-
-
{/* 操作按钮 */}