Files
MoonTVPlus/src/lib/xiaoya.client.ts

245 lines
5.5 KiB
TypeScript
Raw Normal View History

2026-01-10 21:21:08 +08:00
/* eslint-disable @typescript-eslint/no-explicit-any */
// Token 内存缓存
const tokenCache = new Map<string, { token: string; expiresAt: number }>();
export interface XiaoyaFile {
name: string;
size: number;
is_dir: boolean;
modified: string;
}
export interface XiaoyaListResponse {
content: XiaoyaFile[];
total: number;
}
export class XiaoyaClient {
2026-01-23 16:41:49 +08:00
private token = '';
2026-01-10 21:21:08 +08:00
constructor(
private baseURL: string,
private username?: string,
private password?: string,
private configToken?: string
) {}
/**
* 使Token
*/
static async login(
baseURL: string,
username: string,
password: string
): Promise<string> {
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
*/
2026-01-11 00:52:52 +08:00
async getToken(): Promise<string> {
2026-01-10 21:21:08 +08:00
// 如果配置了 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;
}
/**
*
*/
2026-01-15 23:48:13 +08:00
async listDirectory(path: string, page = 1, perPage = 100, refresh = false): Promise<XiaoyaListResponse> {
2026-01-10 21:21:08 +08:00
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,
2026-01-15 23:48:13 +08:00
refresh,
2026-01-10 21:21:08 +08:00
}),
});
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<XiaoyaListResponse> {
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<XiaoyaFile> {
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<string> {
// Alist 的直接下载链接格式
return `${this.baseURL}/d${path}`;
}
/**
* NFO
*/
async getFileContent(path: string): Promise<string> {
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<boolean> {
try {
await this.getFileInfo(path);
return true;
} catch (error) {
return false;
}
}
}