增加盘搜
This commit is contained in:
@@ -22,6 +22,10 @@ export interface AdminConfig {
|
||||
// TMDB配置
|
||||
TMDBApiKey?: string;
|
||||
TMDBProxy?: string;
|
||||
// Pansou配置
|
||||
PansouApiUrl?: string;
|
||||
PansouUsername?: string;
|
||||
PansouPassword?: string;
|
||||
// 评论功能开关
|
||||
EnableComments: boolean;
|
||||
// 自定义去广告代码
|
||||
|
||||
218
src/lib/pansou.client.ts
Normal file
218
src/lib/pansou.client.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any,no-console */
|
||||
|
||||
/**
|
||||
* Pansou 网盘搜索 API 客户端
|
||||
* 文档: https://github.com/fish2018/pansou
|
||||
*/
|
||||
|
||||
// Token 缓存
|
||||
let cachedToken: string | null = null;
|
||||
let tokenExpiry: number | null = null;
|
||||
|
||||
export interface PansouLink {
|
||||
url: string;
|
||||
password: string;
|
||||
note: string;
|
||||
datetime: string;
|
||||
source: string;
|
||||
images?: string[];
|
||||
}
|
||||
|
||||
export interface PansouSearchResult {
|
||||
total: number;
|
||||
merged_by_type?: {
|
||||
[key: string]: PansouLink[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface PansouLoginResponse {
|
||||
token: string;
|
||||
expires_at: number;
|
||||
username: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录 Pansou 获取 Token
|
||||
*/
|
||||
export async function loginPansou(
|
||||
apiUrl: string,
|
||||
username: string,
|
||||
password: string
|
||||
): Promise<string> {
|
||||
try {
|
||||
const response = await fetch(`${apiUrl}/api/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ username, password }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || '登录失败');
|
||||
}
|
||||
|
||||
const data: PansouLoginResponse = await response.json();
|
||||
|
||||
// 缓存 Token
|
||||
cachedToken = data.token;
|
||||
tokenExpiry = data.expires_at;
|
||||
|
||||
return data.token;
|
||||
} catch (error) {
|
||||
console.error('Pansou 登录失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取有效的 Token(自动处理登录和缓存)
|
||||
*/
|
||||
async function getValidToken(
|
||||
apiUrl: string,
|
||||
username?: string,
|
||||
password?: string
|
||||
): Promise<string | null> {
|
||||
// 如果没有配置账号密码,返回 null(不需要认证)
|
||||
if (!username || !password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查缓存的 Token 是否有效
|
||||
if (cachedToken && tokenExpiry) {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
// 提前 5 分钟刷新 Token
|
||||
if (tokenExpiry - now > 300) {
|
||||
return cachedToken;
|
||||
}
|
||||
}
|
||||
|
||||
// Token 过期或不存在,重新登录
|
||||
try {
|
||||
return await loginPansou(apiUrl, username, password);
|
||||
} catch (error) {
|
||||
console.error('获取 Pansou Token 失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索网盘资源
|
||||
*/
|
||||
export async function searchPansou(
|
||||
apiUrl: string,
|
||||
keyword: string,
|
||||
options?: {
|
||||
username?: string;
|
||||
password?: string;
|
||||
refresh?: boolean;
|
||||
cloudTypes?: string[];
|
||||
}
|
||||
): Promise<PansouSearchResult> {
|
||||
try {
|
||||
// 获取 Token(如果需要认证)
|
||||
const token = await getValidToken(
|
||||
apiUrl,
|
||||
options?.username,
|
||||
options?.password
|
||||
);
|
||||
|
||||
// 构建请求头
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
// 构建请求体
|
||||
const body: any = {
|
||||
kw: keyword,
|
||||
res: 'merge', // 只返回按网盘类型分类的结果
|
||||
};
|
||||
|
||||
if (options?.refresh) {
|
||||
body.refresh = true;
|
||||
}
|
||||
|
||||
if (options?.cloudTypes && options.cloudTypes.length > 0) {
|
||||
body.cloud_types = options.cloudTypes;
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiUrl}/api/search`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || error.message || '搜索失败');
|
||||
}
|
||||
|
||||
const responseData = await response.json();
|
||||
|
||||
// Pansou API 返回的数据结构是 { code, message, data }
|
||||
// 实际数据在 data 字段中
|
||||
let data: PansouSearchResult;
|
||||
|
||||
if (responseData.data) {
|
||||
// 如果有 data 字段,使用 data 中的内容
|
||||
data = responseData.data;
|
||||
} else {
|
||||
// 否则直接使用返回的数据
|
||||
data = responseData;
|
||||
}
|
||||
|
||||
// 验证返回的数据结构
|
||||
if (!data || typeof data !== 'object') {
|
||||
throw new Error('返回数据格式错误');
|
||||
}
|
||||
|
||||
// 确保 merged_by_type 存在
|
||||
if (!data.merged_by_type) {
|
||||
data.merged_by_type = {};
|
||||
}
|
||||
|
||||
// 确保 total 存在
|
||||
if (typeof data.total !== 'number') {
|
||||
data.total = 0;
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Pansou 搜索失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存的 Token
|
||||
*/
|
||||
export function clearPansouToken(): void {
|
||||
cachedToken = null;
|
||||
tokenExpiry = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 Pansou 服务是否可用
|
||||
*/
|
||||
export async function checkPansouHealth(apiUrl: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(`${apiUrl}/api/health`, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.status === 'ok';
|
||||
} catch (error) {
|
||||
console.error('Pansou 健康检查失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user