openlist离线下载增加额外源选择

This commit is contained in:
mtvpls
2026-05-22 00:24:44 +08:00
parent a24de1346f
commit 7012a79210
7 changed files with 286 additions and 94 deletions
+4
View File
@@ -141,6 +141,10 @@ export interface AdminConfig {
RootPath?: string; // 旧字段:根目录路径(向后兼容,迁移后删除)
RootPaths?: string[]; // 新字段:多根目录路径列表
OfflineDownloadPath: string; // 离线下载目录,默认 "/"
OfflineDownloadUseCustomSource?: boolean; // 离线下载是否使用独立 OpenList 源
OfflineDownloadURL?: string; // 独立离线下载 OpenList 服务器地址
OfflineDownloadUsername?: string; // 独立离线下载 OpenList 账号
OfflineDownloadPassword?: string; // 独立离线下载 OpenList 密码
LastRefreshTime?: number; // 上次刷新时间戳
ResourceCount?: number; // 资源数量
ScanInterval?: number; // 定时扫描间隔(分钟),0表示关闭,最低60分钟
+11 -45
View File
@@ -5,7 +5,11 @@ import { parseStringPromise } from 'xml2js';
import { getConfig, setCachedConfig } from '@/lib/config';
import { db, getStorage } from '@/lib/db';
import { EmailService } from '@/lib/email.service';
import { OpenListClient } from '@/lib/openlist.client';
import {
addOpenListOfflineDownload,
getOfflineDownloadBasePath,
joinOpenListPath,
} from '@/lib/openlist-offline-download';
import { AnimeSubscription, AnimeSubscriptionDownloadTool } from '@/types/anime-subscription';
const downloadTools: AnimeSubscriptionDownloadTool[] = ['aria2', 'qBittorrent', 'Transmission'];
@@ -128,50 +132,11 @@ export async function addOfflineDownload(
downloadPath: string
) {
const config = await getConfig();
const openlistConfig = config.OpenListConfig;
const downloadTool = getAnimeSubscriptionDownloadTool(
config.AnimeSubscriptionConfig?.DownloadTool
);
if (!openlistConfig?.Enabled) {
throw new Error('私人影库功能未启用');
}
if (
!openlistConfig.URL ||
!openlistConfig.Username ||
!openlistConfig.Password
) {
throw new Error('OpenList 配置不完整');
}
const client = new OpenListClient(
openlistConfig.URL,
openlistConfig.Username,
openlistConfig.Password
);
const token = await (client as any).getToken();
const openlistUrl = `${openlistConfig.URL.replace(/\/$/, '')}/api/fs/add_offline_download`;
const response = await fetch(openlistUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: token,
},
body: JSON.stringify({
path: downloadPath,
urls: [torrentUrl],
tool: downloadTool,
}),
});
const data = await response.json();
if (!response.ok || data.code !== 200) {
throw new Error(data.message || '添加离线下载任务失败');
}
await addOpenListOfflineDownload(config, downloadPath, torrentUrl, downloadTool);
}
/**
@@ -306,9 +271,7 @@ async function sendAnimeUpdateNotifications(
*/
export async function checkSubscription(subscription: AnimeSubscription) {
const config = await getConfig();
const openlistConfig = config.OpenListConfig;
if (!openlistConfig?.OfflineDownloadPath) {
if (!config.OpenListConfig?.OfflineDownloadPath) {
throw new Error('OpenList 离线下载路径未配置');
}
@@ -329,7 +292,10 @@ export async function checkSubscription(subscription: AnimeSubscription) {
const downloaded = [];
for (const item of newEpisodes) {
try {
const downloadPath = `${openlistConfig.OfflineDownloadPath.replace(/\/$/, '')}/${subscription.title}`;
const downloadPath = joinOpenListPath(
getOfflineDownloadBasePath(config),
subscription.title
);
await addOfflineDownload(item.torrentUrl, downloadPath);
// 成功后更新 lastEpisode
+23
View File
@@ -558,6 +558,29 @@ export function configSelfCheck(adminConfig: AdminConfig): AdminConfig {
}
adminConfig.LiveRefreshIntervalHours = normalizeLiveRefreshIntervalHours(adminConfig.LiveRefreshIntervalHours);
if (adminConfig.OpenListConfig) {
if (!adminConfig.OpenListConfig.RootPaths) {
adminConfig.OpenListConfig.RootPaths = adminConfig.OpenListConfig.RootPath
? [adminConfig.OpenListConfig.RootPath]
: ['/'];
}
if (!adminConfig.OpenListConfig.OfflineDownloadPath) {
adminConfig.OpenListConfig.OfflineDownloadPath = '/';
}
if (adminConfig.OpenListConfig.OfflineDownloadUseCustomSource === undefined) {
adminConfig.OpenListConfig.OfflineDownloadUseCustomSource = false;
}
if (adminConfig.OpenListConfig.OfflineDownloadURL === undefined) {
adminConfig.OpenListConfig.OfflineDownloadURL = '';
}
if (adminConfig.OpenListConfig.OfflineDownloadUsername === undefined) {
adminConfig.OpenListConfig.OfflineDownloadUsername = '';
}
if (adminConfig.OpenListConfig.OfflineDownloadPassword === undefined) {
adminConfig.OpenListConfig.OfflineDownloadPassword = '';
}
}
// 用户信息已迁移到新版数据库
// 这里只保留站长用户用于兼容性,其他用户从数据库读取
const ownerUser = process.env.USERNAME;
+82
View File
@@ -0,0 +1,82 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { AdminConfig } from '@/lib/admin.types';
import { OpenListClient } from '@/lib/openlist.client';
type OpenListOfflineDownloadSource = {
url: string;
username: string;
password: string;
};
function getOfflineDownloadSource(config: AdminConfig): OpenListOfflineDownloadSource {
const openlistConfig = config.OpenListConfig;
if (!openlistConfig?.Enabled) {
throw new Error('私人影库功能未启用');
}
const useCustomSource = openlistConfig.OfflineDownloadUseCustomSource === true;
const source = useCustomSource
? {
url: openlistConfig.OfflineDownloadURL || '',
username: openlistConfig.OfflineDownloadUsername || '',
password: openlistConfig.OfflineDownloadPassword || '',
}
: {
url: openlistConfig.URL,
username: openlistConfig.Username,
password: openlistConfig.Password,
};
if (!source.url || !source.username || !source.password) {
throw new Error(
useCustomSource
? '离线下载 OpenList 配置不完整'
: 'OpenList 配置不完整'
);
}
return source;
}
export function getOfflineDownloadBasePath(config: AdminConfig): string {
const path = config.OpenListConfig?.OfflineDownloadPath || '/';
const normalizedPath = path.replace(/\/$/, '');
return normalizedPath || '/';
}
export function joinOpenListPath(basePath: string, name: string): string {
return basePath === '/' ? `/${name}` : `${basePath}/${name}`;
}
export async function addOpenListOfflineDownload(
config: AdminConfig,
downloadPath: string,
url: string,
tool: string
) {
const source = getOfflineDownloadSource(config);
const client = new OpenListClient(source.url, source.username, source.password);
const token = await (client as any).getToken();
const openlistUrl = `${source.url.replace(/\/$/, '')}/api/fs/add_offline_download`;
const response = await fetch(openlistUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: token,
},
body: JSON.stringify({
path: downloadPath,
urls: [url],
tool,
}),
});
const data = await response.json();
if (!response.ok || data.code !== 200) {
throw new Error(data.message || '添加离线下载任务失败');
}
}