/* eslint-disable @typescript-eslint/no-explicit-any */ // Token 内存缓存 const tokenCache = new Map(); export interface XiaoyaFile { name: string; size: number; is_dir: boolean; modified: string; } export interface XiaoyaListResponse { content: XiaoyaFile[]; total: number; } export class XiaoyaClient { private token = ''; constructor( private baseURL: string, private username?: string, private password?: string, private configToken?: string ) {} /** * 使用账号密码登录获取Token */ static async login( baseURL: string, username: string, password: string ): Promise { const response = await fetch(`${baseURL}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username, password, }), }); if (!response.ok) { throw new Error(`小雅登录失败: ${response.status}`); } const data = await response.json(); if (data.code !== 200 || !data.data?.token) { throw new Error('小雅登录失败: 未获取到Token'); } return data.data.token; } /** * 获取缓存的 Token 或重新登录 */ async getToken(): Promise { // 如果配置了 Token,直接使用 if (this.configToken) { return this.configToken; } // 如果没有配置用户名密码,返回空字符串(guest 模式) if (!this.username || !this.password) { return ''; } const cacheKey = `${this.baseURL}:${this.username}`; const cached = tokenCache.get(cacheKey); // 如果有缓存且未过期,直接返回 if (cached && cached.expiresAt > Date.now()) { this.token = cached.token; return this.token; } // 否则重新登录 console.log('[XiaoyaClient] Token 不存在或已过期,重新登录'); this.token = await XiaoyaClient.login( this.baseURL, this.username, this.password ); // 缓存 Token,设置 1 小时过期 tokenCache.set(cacheKey, { token: this.token, expiresAt: Date.now() + 60 * 60 * 1000, }); return this.token; } /** * 获取基础 URL */ getBaseURL(): string { return this.baseURL; } /** * 列出目录内容 */ async listDirectory(path: string, page = 1, perPage = 100, refresh = false): Promise { const token = await this.getToken(); const response = await fetch(`${this.baseURL}/api/fs/list`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': token, }, body: JSON.stringify({ path, page, per_page: perPage, refresh, }), }); if (!response.ok) { throw new Error(`小雅列表获取失败: ${response.status}`); } const data = await response.json(); if (data.code !== 200) { throw new Error(`小雅列表获取失败: ${data.message}`); } return { content: data.data.content || [], total: data.data.total || 0, }; } /** * 搜索文件 */ async search(keyword: string, page = 1, perPage = 100): Promise { const token = await this.getToken(); const response = await fetch(`${this.baseURL}/api/fs/search`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': token, }, body: JSON.stringify({ parent: '/', keywords: keyword, scope: 1, // 递归搜索 page, per_page: perPage, }), }); if (!response.ok) { throw new Error(`小雅搜索失败: ${response.status}`); } const data = await response.json(); if (data.code !== 200) { throw new Error(`小雅搜索失败: ${data.message}`); } return { content: data.data.content || [], total: data.data.total || 0, }; } /** * 获取文件信息 */ async getFileInfo(path: string): Promise { const token = await this.getToken(); const response = await fetch(`${this.baseURL}/api/fs/get`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': token, }, body: JSON.stringify({ path, }), }); if (!response.ok) { throw new Error(`小雅文件信息获取失败: ${response.status}`); } const data = await response.json(); if (data.code !== 200) { throw new Error(`小雅文件信息获取失败: ${data.message}`); } return data.data; } /** * 获取文件下载链接 */ async getDownloadUrl(path: string): Promise { // Alist 的直接下载链接格式 return `${this.baseURL}/d${path}`; } /** * 获取文件内容(用于读取 NFO 等文本文件) */ async getFileContent(path: string): Promise { const downloadUrl = await this.getDownloadUrl(path); const response = await fetch(downloadUrl, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', }, }); if (!response.ok) { throw new Error(`文件读取失败: ${response.status}`); } return await response.text(); } /** * 检查文件是否存在 */ async fileExists(path: string): Promise { try { await this.getFileInfo(path); return true; } catch (error) { return false; } } }