mirror of
https://github.com/wushuo894/ani-rss.git
synced 2026-03-15 01:20:23 +00:00
feat: 将OpenList上传移至通知, 现在支持洗版上传了
This commit is contained in:
@@ -3,10 +3,7 @@ package ani.rss.download;
|
||||
import ani.rss.commons.ExceptionUtils;
|
||||
import ani.rss.commons.FileUtils;
|
||||
import ani.rss.commons.GsonStatic;
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.entity.Config;
|
||||
import ani.rss.entity.Item;
|
||||
import ani.rss.entity.TorrentsInfo;
|
||||
import ani.rss.entity.*;
|
||||
import ani.rss.enums.NotificationStatusEnum;
|
||||
import ani.rss.enums.StringEnum;
|
||||
import ani.rss.util.basic.HttpReq;
|
||||
@@ -28,12 +25,9 @@ import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.Method;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -384,7 +378,7 @@ public class OpenList implements BaseDownload {
|
||||
List<OpenListFileInfo> openListFileInfos = ls(path);
|
||||
List<OpenListFileInfo> list = openListFileInfos.stream()
|
||||
.flatMap(openListFileInfo -> {
|
||||
if (openListFileInfo.getIs_dir()) {
|
||||
if (openListFileInfo.getIsDir()) {
|
||||
return findFiles(path + "/" + openListFileInfo.getName()).stream();
|
||||
}
|
||||
return Stream.of(openListFileInfo);
|
||||
@@ -396,17 +390,6 @@ public class OpenList implements BaseDownload {
|
||||
}));
|
||||
}
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class OpenListFileInfo implements Serializable {
|
||||
private String name;
|
||||
private Long size;
|
||||
private Boolean is_dir;
|
||||
private Date modified;
|
||||
private Date created;
|
||||
private String path;
|
||||
}
|
||||
|
||||
/**
|
||||
* post api
|
||||
*
|
||||
|
||||
@@ -164,11 +164,6 @@ public class Config implements Serializable {
|
||||
*/
|
||||
private Boolean deleteStandbyRSSOnly;
|
||||
|
||||
/**
|
||||
* 删除本地文件
|
||||
*/
|
||||
private Boolean deleteFiles;
|
||||
|
||||
/**
|
||||
* 自动推断剧集偏移
|
||||
*/
|
||||
@@ -416,41 +411,6 @@ public class Config implements Serializable {
|
||||
*/
|
||||
private Boolean autoUpdate;
|
||||
|
||||
/**
|
||||
* OpenList
|
||||
*/
|
||||
private String alistHost;
|
||||
|
||||
/**
|
||||
* OpenList 令牌
|
||||
*/
|
||||
private String alistToken;
|
||||
|
||||
/**
|
||||
* 上传位置
|
||||
*/
|
||||
private String alistPath;
|
||||
|
||||
/**
|
||||
* 剧场版上传位置
|
||||
*/
|
||||
private String alistOvaPath;
|
||||
|
||||
/**
|
||||
* 启用 OpenList
|
||||
*/
|
||||
private Boolean alist;
|
||||
|
||||
/**
|
||||
* OpenList 上传失败时的重试次数
|
||||
*/
|
||||
private Integer alistRetry;
|
||||
|
||||
/**
|
||||
* OpenList 添加上传任务
|
||||
*/
|
||||
private Boolean alistTask;
|
||||
|
||||
/**
|
||||
* 版本
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
package ani.rss.notification;
|
||||
|
||||
import ani.rss.commons.FileUtils;
|
||||
import ani.rss.commons.GsonStatic;
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.entity.NotificationConfig;
|
||||
import ani.rss.entity.OpenListFileInfo;
|
||||
import ani.rss.enums.NotificationStatusEnum;
|
||||
import ani.rss.enums.StringEnum;
|
||||
import ani.rss.service.DownloadService;
|
||||
import ani.rss.util.basic.HttpReq;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.text.StrFormatter;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.http.Header;
|
||||
import cn.hutool.http.HttpConfig;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public class OpenListUploadNotification implements BaseNotification {
|
||||
/**
|
||||
* 上传配置
|
||||
*/
|
||||
private final HttpConfig httpConfig = new HttpConfig()
|
||||
.setBlockSize(1024 * 1024 * 50);
|
||||
private NotificationConfig notificationConfig;
|
||||
|
||||
@Override
|
||||
public Boolean send(NotificationConfig notificationConfig, Ani ani, String text, NotificationStatusEnum notificationStatusEnum) {
|
||||
Assert.isTrue(NotificationStatusEnum.DOWNLOAD_END == notificationStatusEnum, "OpenListUpload 仅支持下载完成通知");
|
||||
|
||||
ani = ObjectUtil.clone(ani);
|
||||
|
||||
this.notificationConfig = notificationConfig;
|
||||
|
||||
|
||||
String openListUploadPath = notificationConfig.getOpenListUploadPath();
|
||||
String openListUploadOvaPath = notificationConfig.getOpenListUploadOvaPath();
|
||||
Boolean deleteOldEpisode = notificationConfig.getOpenListUploadDeleteOldEpisode();
|
||||
|
||||
// 本地位置
|
||||
String localPath = DownloadService.getDownloadPath(ani);
|
||||
|
||||
// 新的位置; 设置自定义下载位置同时启用, 用以获取新的位置
|
||||
Boolean ova = ani.getOva();
|
||||
if (ova) {
|
||||
ani.setDownloadPath(openListUploadOvaPath);
|
||||
} else {
|
||||
ani.setDownloadPath(openListUploadPath);
|
||||
}
|
||||
ani.setCustomDownloadPath(true);
|
||||
|
||||
String target = DownloadService.getDownloadPath(ani);
|
||||
target = ReUtil.replaceAll(target, "^[A-z]:", "");
|
||||
|
||||
if (ova) {
|
||||
uploadOva(localPath, target);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (deleteOldEpisode) {
|
||||
deleteOldEpisode(localPath, target);
|
||||
}
|
||||
|
||||
upload(localPath, target);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void uploadOva(String localFilePath, String cloudFilePath) {
|
||||
List<File> files = FileUtils.listFileList(localFilePath);
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
// 文件夹 跳过
|
||||
continue;
|
||||
}
|
||||
String name = file.getName();
|
||||
|
||||
if (!FileUtils.isSubtitleFormat(name) && !FileUtils.isVideoFormat(name)) {
|
||||
// 非视频与字幕 跳过
|
||||
continue;
|
||||
}
|
||||
|
||||
// 开始上传
|
||||
uploadFile(file.getAbsolutePath(), cloudFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteOldEpisode(String localFilePath, String cloudFilePath) {
|
||||
Set<String> episodeSet = FileUtils.listFileList(localFilePath)
|
||||
.stream()
|
||||
.filter(FileUtil::isFile)
|
||||
.map(File::getName)
|
||||
.filter(name -> FileUtils.isVideoFormat(name) || FileUtils.isSubtitleFormat(name))
|
||||
.filter(name -> ReUtil.contains(StringEnum.SEASON_REG, name))
|
||||
.map(name -> ReUtil.get(StringEnum.SEASON_REG, name, 0))
|
||||
.map(String::toUpperCase)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
List<OpenListFileInfo> fileInfos = fileList(cloudFilePath);
|
||||
for (OpenListFileInfo fileInfo : fileInfos) {
|
||||
String name = fileInfo.getName();
|
||||
if (!FileUtils.isVideoFormat(name) && !FileUtils.isSubtitleFormat(name)) {
|
||||
// 非视频与字幕文件
|
||||
continue;
|
||||
}
|
||||
if (!ReUtil.contains(StringEnum.SEASON_REG, name)) {
|
||||
// 确保命名
|
||||
continue;
|
||||
}
|
||||
|
||||
String episode = ReUtil.get(StringEnum.SEASON_REG, name, 0);
|
||||
episode = episode.toUpperCase();
|
||||
if (!episodeSet.contains(episode)) {
|
||||
// 新位置没有旧的同集文件, 不需要删除
|
||||
continue;
|
||||
}
|
||||
|
||||
log.info("因洗版需要删除: {}", name);
|
||||
|
||||
// 删除 洗版
|
||||
postApi("fs/remove")
|
||||
.body(GsonStatic.toJson(Map.of(
|
||||
"dir", cloudFilePath,
|
||||
"names", List.of(name)
|
||||
))).then(HttpResponse::isOk);
|
||||
}
|
||||
}
|
||||
|
||||
private void upload(String localFilePath, String cloudFilePath) {
|
||||
Boolean openListUploadDeleteLocalFile = notificationConfig.getOpenListUploadDeleteLocalFile();
|
||||
|
||||
for (File file : FileUtils.listFileList(localFilePath)) {
|
||||
if (file.isDirectory()) {
|
||||
// 过滤掉文件夹
|
||||
continue;
|
||||
}
|
||||
|
||||
String name = file.getName();
|
||||
if (!FileUtils.isVideoFormat(name) && !FileUtils.isSubtitleFormat(name)) {
|
||||
// 非视频与字幕文件
|
||||
continue;
|
||||
}
|
||||
if (!ReUtil.contains(StringEnum.SEASON_REG, name)) {
|
||||
// 确保命名
|
||||
continue;
|
||||
}
|
||||
log.info("文件上传: {} => {}", file, cloudFilePath);
|
||||
|
||||
uploadFile(file.getAbsolutePath(), cloudFilePath);
|
||||
|
||||
if (openListUploadDeleteLocalFile) {
|
||||
log.info("删除本地文件 {}", file);
|
||||
FileUtil.del(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*
|
||||
* @param localFilePath 本地文件位置
|
||||
* @param cloudFilePath 云端文件位置
|
||||
*/
|
||||
private void uploadFile(String localFilePath, String cloudFilePath) {
|
||||
Assert.isTrue(FileUtil.exist(localFilePath), "文件不存在 {}", localFilePath);
|
||||
|
||||
if (FileUtil.isDirectory(localFilePath)) {
|
||||
List<File> files = FileUtils.listFileList(localFilePath);
|
||||
for (File file : files) {
|
||||
uploadFile(file.getAbsolutePath(), cloudFilePath);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String openListUploadHost = notificationConfig.getOpenListUploadHost();
|
||||
String openListUploadApiKey = notificationConfig.getOpenListUploadApiKey();
|
||||
Boolean openListUploadTask = notificationConfig.getOpenListUploadTask();
|
||||
|
||||
String url = StrUtil.format("{}/api/fs/put", openListUploadHost);
|
||||
|
||||
|
||||
String filename = FileUtil.getName(localFilePath);
|
||||
|
||||
HttpReq
|
||||
.put(url)
|
||||
.timeout(1000 * 60 * 2)
|
||||
.setConfig(httpConfig)
|
||||
.header(Header.AUTHORIZATION, openListUploadApiKey)
|
||||
.header("As-Task", Boolean.toString(openListUploadTask))
|
||||
.header("File-Path", URLUtil.encode(cloudFilePath))
|
||||
.contentType("application/octet-stream")
|
||||
.body(ResourceUtil.getResourceObj(localFilePath))
|
||||
.then(res -> {
|
||||
Assert.isTrue(res.isOk(), "上传失败 {} 状态码:{}", localFilePath, res.getStatus());
|
||||
JsonObject jsonObject = GsonStatic.fromJson(res.body(), JsonObject.class);
|
||||
int code = jsonObject.get("code").getAsInt();
|
||||
log.info(jsonObject.toString());
|
||||
Assert.isTrue(code == 200, "上传失败 {} 状态码:{}", localFilePath, code);
|
||||
|
||||
String text = StrFormatter.format("OpenList 上传完成 {}", filename);
|
||||
if (openListUploadTask) {
|
||||
text = StrFormatter.format("已向 OpenList 添加上传任务 {}", filename);
|
||||
}
|
||||
log.info(text);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件列表
|
||||
*
|
||||
* @param path 目录
|
||||
* @return 文件列表
|
||||
*/
|
||||
public List<OpenListFileInfo> fileList(String path) {
|
||||
try {
|
||||
return postApi("fs/list")
|
||||
.body(GsonStatic.toJson(Map.of(
|
||||
"path", path,
|
||||
"page", 1,
|
||||
"per_page", 256,
|
||||
"refresh", true
|
||||
)))
|
||||
.thenFunction(res -> {
|
||||
JsonObject jsonObject = GsonStatic.fromJson(res.body(), JsonObject.class);
|
||||
int code = jsonObject.get("code").getAsInt();
|
||||
if (code != 200) {
|
||||
return List.of();
|
||||
}
|
||||
JsonElement data = jsonObject.get("data");
|
||||
if (Objects.isNull(data) || data.isJsonNull()) {
|
||||
return List.of();
|
||||
}
|
||||
JsonElement content = data.getAsJsonObject()
|
||||
.get("content");
|
||||
if (Objects.isNull(content) || content.isJsonNull()) {
|
||||
return List.of();
|
||||
}
|
||||
List<OpenListFileInfo> infos = GsonStatic.fromJsonList(content.getAsJsonArray(), OpenListFileInfo.class);
|
||||
for (OpenListFileInfo info : infos) {
|
||||
info.setPath(path);
|
||||
}
|
||||
return ListUtil.sort(new ArrayList<>(infos), Comparator.comparing(fileInfo -> {
|
||||
Long size = fileInfo.getSize();
|
||||
return Long.MAX_VALUE - ObjectUtil.defaultIfNull(size, 0L);
|
||||
}));
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* post api
|
||||
*
|
||||
* @param action 操作
|
||||
* @return HttpReq
|
||||
*/
|
||||
public synchronized HttpRequest postApi(String action) {
|
||||
ThreadUtil.sleep(2000);
|
||||
String openListUploadHost = notificationConfig.getOpenListUploadHost();
|
||||
String openListUploadApiKey = notificationConfig.getOpenListUploadApiKey();
|
||||
return HttpReq.post(openListUploadHost + "/api/" + action)
|
||||
.header(Header.AUTHORIZATION, openListUploadApiKey);
|
||||
}
|
||||
}
|
||||
@@ -480,17 +480,6 @@ public class DownloadService {
|
||||
// 刮削
|
||||
ScrapeService.scrape(ani, false);
|
||||
}
|
||||
|
||||
try {
|
||||
OpenListUtil.upload(torrentsInfo, ani);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
try {
|
||||
OpenListUtil.refresh(ani);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
String text = StrFormatter.format("{} 下载完成", name);
|
||||
if (tags.contains(TorrentsTags.BACK_RSS.getValue())) {
|
||||
text = StrFormatter.format("(备用RSS) {}", text);
|
||||
|
||||
@@ -820,19 +820,16 @@ public class BgmUtil {
|
||||
// 下载位置
|
||||
String downloadPath = DownloadService.getDownloadPath(ani);
|
||||
|
||||
String alistPath = config.getAlistPath();
|
||||
String completedPathTemplate = config.getCompletedPathTemplate();
|
||||
|
||||
if (ova) {
|
||||
// 剧场版默认不开启摸鱼检测
|
||||
ani.setProcrastinating(false);
|
||||
alistPath = config.getAlistOvaPath();
|
||||
}
|
||||
|
||||
return ani
|
||||
// tmdb 标题
|
||||
.setThemoviedbName(themoviedbName)
|
||||
.setAlistPath(alistPath)
|
||||
.setDownloadPath(downloadPath)
|
||||
.setCustomCompletedPathTemplate(completedPathTemplate);
|
||||
}
|
||||
|
||||
@@ -122,7 +122,6 @@ public class ConfigUtil {
|
||||
.setAwaitStalledUP(true)
|
||||
.setDelete(false)
|
||||
.setDeleteStandbyRSSOnly(false)
|
||||
.setDeleteFiles(false)
|
||||
.setOffset(false)
|
||||
.setTitleYear(false)
|
||||
.setAutoDisabled(false)
|
||||
@@ -187,13 +186,6 @@ public class ConfigUtil {
|
||||
.setAutoTrackersUpdate(false)
|
||||
.setTrackersUpdateUrls("https://cf.trackerslist.com/best.txt")
|
||||
.setAutoUpdate(false)
|
||||
.setAlist(false)
|
||||
.setAlistRetry(5)
|
||||
.setAlistTask(true)
|
||||
.setAlistPath("/115/Media/番剧/${title}/Season ${season}")
|
||||
.setAlistOvaPath("/115/Media/剧场版/${title}")
|
||||
.setAlistHost("")
|
||||
.setAlistToken("")
|
||||
.setVersion("")
|
||||
.setBgmImage("large")
|
||||
.setCustomCss("")
|
||||
@@ -451,7 +443,6 @@ public class ConfigUtil {
|
||||
public static void formatUrl(Config config) {
|
||||
List<Func1<Config, String>> func1List = List.of(
|
||||
Config::getDownloadToolHost,
|
||||
Config::getAlistHost,
|
||||
Config::getMikanHost,
|
||||
Config::getTmdbApi,
|
||||
Config::getCustomGithubUrl
|
||||
@@ -476,9 +467,7 @@ public class ConfigUtil {
|
||||
List<Func1<Config, String>> func1List = List.of(
|
||||
Config::getDownloadPathTemplate,
|
||||
Config::getOvaDownloadPathTemplate,
|
||||
Config::getCompletedPathTemplate,
|
||||
Config::getAlistPath,
|
||||
Config::getAlistOvaPath
|
||||
Config::getCompletedPathTemplate
|
||||
);
|
||||
|
||||
DynaBean dynaBean = DynaBean.create(config);
|
||||
|
||||
@@ -35,7 +35,8 @@ public class NotificationUtil {
|
||||
NotificationTypeEnum.TELEGRAM, TelegramNotification.class,
|
||||
NotificationTypeEnum.WEB_HOOK, WebHookNotification.class,
|
||||
NotificationTypeEnum.SHELL, ShellNotification.class,
|
||||
NotificationTypeEnum.FILE_MOVE, FileMoveNotification.class
|
||||
NotificationTypeEnum.FILE_MOVE, FileMoveNotification.class,
|
||||
NotificationTypeEnum.OPEN_LIST_UPLOAD, OpenListUploadNotification.class
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
package ani.rss.util.other;
|
||||
|
||||
import ani.rss.commons.FileUtils;
|
||||
import ani.rss.commons.GsonStatic;
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.entity.Config;
|
||||
import ani.rss.entity.TorrentsInfo;
|
||||
import ani.rss.enums.NotificationStatusEnum;
|
||||
import ani.rss.enums.TorrentsTags;
|
||||
import ani.rss.service.DownloadService;
|
||||
import ani.rss.util.basic.HttpReq;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.text.StrFormatter;
|
||||
import cn.hutool.core.thread.ExecutorBuilder;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.http.Header;
|
||||
import cn.hutool.http.HttpConfig;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* OpenList
|
||||
*/
|
||||
@Slf4j
|
||||
public class OpenListUtil {
|
||||
private static final ExecutorService EXECUTOR = ExecutorBuilder.create()
|
||||
.setCorePoolSize(1)
|
||||
.setMaxPoolSize(1)
|
||||
.setWorkQueue(new LinkedBlockingQueue<>(256))
|
||||
.build();
|
||||
|
||||
/**
|
||||
* 将下载完成的任务上传至 OpenList
|
||||
*
|
||||
* @param torrentsInfo 任务
|
||||
*/
|
||||
public static void upload(TorrentsInfo torrentsInfo, Ani ani) {
|
||||
Boolean upload = ani.getUpload();
|
||||
// 禁止自动上传
|
||||
if (!upload) {
|
||||
return;
|
||||
}
|
||||
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
Boolean alist = config.getAlist();
|
||||
if (!alist) {
|
||||
return;
|
||||
}
|
||||
String alistHost = config.getAlistHost();
|
||||
String alistToken = config.getAlistToken();
|
||||
Integer alistRetry = config.getAlistRetry();
|
||||
|
||||
verify();
|
||||
|
||||
List<String> tags = torrentsInfo.getTags();
|
||||
if (tags.contains(TorrentsTags.OPEN_LIST.getValue())) {
|
||||
return;
|
||||
}
|
||||
if (tags.contains(TorrentsTags.UPLOAD_COMPLETED.getValue())) {
|
||||
return;
|
||||
}
|
||||
|
||||
TorrentUtil.addTags(torrentsInfo, TorrentsTags.OPEN_LIST.getValue());
|
||||
|
||||
String downloadDir = FileUtils.getAbsolutePath(torrentsInfo.getDownloadDir());
|
||||
|
||||
List<String> files = torrentsInfo.getFiles().get();
|
||||
String filePath = getPath(ani);
|
||||
for (String fileName : files) {
|
||||
String finalFilePath = filePath + "/" + fileName;
|
||||
String file = downloadDir + "/" + fileName;
|
||||
if (!FileUtil.exist(file)) {
|
||||
log.error("文件不存在 {}", file);
|
||||
return;
|
||||
}
|
||||
|
||||
EXECUTOR.execute(() -> {
|
||||
log.info("上传 {} ==> {}", file, finalFilePath);
|
||||
|
||||
Boolean alistTask = config.getAlistTask();
|
||||
|
||||
for (int i = 0; i < alistRetry; i++) {
|
||||
try {
|
||||
String url = alistHost;
|
||||
// 使用流式上传
|
||||
url += "/api/fs/put";
|
||||
|
||||
// 50M 上传
|
||||
HttpConfig httpConfig = new HttpConfig()
|
||||
.setBlockSize(1024 * 1024 * 50);
|
||||
|
||||
HttpReq
|
||||
.put(url)
|
||||
.timeout(1000 * 60 * 2)
|
||||
.setConfig(httpConfig)
|
||||
.header(Header.AUTHORIZATION, alistToken)
|
||||
.header("As-Task", Boolean.toString(alistTask))
|
||||
.header("File-Path", URLUtil.encode(finalFilePath))
|
||||
.contentType("application/octet-stream")
|
||||
.body(ResourceUtil.getResourceObj(file))
|
||||
.then(res -> {
|
||||
Assert.isTrue(res.isOk(), "上传失败 {} 状态码:{}", fileName, res.getStatus());
|
||||
JsonObject jsonObject = GsonStatic.fromJson(res.body(), JsonObject.class);
|
||||
int code = jsonObject.get("code").getAsInt();
|
||||
log.info(jsonObject.toString());
|
||||
Assert.isTrue(code == 200, "上传失败 {} 状态码:{}", fileName, code);
|
||||
|
||||
String text = StrFormatter.format("OpenList 上传完成 {}", fileName);
|
||||
if (alistTask) {
|
||||
text = StrFormatter.format("已向 OpenList 添加上传任务 {}", fileName);
|
||||
}
|
||||
log.info(text);
|
||||
NotificationUtil.send(config, ani, text, NotificationStatusEnum.OPEN_LIST_UPLOAD);
|
||||
});
|
||||
TorrentUtil.addTags(torrentsInfo, TorrentsTags.UPLOAD_COMPLETED.getValue());
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
NotificationUtil.send(config, ani, "OpenList 上传失败 " + fileName, NotificationStatusEnum.ERROR);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新 OpenList 路径
|
||||
*/
|
||||
public static void refresh(Ani ani) {
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
Boolean refresh = config.getAlistRefresh();
|
||||
if (!refresh) {
|
||||
return;
|
||||
}
|
||||
String alistHost = config.getAlistHost();
|
||||
String alistToken = config.getAlistToken();
|
||||
Integer alistRetry = config.getAlistRetry();
|
||||
|
||||
verify();
|
||||
|
||||
String finalPath = getPath(ani);
|
||||
EXECUTOR.execute(() -> {
|
||||
Long getAlistRefreshDelay = config.getAlistRefreshDelayed();
|
||||
if (getAlistRefreshDelay > 0) {
|
||||
ThreadUtil.sleep(getAlistRefreshDelay, TimeUnit.SECONDS);
|
||||
}
|
||||
log.info("刷新 OpenList 路径: {}", finalPath);
|
||||
|
||||
for (int i = 0; i < alistRetry; i++) {
|
||||
try {
|
||||
HttpReq
|
||||
.post(alistHost + "/api/fs/mkdir")
|
||||
.header(Header.AUTHORIZATION, alistToken)
|
||||
.body(GsonStatic.toJson(Map.of("path", finalPath)))
|
||||
.then(HttpReq::assertStatus);
|
||||
|
||||
String url = alistHost;
|
||||
url += "/api/fs/list";
|
||||
|
||||
Map<String, Object> resolved = Map.of(
|
||||
"path", finalPath,
|
||||
"refresh", true
|
||||
);
|
||||
HttpReq.post(url)
|
||||
.timeout(1000 * 20)
|
||||
.header(Header.AUTHORIZATION, alistToken)
|
||||
.body(GsonStatic.toJson(resolved))
|
||||
.then(res -> {
|
||||
Assert.isTrue(res.isOk(), "刷新失败 路径: {} 状态码: {}", finalPath, res.getStatus());
|
||||
JsonObject jsonObject = GsonStatic.fromJson(res.body(), JsonObject.class);
|
||||
int code = jsonObject.get("code").getAsInt();
|
||||
Assert.isTrue(code == 200, "刷新失败 路径: {} 状态码: {}", finalPath, code);
|
||||
log.info("已成功刷新 OpenList 路径: {}", finalPath);
|
||||
});
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
ThreadUtil.sleep(3000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验配置
|
||||
*/
|
||||
public static void verify() {
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
String alistHost = config.getAlistHost();
|
||||
String alistPath = FileUtils.getAbsolutePath(config.getAlistPath());
|
||||
String alistToken = config.getAlistToken();
|
||||
|
||||
Assert.notBlank(alistHost, "alistHost 未配置");
|
||||
Assert.notBlank(alistPath, "alistPath 未配置");
|
||||
Assert.notBlank(alistToken, "alistToken 未配置");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上传位置
|
||||
*
|
||||
* @param ani
|
||||
* @return
|
||||
*/
|
||||
private static String getPath(Ani ani) {
|
||||
ani = ObjectUtil.clone(ani)
|
||||
// 因为临时修改下载位置模版以获取对应下载位置, 要关闭自定义下载位置
|
||||
.setCustomDownloadPath(false);
|
||||
Config config = ObjectUtil.clone(ConfigUtil.CONFIG);
|
||||
|
||||
config.setDownloadPathTemplate(config.getAlistPath())
|
||||
.setOvaDownloadPathTemplate(config.getAlistOvaPath());
|
||||
|
||||
Boolean customAlistPath = ani.getCustomAlistPath();
|
||||
String alistPath = ani.getAlistPath();
|
||||
if (customAlistPath) {
|
||||
config.setDownloadPathTemplate(alistPath)
|
||||
.setOvaDownloadPathTemplate(alistPath);
|
||||
}
|
||||
|
||||
String path = DownloadService.getDownloadPath(ani, config);
|
||||
|
||||
path = ReUtil.replaceAll(path, "^[A-z]:", "");
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@@ -240,17 +240,7 @@ public class TorrentUtil {
|
||||
* @param torrentsInfo
|
||||
*/
|
||||
public static synchronized Boolean delete(TorrentsInfo torrentsInfo) {
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
Boolean deleteFiles = config.getDeleteFiles();
|
||||
Boolean alist = config.getAlist();
|
||||
if (!deleteFiles || !alist) {
|
||||
return delete(torrentsInfo, false, false);
|
||||
}
|
||||
// 开启 OpenList 上传 后删除源文件的行为需要等待 OpenList 上传完成
|
||||
if (torrentsInfo.getTags().contains(TorrentsTags.UPLOAD_COMPLETED.getValue())) {
|
||||
return delete(torrentsInfo, false, true);
|
||||
}
|
||||
return false;
|
||||
return delete(torrentsInfo, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -183,6 +183,41 @@ public class NotificationConfig implements Serializable {
|
||||
*/
|
||||
private Boolean fileMoveDeleteOldEpisode;
|
||||
|
||||
/**
|
||||
* OpenList Host
|
||||
*/
|
||||
private String openListUploadHost;
|
||||
|
||||
/**
|
||||
* OpenList ApiKey
|
||||
*/
|
||||
private String openListUploadApiKey;
|
||||
|
||||
/**
|
||||
* OpenList 上传位置
|
||||
*/
|
||||
private String openListUploadPath;
|
||||
|
||||
/**
|
||||
* OpenList OVA/剧场版 上传位置
|
||||
*/
|
||||
private String openListUploadOvaPath;
|
||||
|
||||
/**
|
||||
* 添加为上传任务
|
||||
*/
|
||||
private Boolean openListUploadTask;
|
||||
|
||||
/**
|
||||
* 上传完成后删除本地文件
|
||||
*/
|
||||
private Boolean openListUploadDeleteLocalFile;
|
||||
|
||||
/**
|
||||
* 删除同及文件
|
||||
*/
|
||||
private Boolean openListUploadDeleteOldEpisode;
|
||||
|
||||
/**
|
||||
* 通知 状态
|
||||
*/
|
||||
@@ -250,14 +285,26 @@ public class NotificationConfig implements Serializable {
|
||||
.setEmbyRefreshViewIds(new ArrayList<>())
|
||||
.setEmbyDelayed(0L);
|
||||
|
||||
// SHELL
|
||||
notificationConfig.setShell("")
|
||||
.setAliveLimit(10);
|
||||
|
||||
// FileMove
|
||||
notificationConfig
|
||||
.setFileMoveTarget("/CD2/115/Media/番剧/${title}/Season ${season}")
|
||||
.setFileMoveOvaTarget("/CD2/115/Media/剧场版/${title}")
|
||||
.setFileMoveDeleteOldEpisode(false);
|
||||
|
||||
// OpenList
|
||||
notificationConfig
|
||||
.setOpenListUploadHost("http://127.0.0.1:5244")
|
||||
.setOpenListUploadApiKey("")
|
||||
.setOpenListUploadPath("/115/Media/番剧/${title}/Season ${season}")
|
||||
.setOpenListUploadOvaPath("/115/Media/剧场版/${title}")
|
||||
.setOpenListUploadTask(false)
|
||||
.setOpenListUploadDeleteLocalFile(true)
|
||||
.setOpenListUploadDeleteOldEpisode(false);
|
||||
|
||||
return notificationConfig;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package ani.rss.entity;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class OpenListFileInfo implements Serializable {
|
||||
private String name;
|
||||
private Long size;
|
||||
|
||||
@SerializedName(value = "isDir",alternate = "is_dir")
|
||||
private Boolean isDir;
|
||||
private Date modified;
|
||||
private Date created;
|
||||
private String path;
|
||||
}
|
||||
@@ -20,10 +20,6 @@ public enum NotificationStatusEnum {
|
||||
* 错误
|
||||
*/
|
||||
ERROR("❌", "发生错误"),
|
||||
/**
|
||||
* OpenList 上传通知
|
||||
*/
|
||||
OPEN_LIST_UPLOAD("🙌", "OpenList 上传通知"),
|
||||
/**
|
||||
* 订阅完结
|
||||
*/
|
||||
|
||||
@@ -11,5 +11,6 @@ public enum NotificationTypeEnum {
|
||||
TELEGRAM,
|
||||
WEB_HOOK,
|
||||
SHELL,
|
||||
FILE_MOVE
|
||||
FILE_MOVE,
|
||||
OPEN_LIST_UPLOAD
|
||||
}
|
||||
|
||||
@@ -9,9 +9,7 @@ public enum TorrentsTags {
|
||||
ANI_RSS("ani-rss"),
|
||||
RENAME("RENAME"),
|
||||
BACK_RSS("备用RSS"),
|
||||
DOWNLOAD_COMPLETE("下载完成"),
|
||||
OPEN_LIST("OpenList"),
|
||||
UPLOAD_COMPLETED("上传完成");
|
||||
DOWNLOAD_COMPLETE("下载完成");
|
||||
|
||||
private final String value;
|
||||
}
|
||||
|
||||
@@ -143,15 +143,6 @@
|
||||
<el-text class="mx-1" size="small">
|
||||
<strong>主RSS</strong> 将 <span class="download-danger-text">不会自动删除</span>,仅在其更新后删除对应备用RSS的任务与文件
|
||||
</el-text>
|
||||
<br>
|
||||
<el-checkbox v-model:model-value="props.config.deleteFiles"
|
||||
:disabled="!props.config.delete">
|
||||
<span class="download-danger-text">删除本地文件</span>
|
||||
</el-checkbox>
|
||||
<br>
|
||||
<el-text class="mx-1" size="small">
|
||||
删除本地文件, 仅在同时开启了 <strong>OpenList 上传</strong> 并上传成功后删除
|
||||
</el-text>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="失败重试次数">
|
||||
@@ -199,9 +190,6 @@
|
||||
<el-collapse-item name="qBittorrent" title="qBittorrent 设置">
|
||||
<QBittorrent v-if="activeName.indexOf('qBittorrent') > -1" :config="props.config"/>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="OpenList" title="OpenList 设置">
|
||||
<OpenList v-if="activeName.indexOf('OpenList') > -1" :config="props.config"/>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-form>
|
||||
</template>
|
||||
@@ -212,7 +200,6 @@ import api from "@/js/api.js";
|
||||
import {ElMessage, ElText} from "element-plus";
|
||||
import {Key, User} from "@element-plus/icons-vue";
|
||||
import QBittorrent from "@/config/download/qBittorrent.vue";
|
||||
import OpenList from "@/config/download/OpenList.vue";
|
||||
import PrioKeys from "@/config/PrioKeys.vue";
|
||||
import CustomTags from "@/config/CustomTags.vue";
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ let props = defineProps(['config'])
|
||||
<style>
|
||||
/* 通知组件通用样式 - 非scoped以便子组件使用 */
|
||||
.notification-input-width {
|
||||
width: 160px;
|
||||
width: 160px !important;
|
||||
}
|
||||
|
||||
.notification-flex-between {
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
<el-checkbox label="下载完成" value="DOWNLOAD_END"/>
|
||||
<el-checkbox label="缺集" value="OMIT"/>
|
||||
<el-checkbox label="错误" value="ERROR"/>
|
||||
<el-checkbox label="OpenList 上传通知" value="OPEN_LIST_UPLOAD"/>
|
||||
<el-checkbox label="订阅完结" value="COMPLETED"/>
|
||||
<el-checkbox label="摸鱼检测" value="PROCRASTINATING"/>
|
||||
</el-checkbox-group>
|
||||
@@ -34,9 +33,13 @@
|
||||
<SystemNotification v-model:notification-config="notificationConfig" v-model:config="props.config"/>
|
||||
<ShellNotification v-model:notification-config="notificationConfig" v-model:config="props.config"/>
|
||||
<FileMoveNotification v-model:notification-config="notificationConfig" v-model:config="props.config"/>
|
||||
<OpenListNotification v-model:config="props.config" v-model:notification-config="notificationConfig"/>
|
||||
<el-form-item label="顺序">
|
||||
<div>
|
||||
<el-input-number v-model="notificationConfig['sort']"/>
|
||||
<el-input-number
|
||||
v-model="notificationConfig['sort']"
|
||||
class="notification-input-width"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="重试">
|
||||
@@ -44,12 +47,8 @@
|
||||
:min="0"
|
||||
:max="100"
|
||||
v-model="notificationConfig['retry']"
|
||||
class="notification-config-retry-input"
|
||||
>
|
||||
<template #suffix>
|
||||
<span>次</span>
|
||||
</template>
|
||||
</el-input-number>
|
||||
class="notification-input-width"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="开启">
|
||||
<el-switch v-model="notificationConfig['enable']"/>
|
||||
@@ -78,6 +77,7 @@ import {notificationTypeList} from "@/js/notification-type.js";
|
||||
import {ElMessage} from "element-plus";
|
||||
import api from "@/js/api.js";
|
||||
import FileMoveNotification from "@/config/notification/FileMoveNotification.vue";
|
||||
import OpenListNotification from "@/config/notification/OpenListNotification.vue";
|
||||
|
||||
|
||||
let notificationConfig = ref({
|
||||
@@ -154,10 +154,6 @@ defineExpose({
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.notification-config-retry-input {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.notification-config-footer {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
<template>
|
||||
<el-form-item label="Host">
|
||||
<el-input v-model:model-value="props.config['alistHost']" placeholder="http://127.0.0.1:5244"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="Token">
|
||||
<el-input v-model:model-value="props.config['alistToken']" placeholder="openlist-xxxxxx"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="上传位置">
|
||||
<el-input v-model:model-value="props.config['alistPath']" placeholder="/"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="剧场版上传位置">
|
||||
<el-input v-model:model-value="props.config['alistOvaPath']" placeholder="/"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="失败重试次数">
|
||||
<el-input-number v-model:model-value="props.config['alistRetry']" :max="100" :min="1"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="上传开关">
|
||||
<div class="openlist-container">
|
||||
<div>
|
||||
<el-switch v-model="props.config['alist']"/>
|
||||
</div>
|
||||
<div>
|
||||
<el-checkbox v-model="props.config['alistTask']" :disabled="!props.config['alist']" label="添加为任务"/>
|
||||
</div>
|
||||
<div>
|
||||
<el-text class="mx-1" size="small">
|
||||
自动将下载完成的文件上传至 OpenList
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="刷新开关">
|
||||
<div class="openlist-container">
|
||||
<div>
|
||||
<el-switch v-model:model-value="props.config['alistRefresh']"/>
|
||||
</div>
|
||||
<div>
|
||||
<el-text class="mx-1" size="small">
|
||||
刷新 OpenList 上传路径的文件列表
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="刷新延迟">
|
||||
<el-input-number v-model="props.config['alistRefreshDelayed']" :min="0">
|
||||
<template #suffix>
|
||||
<span>秒</span>
|
||||
</template>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
let props = defineProps(['config'])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.openlist-container {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
36
ani-rss-ui/src/config/notification/OpenListNotification.vue
Normal file
36
ani-rss-ui/src/config/notification/OpenListNotification.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<template v-if="notificationConfig['notificationType'] === 'OPEN_LIST_UPLOAD'">
|
||||
<el-form-item label="Host">
|
||||
<el-input v-model="notificationConfig['openListUploadHost']" placeholder="http://127.0.0.1:5244"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="ApiKey">
|
||||
<el-input v-model="notificationConfig['openListUploadApiKey']" placeholder="openlist-xxxxxx"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="上传位置">
|
||||
<el-input
|
||||
v-model="notificationConfig['openListUploadPath']"
|
||||
placeholder="/115/Media/番剧/${title}/Season ${season}"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="上传位置 (剧场版)">
|
||||
<el-input
|
||||
v-model="notificationConfig['openListUploadOvaPath']"
|
||||
placeholder="/115/Media/剧场版/${title}"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="其他">
|
||||
<div>
|
||||
<el-checkbox v-model="notificationConfig['openListUploadDeleteLocalFile']" label="上传完成后删除本地文件"/>
|
||||
<el-checkbox v-model="notificationConfig['openListUploadTask']" label="添加为上传任务"/>
|
||||
<br/>
|
||||
<el-checkbox v-model="notificationConfig['openListUploadDeleteOldEpisode']" label="删除旧的同集文件"/>
|
||||
<br/>
|
||||
<el-text class="mx-1" size="small">
|
||||
自动时将目标位置存在的同集文件删除, 可达到洗版的效果
|
||||
</el-text>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
let props = defineProps(['notificationConfig', 'config'])
|
||||
</script>
|
||||
@@ -1,27 +1,29 @@
|
||||
<template>
|
||||
<template v-if="notificationConfig['notificationType'] === 'TELEGRAM'">
|
||||
<el-form-item label="通知模版">
|
||||
<el-input v-model:model-value="props.notificationConfig['notificationTemplate']" :autosize="{ minRows: 2}"
|
||||
<el-input v-model="notificationConfig['notificationTemplate']" :autosize="{ minRows: 2}"
|
||||
placeholder="${notification}" type="textarea"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="Api Host">
|
||||
<el-input v-model:model-value="props.notificationConfig['telegramApiHost']"
|
||||
<el-input v-model="notificationConfig['telegramApiHost']"
|
||||
placeholder="https://api.telegram.org"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="Token">
|
||||
<el-input v-model:model-value="props.notificationConfig['telegramBotToken']"
|
||||
<el-input v-model="notificationConfig['telegramBotToken']"
|
||||
placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="ChatId">
|
||||
<div>
|
||||
<div class="auto-flex notification-flex-between">
|
||||
<div class="notification-margin-top-right">
|
||||
<el-input v-model:model-value="props.notificationConfig['telegramChatId']"
|
||||
<el-input v-model="notificationConfig['telegramChatId']"
|
||||
placeholder="123456789"/>
|
||||
</div>
|
||||
<div class="flex notification-margin-top-center">
|
||||
<div>
|
||||
<el-select v-model:model-value="chatId" @change="chatIdChange" class="notification-input-width">
|
||||
<el-select v-model="chatId"
|
||||
class="notification-input-width"
|
||||
@change="chatIdChange">
|
||||
<el-option v-for="item in Object.keys(chatIdMap)"
|
||||
:key="item"
|
||||
:label="item"
|
||||
@@ -36,15 +38,15 @@
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="话题ID">
|
||||
<el-input-number v-model="props.notificationConfig['telegramTopicId']"
|
||||
<el-input-number v-model="notificationConfig['telegramTopicId']"
|
||||
:min="-1" class="notification-input-width"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="图片">
|
||||
<el-switch v-model:model-value="props.notificationConfig['telegramImage']"/>
|
||||
<el-switch v-model="notificationConfig['telegramImage']"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="格式">
|
||||
<div class="notification-input-width">
|
||||
<el-select v-model:model-value="props.notificationConfig['telegramFormat']" placeholder="None">
|
||||
<el-select v-model="notificationConfig['telegramFormat']" placeholder="None">
|
||||
<el-option label="None" value=""/>
|
||||
<el-option label="Markdown" value="Markdown"/>
|
||||
<el-option label="HTML" value="HTML"/>
|
||||
|
||||
@@ -147,18 +147,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="自定义上传">
|
||||
<div style="width: 100%;">
|
||||
<div>
|
||||
<el-switch v-model="props.ani.customAlistPath"/>
|
||||
</div>
|
||||
<div>
|
||||
<el-input type="textarea" style="width: 100%" :disabled="!props.ani.customAlistPath"
|
||||
:autosize="{ minRows: 2}"
|
||||
v-model:model-value="props.ani.alistPath"/>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="自定义完结迁移">
|
||||
<div style="width: 100%;">
|
||||
<div>
|
||||
|
||||
@@ -30,6 +30,10 @@ export let notificationTypeList = [
|
||||
{
|
||||
name: 'FILE_MOVE',
|
||||
label: '文件移动'
|
||||
},
|
||||
{
|
||||
name: 'OPEN_LIST_UPLOAD',
|
||||
label: 'OpenList上传'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user