修改metainfo为数据库读取,修复tmdb代理连接错误
This commit is contained in:
@@ -46,6 +46,7 @@
|
|||||||
"next": "^14.2.33",
|
"next": "^14.2.33",
|
||||||
"next-pwa": "^5.6.0",
|
"next-pwa": "^5.6.0",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
|
"node-fetch": "^2.7.0",
|
||||||
"parse-torrent-name": "^0.5.4",
|
"parse-torrent-name": "^0.5.4",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
@@ -68,6 +69,7 @@
|
|||||||
"@types/bs58": "^5.0.0",
|
"@types/bs58": "^5.0.0",
|
||||||
"@types/he": "^1.2.3",
|
"@types/he": "^1.2.3",
|
||||||
"@types/node": "24.0.3",
|
"@types/node": "24.0.3",
|
||||||
|
"@types/node-fetch": "^2.6.13",
|
||||||
"@types/react": "^18.3.18",
|
"@types/react": "^18.3.18",
|
||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@types/testing-library__jest-dom": "^5.14.9",
|
"@types/testing-library__jest-dom": "^5.14.9",
|
||||||
|
|||||||
57
pnpm-lock.yaml
generated
57
pnpm-lock.yaml
generated
@@ -86,6 +86,9 @@ importers:
|
|||||||
next-themes:
|
next-themes:
|
||||||
specifier: ^0.4.6
|
specifier: ^0.4.6
|
||||||
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
node-fetch:
|
||||||
|
specifier: ^2.7.0
|
||||||
|
version: 2.7.0
|
||||||
parse-torrent-name:
|
parse-torrent-name:
|
||||||
specifier: ^0.5.4
|
specifier: ^0.5.4
|
||||||
version: 0.5.4
|
version: 0.5.4
|
||||||
@@ -147,6 +150,9 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: 24.0.3
|
specifier: 24.0.3
|
||||||
version: 24.0.3
|
version: 24.0.3
|
||||||
|
'@types/node-fetch':
|
||||||
|
specifier: ^2.6.13
|
||||||
|
version: 2.6.13
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ^18.3.18
|
specifier: ^18.3.18
|
||||||
version: 18.3.23
|
version: 18.3.23
|
||||||
@@ -1565,6 +1571,9 @@ packages:
|
|||||||
'@types/minimist@1.2.5':
|
'@types/minimist@1.2.5':
|
||||||
resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==}
|
resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==}
|
||||||
|
|
||||||
|
'@types/node-fetch@2.6.13':
|
||||||
|
resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==}
|
||||||
|
|
||||||
'@types/node@24.0.3':
|
'@types/node@24.0.3':
|
||||||
resolution: {integrity: sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==}
|
resolution: {integrity: sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==}
|
||||||
|
|
||||||
@@ -3007,6 +3016,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-q5YBMeWy6E2Un0nMGWMgI65MAKtaylxfNJGJxpGh45YDciZB4epbWpaAfImil6CPAPTYB4sh0URQNDRIZG5F2w==}
|
resolution: {integrity: sha512-q5YBMeWy6E2Un0nMGWMgI65MAKtaylxfNJGJxpGh45YDciZB4epbWpaAfImil6CPAPTYB4sh0URQNDRIZG5F2w==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
form-data@4.0.5:
|
||||||
|
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
fraction.js@4.3.7:
|
fraction.js@4.3.7:
|
||||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||||
|
|
||||||
@@ -4082,6 +4095,15 @@ packages:
|
|||||||
no-case@3.0.4:
|
no-case@3.0.4:
|
||||||
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
|
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
|
||||||
|
|
||||||
|
node-fetch@2.7.0:
|
||||||
|
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||||
|
engines: {node: 4.x || >=6.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
encoding: ^0.1.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
encoding:
|
||||||
|
optional: true
|
||||||
|
|
||||||
node-int64@0.4.0:
|
node-int64@0.4.0:
|
||||||
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
|
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
|
||||||
|
|
||||||
@@ -5057,6 +5079,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
|
resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
tr46@0.0.3:
|
||||||
|
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||||
|
|
||||||
tr46@1.0.1:
|
tr46@1.0.1:
|
||||||
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
|
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
|
||||||
|
|
||||||
@@ -5282,6 +5307,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==}
|
resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
|
|
||||||
|
webidl-conversions@3.0.1:
|
||||||
|
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||||
|
|
||||||
webidl-conversions@4.0.2:
|
webidl-conversions@4.0.2:
|
||||||
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
|
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
|
||||||
|
|
||||||
@@ -5334,6 +5362,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
|
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
whatwg-url@5.0.0:
|
||||||
|
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||||
|
|
||||||
whatwg-url@7.1.0:
|
whatwg-url@7.1.0:
|
||||||
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
|
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
|
||||||
|
|
||||||
@@ -7228,6 +7259,11 @@ snapshots:
|
|||||||
|
|
||||||
'@types/minimist@1.2.5': {}
|
'@types/minimist@1.2.5': {}
|
||||||
|
|
||||||
|
'@types/node-fetch@2.6.13':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 24.0.3
|
||||||
|
form-data: 4.0.5
|
||||||
|
|
||||||
'@types/node@24.0.3':
|
'@types/node@24.0.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.8.0
|
undici-types: 7.8.0
|
||||||
@@ -8874,6 +8910,14 @@ snapshots:
|
|||||||
es-set-tostringtag: 2.1.0
|
es-set-tostringtag: 2.1.0
|
||||||
mime-types: 2.1.35
|
mime-types: 2.1.35
|
||||||
|
|
||||||
|
form-data@4.0.5:
|
||||||
|
dependencies:
|
||||||
|
asynckit: 0.4.0
|
||||||
|
combined-stream: 1.0.8
|
||||||
|
es-set-tostringtag: 2.1.0
|
||||||
|
hasown: 2.0.2
|
||||||
|
mime-types: 2.1.35
|
||||||
|
|
||||||
fraction.js@4.3.7: {}
|
fraction.js@4.3.7: {}
|
||||||
|
|
||||||
framer-motion@12.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
framer-motion@12.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
@@ -10249,6 +10293,10 @@ snapshots:
|
|||||||
lower-case: 2.0.2
|
lower-case: 2.0.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
node-fetch@2.7.0:
|
||||||
|
dependencies:
|
||||||
|
whatwg-url: 5.0.0
|
||||||
|
|
||||||
node-int64@0.4.0: {}
|
node-int64@0.4.0: {}
|
||||||
|
|
||||||
node-releases@2.0.19: {}
|
node-releases@2.0.19: {}
|
||||||
@@ -11257,6 +11305,8 @@ snapshots:
|
|||||||
universalify: 0.2.0
|
universalify: 0.2.0
|
||||||
url-parse: 1.5.10
|
url-parse: 1.5.10
|
||||||
|
|
||||||
|
tr46@0.0.3: {}
|
||||||
|
|
||||||
tr46@1.0.1:
|
tr46@1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
punycode: 2.3.1
|
punycode: 2.3.1
|
||||||
@@ -11501,6 +11551,8 @@ snapshots:
|
|||||||
glob-to-regexp: 0.4.1
|
glob-to-regexp: 0.4.1
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
|
|
||||||
|
webidl-conversions@3.0.1: {}
|
||||||
|
|
||||||
webidl-conversions@4.0.2: {}
|
webidl-conversions@4.0.2: {}
|
||||||
|
|
||||||
webidl-conversions@5.0.0: {}
|
webidl-conversions@5.0.0: {}
|
||||||
@@ -11571,6 +11623,11 @@ snapshots:
|
|||||||
|
|
||||||
whatwg-mimetype@4.0.0: {}
|
whatwg-mimetype@4.0.0: {}
|
||||||
|
|
||||||
|
whatwg-url@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
tr46: 0.0.3
|
||||||
|
webidl-conversions: 3.0.1
|
||||||
|
|
||||||
whatwg-url@7.1.0:
|
whatwg-url@7.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash.sortby: 4.7.0
|
lodash.sortby: 4.7.0
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
|
||||||
import { getConfig } from '@/lib/config';
|
import { getConfig } from '@/lib/config';
|
||||||
|
import { db } from '@/lib/db';
|
||||||
import { OpenListClient } from '@/lib/openlist.client';
|
import { OpenListClient } from '@/lib/openlist.client';
|
||||||
import {
|
import {
|
||||||
getCachedMetaInfo,
|
getCachedMetaInfo,
|
||||||
@@ -273,30 +274,19 @@ async function handleOpenListProxy(request: NextRequest) {
|
|||||||
const rootPath = openListConfig.RootPath || '/';
|
const rootPath = openListConfig.RootPath || '/';
|
||||||
const client = new OpenListClient(openListConfig.URL, openListConfig.Token);
|
const client = new OpenListClient(openListConfig.URL, openListConfig.Token);
|
||||||
|
|
||||||
// 读取 metainfo.json
|
// 读取 metainfo (从数据库或缓存)
|
||||||
let metaInfo: MetaInfo | null = getCachedMetaInfo(rootPath);
|
let metaInfo: MetaInfo | null = getCachedMetaInfo(rootPath);
|
||||||
|
|
||||||
if (!metaInfo) {
|
if (!metaInfo) {
|
||||||
try {
|
try {
|
||||||
const metainfoPath = `${rootPath}${rootPath.endsWith('/') ? '' : '/'}metainfo.json`;
|
const metainfoJson = await db.getGlobalValue('video.metainfo');
|
||||||
const fileResponse = await client.getFile(metainfoPath);
|
if (metainfoJson) {
|
||||||
|
metaInfo = JSON.parse(metainfoJson) as MetaInfo;
|
||||||
if (fileResponse.code === 200 && fileResponse.data.raw_url) {
|
|
||||||
const downloadUrl = fileResponse.data.raw_url;
|
|
||||||
const contentResponse = await fetch(downloadUrl, {
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
||||||
'Accept': '*/*',
|
|
||||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const content = await contentResponse.text();
|
|
||||||
metaInfo = JSON.parse(content) as MetaInfo;
|
|
||||||
setCachedMetaInfo(rootPath, metaInfo);
|
setCachedMetaInfo(rootPath, metaInfo);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ code: 0, msg: 'metainfo.json 不存在', list: [] },
|
{ code: 0, msg: 'metainfo 不存在', list: [] },
|
||||||
{ status: 200 }
|
{ status: 200 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,35 +32,24 @@ export async function GET(request: NextRequest) {
|
|||||||
|
|
||||||
const rootPath = openListConfig.RootPath || '/';
|
const rootPath = openListConfig.RootPath || '/';
|
||||||
|
|
||||||
// 1. 读取 metainfo.json 获取元数据
|
// 1. 读取 metainfo 获取元数据
|
||||||
let metaInfo: any = null;
|
let metaInfo: any = null;
|
||||||
try {
|
try {
|
||||||
const { OpenListClient } = await import('@/lib/openlist.client');
|
const { getCachedMetaInfo, setCachedMetaInfo } = await import('@/lib/openlist-cache');
|
||||||
const { getCachedMetaInfo } = await import('@/lib/openlist-cache');
|
|
||||||
const { getTMDBImageUrl } = await import('@/lib/tmdb.search');
|
const { getTMDBImageUrl } = await import('@/lib/tmdb.search');
|
||||||
|
const { db } = await import('@/lib/db');
|
||||||
|
|
||||||
const client = new OpenListClient(openListConfig.URL, openListConfig.Token);
|
|
||||||
metaInfo = getCachedMetaInfo(rootPath);
|
metaInfo = getCachedMetaInfo(rootPath);
|
||||||
|
|
||||||
if (!metaInfo) {
|
if (!metaInfo) {
|
||||||
const metainfoPath = `${rootPath}${rootPath.endsWith('/') ? '' : '/'}metainfo.json`;
|
const metainfoJson = await db.getGlobalValue('video.metainfo');
|
||||||
const fileResponse = await client.getFile(metainfoPath);
|
if (metainfoJson) {
|
||||||
|
metaInfo = JSON.parse(metainfoJson);
|
||||||
if (fileResponse.code === 200 && fileResponse.data.raw_url) {
|
setCachedMetaInfo(rootPath, metaInfo);
|
||||||
const downloadUrl = fileResponse.data.raw_url;
|
|
||||||
const contentResponse = await fetch(downloadUrl, {
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
||||||
'Accept': '*/*',
|
|
||||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const content = await contentResponse.text();
|
|
||||||
metaInfo = JSON.parse(content);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Detail] 读取 metainfo.json 失败:', error);
|
console.error('[Detail] 从数据库读取 metainfo 失败:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 调用 openlist detail API
|
// 2. 调用 openlist detail API
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||||||
|
|
||||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||||
import { getConfig } from '@/lib/config';
|
import { getConfig } from '@/lib/config';
|
||||||
|
import { db } from '@/lib/db';
|
||||||
import { OpenListClient } from '@/lib/openlist.client';
|
import { OpenListClient } from '@/lib/openlist.client';
|
||||||
import {
|
import {
|
||||||
getCachedMetaInfo,
|
getCachedMetaInfo,
|
||||||
@@ -53,35 +54,21 @@ export async function POST(request: NextRequest) {
|
|||||||
openListConfig.Password
|
openListConfig.Password
|
||||||
);
|
);
|
||||||
|
|
||||||
// 读取现有 metainfo.json
|
// 读取现有 metainfo (从数据库或缓存)
|
||||||
let metaInfo: MetaInfo | null = getCachedMetaInfo(rootPath);
|
let metaInfo: MetaInfo | null = getCachedMetaInfo(rootPath);
|
||||||
|
|
||||||
if (!metaInfo) {
|
if (!metaInfo) {
|
||||||
try {
|
try {
|
||||||
const metainfoPath = `${rootPath}${rootPath.endsWith('/') ? '' : '/'}metainfo.json`;
|
console.log('[OpenList Correct] 尝试从数据库读取 metainfo');
|
||||||
const fileResponse = await client.getFile(metainfoPath);
|
const metainfoJson = await db.getGlobalValue('video.metainfo');
|
||||||
|
|
||||||
if (fileResponse.code === 200 && fileResponse.data.raw_url) {
|
if (metainfoJson) {
|
||||||
const downloadUrl = fileResponse.data.raw_url;
|
metaInfo = JSON.parse(metainfoJson);
|
||||||
const contentResponse = await fetch(downloadUrl, {
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
||||||
'Accept': '*/*',
|
|
||||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!contentResponse.ok) {
|
|
||||||
throw new Error(`下载失败: ${contentResponse.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = await contentResponse.text();
|
|
||||||
metaInfo = JSON.parse(content);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[OpenList Correct] 读取 metainfo.json 失败:', error);
|
console.error('[OpenList Correct] 从数据库读取 metainfo 失败:', error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'metainfo.json 读取失败' },
|
{ error: 'metainfo 读取失败' },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -107,11 +94,10 @@ export async function POST(request: NextRequest) {
|
|||||||
failed: false, // 纠错后标记为成功
|
failed: false, // 纠错后标记为成功
|
||||||
};
|
};
|
||||||
|
|
||||||
// 保存 metainfo.json
|
// 保存 metainfo 到数据库
|
||||||
const metainfoPath = `${rootPath}${rootPath.endsWith('/') ? '' : '/'}metainfo.json`;
|
const metainfoContent = JSON.stringify(metaInfo);
|
||||||
const metainfoContent = JSON.stringify(metaInfo, null, 2);
|
|
||||||
|
|
||||||
await client.uploadFile(metainfoPath, metainfoContent);
|
await db.setGlobalValue('video.metainfo', metainfoContent);
|
||||||
|
|
||||||
// 更新缓存
|
// 更新缓存
|
||||||
invalidateMetaInfoCache(rootPath);
|
invalidateMetaInfoCache(rootPath);
|
||||||
|
|||||||
@@ -125,14 +125,7 @@ export async function GET(request: NextRequest) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存 videoinfo.json
|
// 仅缓存到内存,不再持久化到 OpenList
|
||||||
const videoinfoPath = `${folderPath}/videoinfo.json`;
|
|
||||||
await client.uploadFile(
|
|
||||||
videoinfoPath,
|
|
||||||
JSON.stringify(videoInfo, null, 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 缓存
|
|
||||||
setCachedVideoInfo(folderPath, videoInfo);
|
setCachedVideoInfo(folderPath, videoInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||||||
|
|
||||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||||
import { getConfig } from '@/lib/config';
|
import { getConfig } from '@/lib/config';
|
||||||
|
import { db } from '@/lib/db';
|
||||||
import { OpenListClient } from '@/lib/openlist.client';
|
import { OpenListClient } from '@/lib/openlist.client';
|
||||||
import {
|
import {
|
||||||
getCachedMetaInfo,
|
getCachedMetaInfo,
|
||||||
@@ -48,7 +49,7 @@ export async function GET(request: NextRequest) {
|
|||||||
openListConfig.Password
|
openListConfig.Password
|
||||||
);
|
);
|
||||||
|
|
||||||
// 读取 metainfo.json
|
// 读取 metainfo (从数据库或缓存)
|
||||||
let metaInfo: MetaInfo | null = getCachedMetaInfo(rootPath);
|
let metaInfo: MetaInfo | null = getCachedMetaInfo(rootPath);
|
||||||
|
|
||||||
console.log('[OpenList List] 缓存检查:', {
|
console.log('[OpenList List] 缓存检查:', {
|
||||||
@@ -58,40 +59,15 @@ export async function GET(request: NextRequest) {
|
|||||||
|
|
||||||
if (!metaInfo) {
|
if (!metaInfo) {
|
||||||
try {
|
try {
|
||||||
const metainfoPath = `${rootPath}${rootPath.endsWith('/') ? '' : '/'}metainfo.json`;
|
console.log('[OpenList List] 尝试从数据库读取 metainfo');
|
||||||
console.log('[OpenList List] 尝试读取 metainfo.json:', metainfoPath);
|
|
||||||
|
|
||||||
const fileResponse = await client.getFile(metainfoPath);
|
const metainfoJson = await db.getGlobalValue('video.metainfo');
|
||||||
console.log('[OpenList List] getFile 完整响应:', JSON.stringify(fileResponse, null, 2));
|
|
||||||
|
|
||||||
if (fileResponse.code === 200 && fileResponse.data.raw_url) {
|
if (metainfoJson) {
|
||||||
console.log('[OpenList List] 使用 raw_url 获取文件内容');
|
console.log('[OpenList List] 从数据库获取到数据,长度:', metainfoJson.length);
|
||||||
|
|
||||||
const downloadUrl = fileResponse.data.raw_url;
|
|
||||||
console.log('[OpenList List] 下载 URL:', downloadUrl);
|
|
||||||
|
|
||||||
const contentResponse = await fetch(downloadUrl, {
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
||||||
'Accept': '*/*',
|
|
||||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
console.log('[OpenList List] fetch 响应:', {
|
|
||||||
status: contentResponse.status,
|
|
||||||
ok: contentResponse.ok,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!contentResponse.ok) {
|
|
||||||
throw new Error(`获取文件内容失败: ${contentResponse.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = await contentResponse.text();
|
|
||||||
console.log('[OpenList List] 文件内容长度:', content.length);
|
|
||||||
console.log('[OpenList List] 文件内容预览:', content.substring(0, 200));
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
metaInfo = JSON.parse(content);
|
metaInfo = JSON.parse(metainfoJson);
|
||||||
console.log('[OpenList List] JSON 解析成功');
|
console.log('[OpenList List] JSON 解析成功');
|
||||||
console.log('[OpenList List] metaInfo 结构:', {
|
console.log('[OpenList List] metaInfo 结构:', {
|
||||||
hasfolders: !!metaInfo?.folders,
|
hasfolders: !!metaInfo?.folders,
|
||||||
@@ -114,18 +90,13 @@ export async function GET(request: NextRequest) {
|
|||||||
throw new Error(`JSON 解析失败: ${(parseError as Error).message}`);
|
throw new Error(`JSON 解析失败: ${(parseError as Error).message}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('[OpenList List] getFile 失败或无 sign:', {
|
throw new Error('数据库中没有 metainfo 数据');
|
||||||
code: fileResponse.code,
|
|
||||||
message: fileResponse.message,
|
|
||||||
data: fileResponse.data,
|
|
||||||
});
|
|
||||||
throw new Error(`getFile 返回错误: code=${fileResponse.code}, message=${fileResponse.message}`);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[OpenList List] 读取 metainfo.json 失败:', error);
|
console.error('[OpenList List] 从数据库读取 metainfo 失败:', error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
error: 'metainfo.json 读取失败',
|
error: 'metainfo 读取失败',
|
||||||
details: (error as Error).message,
|
details: (error as Error).message,
|
||||||
list: [],
|
list: [],
|
||||||
total: 0,
|
total: 0,
|
||||||
|
|||||||
@@ -117,54 +117,25 @@ async function performScan(
|
|||||||
updateScanTaskProgress(taskId, 0, 0);
|
updateScanTaskProgress(taskId, 0, 0);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. 读取现有 metainfo.json (如果存在)
|
// 1. 读取现有 metainfo (从数据库或缓存)
|
||||||
let existingMetaInfo: MetaInfo | null = getCachedMetaInfo(rootPath);
|
let existingMetaInfo: MetaInfo | null = getCachedMetaInfo(rootPath);
|
||||||
|
|
||||||
if (!existingMetaInfo) {
|
if (!existingMetaInfo) {
|
||||||
try {
|
try {
|
||||||
const metainfoPath = `${rootPath}${rootPath.endsWith('/') ? '' : '/'}metainfo.json`;
|
console.log('[OpenList Refresh] 尝试从数据库读取 metainfo');
|
||||||
console.log('[OpenList Refresh] 尝试读取现有 metainfo.json:', metainfoPath);
|
const metainfoJson = await db.getGlobalValue('video.metainfo');
|
||||||
|
|
||||||
const fileResponse = await client.getFile(metainfoPath);
|
if (metainfoJson) {
|
||||||
console.log('[OpenList Refresh] getFile 完整响应:', JSON.stringify(fileResponse, null, 2));
|
existingMetaInfo = JSON.parse(metainfoJson);
|
||||||
|
console.log('[OpenList Refresh] 从数据库读取到现有数据:', {
|
||||||
if (fileResponse.code === 200 && fileResponse.data.raw_url) {
|
|
||||||
const downloadUrl = fileResponse.data.raw_url;
|
|
||||||
console.log('[OpenList Refresh] 下载 URL:', downloadUrl);
|
|
||||||
|
|
||||||
const contentResponse = await fetch(downloadUrl, {
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
||||||
'Accept': '*/*',
|
|
||||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('[OpenList Refresh] fetch 响应:', {
|
|
||||||
status: contentResponse.status,
|
|
||||||
ok: contentResponse.ok,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!contentResponse.ok) {
|
|
||||||
throw new Error(`下载失败: ${contentResponse.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = await contentResponse.text();
|
|
||||||
console.log('[OpenList Refresh] 文件内容:', {
|
|
||||||
length: content.length,
|
|
||||||
preview: content.substring(0, 300),
|
|
||||||
});
|
|
||||||
|
|
||||||
existingMetaInfo = JSON.parse(content);
|
|
||||||
console.log('[OpenList Refresh] 读取到现有数据:', {
|
|
||||||
hasfolders: !!existingMetaInfo?.folders,
|
hasfolders: !!existingMetaInfo?.folders,
|
||||||
foldersType: typeof existingMetaInfo?.folders,
|
foldersType: typeof existingMetaInfo?.folders,
|
||||||
videoCount: Object.keys(existingMetaInfo?.folders || {}).length,
|
videoCount: Object.keys(existingMetaInfo?.folders || {}).length,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[OpenList Refresh] 读取 metainfo.json 失败:', error);
|
console.error('[OpenList Refresh] 从数据库读取 metainfo 失败:', error);
|
||||||
console.log('[OpenList Refresh] 将创建新文件');
|
console.log('[OpenList Refresh] 将创建新数据');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('[OpenList Refresh] 使用缓存的 metainfo,视频数:', Object.keys(existingMetaInfo.folders).length);
|
console.log('[OpenList Refresh] 使用缓存的 metainfo,视频数:', Object.keys(existingMetaInfo.folders).length);
|
||||||
@@ -294,41 +265,23 @@ async function performScan(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 更新 metainfo.json
|
// 4. 保存 metainfo 到数据库
|
||||||
metaInfo.last_refresh = Date.now();
|
metaInfo.last_refresh = Date.now();
|
||||||
|
|
||||||
const metainfoPath = `${rootPath}${rootPath.endsWith('/') ? '' : '/'}metainfo.json`;
|
const metainfoContent = JSON.stringify(metaInfo);
|
||||||
const metainfoContent = JSON.stringify(metaInfo, null, 2);
|
console.log('[OpenList Refresh] 保存 metainfo 到数据库:', {
|
||||||
console.log('[OpenList Refresh] 上传 metainfo.json:', {
|
|
||||||
path: metainfoPath,
|
|
||||||
videoCount: Object.keys(metaInfo.folders).length,
|
videoCount: Object.keys(metaInfo.folders).length,
|
||||||
contentLength: metainfoContent.length,
|
contentLength: metainfoContent.length,
|
||||||
contentPreview: metainfoContent.substring(0, 300),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await client.uploadFile(metainfoPath, metainfoContent);
|
await db.setGlobalValue('video.metainfo', metainfoContent);
|
||||||
console.log('[OpenList Refresh] 上传成功');
|
console.log('[OpenList Refresh] 保存成功');
|
||||||
|
|
||||||
// 验证上传:立即读取文件
|
// 验证保存:立即读取数据库
|
||||||
try {
|
try {
|
||||||
console.log('[OpenList Refresh] 验证上传:读取文件');
|
console.log('[OpenList Refresh] 验证保存:读取数据库');
|
||||||
const verifyResponse = await client.getFile(metainfoPath);
|
const verifyContent = await db.getGlobalValue('video.metainfo');
|
||||||
if (verifyResponse.code === 200 && verifyResponse.data.raw_url) {
|
if (verifyContent) {
|
||||||
const downloadUrl = verifyResponse.data.raw_url;
|
|
||||||
const verifyContentResponse = await fetch(downloadUrl, {
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
||||||
'Accept': '*/*',
|
|
||||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const verifyContent = await verifyContentResponse.text();
|
|
||||||
console.log('[OpenList Refresh] 验证读取成功:', {
|
|
||||||
contentLength: verifyContent.length,
|
|
||||||
contentPreview: verifyContent.substring(0, 300),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 尝试解析
|
|
||||||
const verifyParsed = JSON.parse(verifyContent);
|
const verifyParsed = JSON.parse(verifyContent);
|
||||||
console.log('[OpenList Refresh] 验证解析成功:', {
|
console.log('[OpenList Refresh] 验证解析成功:', {
|
||||||
hasfolders: !!verifyParsed.folders,
|
hasfolders: !!verifyParsed.folders,
|
||||||
|
|||||||
@@ -80,37 +80,25 @@ export async function GET(request: NextRequest) {
|
|||||||
// 搜索 OpenList(如果配置了)
|
// 搜索 OpenList(如果配置了)
|
||||||
if (hasOpenList) {
|
if (hasOpenList) {
|
||||||
try {
|
try {
|
||||||
const { getCachedMetaInfo } = await import('@/lib/openlist-cache');
|
const { getCachedMetaInfo, setCachedMetaInfo } = await import('@/lib/openlist-cache');
|
||||||
const { getTMDBImageUrl } = await import('@/lib/tmdb.search');
|
const { getTMDBImageUrl } = await import('@/lib/tmdb.search');
|
||||||
const { OpenListClient } = await import('@/lib/openlist.client');
|
const { db } = await import('@/lib/db');
|
||||||
|
|
||||||
const rootPath = config.OpenListConfig!.RootPath || '/';
|
const rootPath = config.OpenListConfig!.RootPath || '/';
|
||||||
let metaInfo = getCachedMetaInfo(rootPath);
|
let metaInfo = getCachedMetaInfo(rootPath);
|
||||||
|
|
||||||
// 如果没有缓存,尝试从 OpenList 读取
|
// 如果没有缓存,尝试从数据库读取
|
||||||
if (!metaInfo) {
|
if (!metaInfo) {
|
||||||
try {
|
try {
|
||||||
const client = new OpenListClient(
|
const metainfoJson = await db.getGlobalValue('video.metainfo');
|
||||||
config.OpenListConfig!.URL,
|
if (metainfoJson) {
|
||||||
config.OpenListConfig!.Token
|
metaInfo = JSON.parse(metainfoJson);
|
||||||
);
|
if (metaInfo) {
|
||||||
const metainfoPath = `${rootPath}${rootPath.endsWith('/') ? '' : '/'}metainfo.json`;
|
setCachedMetaInfo(rootPath, metaInfo);
|
||||||
const fileResponse = await client.getFile(metainfoPath);
|
}
|
||||||
|
|
||||||
if (fileResponse.code === 200 && fileResponse.data.raw_url) {
|
|
||||||
const downloadUrl = fileResponse.data.raw_url;
|
|
||||||
const contentResponse = await fetch(downloadUrl, {
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
||||||
'Accept': '*/*',
|
|
||||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const content = await contentResponse.text();
|
|
||||||
metaInfo = JSON.parse(content);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Search WS] 读取 metainfo.json 失败:', error);
|
console.error('[Search WS] 从数据库读取 metainfo 失败:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,26 +5,10 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||||
import { getConfig } from '@/lib/config';
|
import { getConfig } from '@/lib/config';
|
||||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||||
|
import nodeFetch from 'node-fetch';
|
||||||
|
|
||||||
export const runtime = 'nodejs';
|
export const runtime = 'nodejs';
|
||||||
|
|
||||||
// 代理 agent 缓存
|
|
||||||
const proxyAgentCache = new Map<string, HttpsProxyAgent<string>>();
|
|
||||||
|
|
||||||
function getProxyAgent(proxy: string): HttpsProxyAgent<string> {
|
|
||||||
if (!proxyAgentCache.has(proxy)) {
|
|
||||||
const agent = new HttpsProxyAgent(proxy, {
|
|
||||||
timeout: 30000,
|
|
||||||
keepAlive: true,
|
|
||||||
keepAliveMsecs: 60000,
|
|
||||||
maxSockets: 10,
|
|
||||||
maxFreeSockets: 5,
|
|
||||||
});
|
|
||||||
proxyAgentCache.set(proxy, agent);
|
|
||||||
}
|
|
||||||
return proxyAgentCache.get(proxy)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/tmdb/search?query=xxx
|
* GET /api/tmdb/search?query=xxx
|
||||||
* 搜索TMDB,返回多个结果供用户选择
|
* 搜索TMDB,返回多个结果供用户选择
|
||||||
@@ -59,14 +43,18 @@ export async function GET(request: NextRequest) {
|
|||||||
|
|
||||||
const fetchOptions: any = tmdbProxy
|
const fetchOptions: any = tmdbProxy
|
||||||
? {
|
? {
|
||||||
agent: getProxyAgent(tmdbProxy),
|
agent: new HttpsProxyAgent(tmdbProxy, {
|
||||||
|
timeout: 30000,
|
||||||
|
keepAlive: false,
|
||||||
|
}),
|
||||||
signal: AbortSignal.timeout(30000),
|
signal: AbortSignal.timeout(30000),
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
signal: AbortSignal.timeout(15000),
|
signal: AbortSignal.timeout(15000),
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch(url, fetchOptions);
|
// 使用 node-fetch 而不是原生 fetch
|
||||||
|
const response = await nodeFetch(url, fetchOptions);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error('TMDB 搜索失败:', response.status, response.statusText);
|
console.error('TMDB 搜索失败:', response.status, response.statusText);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ interface VideoInfoCacheEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const METAINFO_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7天
|
const METAINFO_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7天
|
||||||
const VIDEOINFO_CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 1天
|
const VIDEOINFO_CACHE_TTL_MS = 60 * 60 * 1000; // 1小时
|
||||||
|
|
||||||
const METAINFO_CACHE: Map<string, MetaInfoCacheEntry> = new Map();
|
const METAINFO_CACHE: Map<string, MetaInfoCacheEntry> = new Map();
|
||||||
const VIDEOINFO_CACHE: Map<string, VideoInfoCacheEntry> = new Map();
|
const VIDEOINFO_CACHE: Map<string, VideoInfoCacheEntry> = new Map();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any,no-console */
|
/* eslint-disable @typescript-eslint/no-explicit-any,no-console */
|
||||||
|
|
||||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||||
|
import nodeFetch from 'node-fetch';
|
||||||
|
|
||||||
export interface TMDBMovie {
|
export interface TMDBMovie {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -45,29 +46,6 @@ interface TMDBTVAiringTodayResponse {
|
|||||||
total_results: number;
|
total_results: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 代理 agent 缓存,避免每次都创建新实例
|
|
||||||
const proxyAgentCache = new Map<string, HttpsProxyAgent<string>>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取或创建代理 agent(复用连接池)
|
|
||||||
*/
|
|
||||||
function getProxyAgent(proxy: string): HttpsProxyAgent<string> {
|
|
||||||
if (!proxyAgentCache.has(proxy)) {
|
|
||||||
const agent = new HttpsProxyAgent(proxy, {
|
|
||||||
// 增加超时时间
|
|
||||||
timeout: 30000, // 30秒
|
|
||||||
// 保持连接活跃
|
|
||||||
keepAlive: true,
|
|
||||||
keepAliveMsecs: 60000, // 60秒
|
|
||||||
// 最大空闲连接数
|
|
||||||
maxSockets: 10,
|
|
||||||
maxFreeSockets: 5,
|
|
||||||
});
|
|
||||||
proxyAgentCache.set(proxy, agent);
|
|
||||||
}
|
|
||||||
return proxyAgentCache.get(proxy)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取即将上映的电影
|
* 获取即将上映的电影
|
||||||
* @param apiKey - TMDB API Key
|
* @param apiKey - TMDB API Key
|
||||||
@@ -90,21 +68,25 @@ export async function getTMDBUpcomingMovies(
|
|||||||
const url = `https://api.themoviedb.org/3/movie/upcoming?api_key=${apiKey}&language=zh-CN&page=${page}®ion=${region}`;
|
const url = `https://api.themoviedb.org/3/movie/upcoming?api_key=${apiKey}&language=zh-CN&page=${page}®ion=${region}`;
|
||||||
const fetchOptions: any = proxy
|
const fetchOptions: any = proxy
|
||||||
? {
|
? {
|
||||||
agent: getProxyAgent(proxy),
|
agent: new HttpsProxyAgent(proxy, {
|
||||||
|
timeout: 30000,
|
||||||
|
keepAlive: false,
|
||||||
|
}),
|
||||||
signal: AbortSignal.timeout(30000),
|
signal: AbortSignal.timeout(30000),
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
signal: AbortSignal.timeout(15000),
|
signal: AbortSignal.timeout(15000),
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch(url, fetchOptions);
|
// 使用 node-fetch 而不是原生 fetch
|
||||||
|
const response = await nodeFetch(url, fetchOptions);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error('TMDB API 请求失败:', response.status, response.statusText);
|
console.error('TMDB API 请求失败:', response.status, response.statusText);
|
||||||
return { code: response.status, list: [] };
|
return { code: response.status, list: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: TMDBUpcomingResponse = await response.json();
|
const data: TMDBUpcomingResponse = await response.json() as TMDBUpcomingResponse;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
@@ -137,21 +119,25 @@ export async function getTMDBUpcomingTVShows(
|
|||||||
const url = `https://api.themoviedb.org/3/tv/on_the_air?api_key=${apiKey}&language=zh-CN&page=${page}`;
|
const url = `https://api.themoviedb.org/3/tv/on_the_air?api_key=${apiKey}&language=zh-CN&page=${page}`;
|
||||||
const fetchOptions: any = proxy
|
const fetchOptions: any = proxy
|
||||||
? {
|
? {
|
||||||
agent: getProxyAgent(proxy),
|
agent: new HttpsProxyAgent(proxy, {
|
||||||
|
timeout: 30000,
|
||||||
|
keepAlive: false,
|
||||||
|
}),
|
||||||
signal: AbortSignal.timeout(30000),
|
signal: AbortSignal.timeout(30000),
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
signal: AbortSignal.timeout(15000),
|
signal: AbortSignal.timeout(15000),
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch(url, fetchOptions);
|
// 使用 node-fetch 而不是原生 fetch
|
||||||
|
const response = await nodeFetch(url, fetchOptions);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error('TMDB TV API 请求失败:', response.status, response.statusText);
|
console.error('TMDB TV API 请求失败:', response.status, response.statusText);
|
||||||
return { code: response.status, list: [] };
|
return { code: response.status, list: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: TMDBTVAiringTodayResponse = await response.json();
|
const data: TMDBTVAiringTodayResponse = await response.json() as TMDBTVAiringTodayResponse;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||||
|
import nodeFetch from 'node-fetch';
|
||||||
|
|
||||||
export interface TMDBSearchResult {
|
export interface TMDBSearchResult {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -21,29 +22,6 @@ interface TMDBSearchResponse {
|
|||||||
total_results: number;
|
total_results: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 代理 agent 缓存,避免每次都创建新实例
|
|
||||||
const proxyAgentCache = new Map<string, HttpsProxyAgent<string>>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取或创建代理 agent(复用连接池)
|
|
||||||
*/
|
|
||||||
function getProxyAgent(proxy: string): HttpsProxyAgent<string> {
|
|
||||||
if (!proxyAgentCache.has(proxy)) {
|
|
||||||
const agent = new HttpsProxyAgent(proxy, {
|
|
||||||
// 增加超时时间
|
|
||||||
timeout: 30000, // 30秒
|
|
||||||
// 保持连接活跃
|
|
||||||
keepAlive: true,
|
|
||||||
keepAliveMsecs: 60000, // 60秒
|
|
||||||
// 最大空闲连接数
|
|
||||||
maxSockets: 10,
|
|
||||||
maxFreeSockets: 5,
|
|
||||||
});
|
|
||||||
proxyAgentCache.set(proxy, agent);
|
|
||||||
}
|
|
||||||
return proxyAgentCache.get(proxy)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 搜索 TMDB (电影+电视剧)
|
* 搜索 TMDB (电影+电视剧)
|
||||||
*/
|
*/
|
||||||
@@ -59,26 +37,28 @@ export async function searchTMDB(
|
|||||||
|
|
||||||
// 使用 multi search 同时搜索电影和电视剧
|
// 使用 multi search 同时搜索电影和电视剧
|
||||||
const url = `https://api.themoviedb.org/3/search/multi?api_key=${apiKey}&language=zh-CN&query=${encodeURIComponent(query)}&page=1`;
|
const url = `https://api.themoviedb.org/3/search/multi?api_key=${apiKey}&language=zh-CN&query=${encodeURIComponent(query)}&page=1`;
|
||||||
|
|
||||||
const fetchOptions: any = proxy
|
const fetchOptions: any = proxy
|
||||||
? {
|
? {
|
||||||
agent: getProxyAgent(proxy),
|
agent: new HttpsProxyAgent(proxy, {
|
||||||
// 设置请求超时(30秒)
|
timeout: 30000,
|
||||||
|
keepAlive: false,
|
||||||
|
}),
|
||||||
signal: AbortSignal.timeout(30000),
|
signal: AbortSignal.timeout(30000),
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
// 即使不用代理也设置超时
|
|
||||||
signal: AbortSignal.timeout(15000),
|
signal: AbortSignal.timeout(15000),
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch(url, fetchOptions);
|
// 使用 node-fetch 而不是原生 fetch,因为原生 fetch 不支持 agent 选项
|
||||||
|
const response = await nodeFetch(url, fetchOptions);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error('TMDB 搜索失败:', response.status, response.statusText);
|
console.error('TMDB 搜索失败:', response.status, response.statusText);
|
||||||
return { code: response.status, result: null };
|
return { code: response.status, result: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: TMDBSearchResponse = await response.json();
|
const data: TMDBSearchResponse = await response.json() as TMDBSearchResponse;
|
||||||
|
|
||||||
// 过滤出电影和电视剧,取第一个结果
|
// 过滤出电影和电视剧,取第一个结果
|
||||||
const validResults = data.results.filter(
|
const validResults = data.results.filter(
|
||||||
|
|||||||
Reference in New Issue
Block a user