224 lines
4.8 KiB
TypeScript
224 lines
4.8 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any,no-console */
|
|
|
|
/**
|
|
* Pansou 网盘搜索 API 客户端
|
|
* 文档: https://github.com/fish2018/pansou
|
|
*/
|
|
|
|
import { normalizeApiBaseUrl } from '@/lib/url';
|
|
|
|
// 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 baseUrl = normalizeApiBaseUrl(apiUrl);
|
|
const response = await fetch(`${baseUrl}/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 {
|
|
const baseUrl = normalizeApiBaseUrl(apiUrl);
|
|
// 获取 Token(如果需要认证)
|
|
const token = await getValidToken(
|
|
baseUrl,
|
|
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(`${baseUrl}/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 baseUrl = normalizeApiBaseUrl(apiUrl);
|
|
const response = await fetch(`${baseUrl}/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;
|
|
}
|
|
}
|