mirror of
https://github.com/wushuo894/ani-rss.git
synced 2026-03-13 09:20:23 +00:00
3.0
This commit is contained in:
4
.github/workflows/deploy-test.yml
vendored
4
.github/workflows/deploy-test.yml
vendored
@@ -16,13 +16,13 @@ jobs:
|
||||
- name: Build with Project
|
||||
run: |
|
||||
bash ./package.sh
|
||||
version=$(cat pom.xml | grep -oPm1 '(?<=<version>).*?(?=</version>)')
|
||||
version=$(cat ani-rss-application/pom.xml | grep -oPm1 '(?<=<version>).*?(?=</version>)')
|
||||
echo "version=v$version" >> $GITHUB_ENV
|
||||
- name: Upload to Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Artifacts
|
||||
path: ./ani-rss-application/target/ani-rss-jar-with-dependencies.jar
|
||||
path: ./ani-rss-application/target/ani-rss.jar
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
|
||||
13
.github/workflows/deploy.yml
vendored
13
.github/workflows/deploy.yml
vendored
@@ -16,10 +16,9 @@ jobs:
|
||||
- name: Build with Project
|
||||
run: |
|
||||
bash ./package.sh
|
||||
bash ./package-win.sh
|
||||
|
||||
time=$(date +%s%3N)
|
||||
version=$(cat pom.xml | grep -oPm1 '(?<=<version>).*?(?=</version>)')
|
||||
version=$(cat ani-rss-application/pom.xml | grep -oPm1 '(?<=<version>).*?(?=</version>)')
|
||||
echo "{\"time\":$time,\"version\":\"$version\"}" > info.json
|
||||
jq --arg content "$(cat UPDATE.md)" '. += {markdown: $content}' info.json > temp.json && mv temp.json info.json
|
||||
|
||||
@@ -35,12 +34,10 @@ jobs:
|
||||
append_body: false
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: |
|
||||
./ani-rss-application/target/ani-rss-jar-with-dependencies.jar
|
||||
./ani-rss-application/target/ani-rss-jar-with-dependencies.jar.md5
|
||||
./ani-rss-application/target/ani-rss-launcher.exe
|
||||
./ani-rss-application/target/ani-rss-launcher.exe.md5
|
||||
./ani-rss-application/target/ani-rss.win.x86_64.zip
|
||||
./ani-rss-application/target/ani-rss.win.x86_64.zip.md5
|
||||
./ani-rss-application/target/ani-rss.jar
|
||||
./ani-rss-application/target/ani-rss.jar.md5
|
||||
./ani-rss-application/target/ani-rss.exe
|
||||
./ani-rss-application/target/ani-rss.exe.md5
|
||||
./info.json
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
56
.gitignore
vendored
56
.gitignore
vendored
@@ -1,57 +1,7 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
|
||||
config.json
|
||||
ani.json
|
||||
files
|
||||
target
|
||||
logs
|
||||
*.log
|
||||
/config
|
||||
.git
|
||||
|
||||
java-17-openjdk-17.0.3.0.6-1.jre.win.x86_64.zip
|
||||
*.log
|
||||
ani-rss-update.exe
|
||||
/info.json
|
||||
build_info
|
||||
*.versionsBackup
|
||||
jre.zip
|
||||
build_info
|
||||
33
UPDATE.md
33
UPDATE.md
@@ -1,4 +1,35 @@
|
||||
- refactor: 优化通知测试
|
||||
## 增加Swagger接口文档
|
||||
|
||||
设置环境变量 `SWAGGER_ENABLED=true`
|
||||
|
||||
通过链接访问 http://127.0.0.1:7789/swagger-ui/index.html
|
||||
|
||||
## 破坏性改动
|
||||
|
||||
### 环境变量与参数
|
||||
|
||||
| 旧 | 新 |
|
||||
|----------|--------------------|
|
||||
| `--port` | `--server.port` |
|
||||
| `--host` | `--server.address` |
|
||||
| `PORT` | `SERVER_PORT` |
|
||||
| `HOST` | `SERVER_ADDRESS` |
|
||||
|
||||
### emby点格子
|
||||
|
||||
旧: `http://[IP]:7789/api/web_hook?s=[ApiKey]`
|
||||
|
||||
新: `http://[IP]:7789/api/embyWebhook?s=[ApiKey]`
|
||||
|
||||
## Windows端
|
||||
|
||||
不再提供内置jdk的压缩包, 需自行安装
|
||||
|
||||
## 此次更新方式
|
||||
|
||||
Docker需要重新部署
|
||||
|
||||
Windows需要手动重新下载
|
||||
|
||||
[请不要将本项目在国内宣传](https://github.com/wushuo894/ani-rss/discussions/504)
|
||||
|
||||
|
||||
33
ani-rss-application/.gitignore
vendored
Normal file
33
ani-rss-application/.gitignore
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
HELP.md
|
||||
target/
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
@@ -1,12 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>ani.rss</groupId>
|
||||
<artifactId>ani-rss</artifactId>
|
||||
<version>2.5.10</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ani-rss-application</artifactId>
|
||||
@@ -17,14 +16,88 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>central</id>
|
||||
<url>https://repo.maven.apache.org/maven2</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>ebml-reader</id>
|
||||
<url>https://raw.github.com/wushuo894/EBMLReader/mvn-repo</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>tmdb-api</id>
|
||||
<url>https://raw.github.com/wushuo894/tmdb-api/mvn-repo</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
<version>4.0.0-M2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse</groupId>
|
||||
<artifactId>bittorrent</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.mail</groupId>
|
||||
<artifactId>jakarta.mail</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.biezhi</groupId>
|
||||
<artifactId>TinyPinyin</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ebml.reader</groupId>
|
||||
<artifactId>ebml-reader</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>wushuo.tmdb.api</groupId>
|
||||
<artifactId>tmdb-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ani.rss</groupId>
|
||||
<artifactId>ani-rss-ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ani.rss</groupId>
|
||||
<artifactId>ani-rss-web</artifactId>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webmvc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webmvc-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -32,28 +105,8 @@
|
||||
<finalName>ani-rss</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<configuration>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
</descriptorRefs>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addClasspath>true</addClasspath>
|
||||
<mainClass>ani.rss.ApplicationMain</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.akathist.maven.plugins.launch4j</groupId>
|
||||
@@ -67,8 +120,8 @@
|
||||
</goals>
|
||||
<configuration>
|
||||
<headerType>gui</headerType>
|
||||
<outfile>target/ani-rss-launcher.exe</outfile>
|
||||
<jar>target/ani-rss-jar-with-dependencies.jar</jar>
|
||||
<outfile>target/ani-rss.exe</outfile>
|
||||
<jar>target/ani-rss.jar</jar>
|
||||
<errTitle>Java environment is required!</errTitle>
|
||||
<cmdLine>--gui</cmdLine>
|
||||
<chdir>.</chdir>
|
||||
@@ -79,11 +132,11 @@
|
||||
<restartOnCrash>false</restartOnCrash>
|
||||
<icon>${project.parent.basedir}/ani-rss-ui/public/favicon.ico</icon>
|
||||
<singleInstance>
|
||||
<mutexName>ani-rss-launcher (${project.version})</mutexName>
|
||||
<mutexName>ani-rss (${project.version})</mutexName>
|
||||
<windowTitle>ani-rss (${project.version})</windowTitle>
|
||||
</singleInstance>
|
||||
<jre>
|
||||
<path>jre/bin;%JAVA_HOME%/bin;%PATH%</path>
|
||||
<path>%JAVA_HOME%/bin;%PATH%</path>
|
||||
<minVersion>17</minVersion>
|
||||
<opts>
|
||||
<opt>-Xms60m -Xmx1g -Xss256k</opt>
|
||||
@@ -99,7 +152,7 @@
|
||||
<copyright>Copyright (C) 2024-2025</copyright>
|
||||
<productName>${project.artifactId}</productName>
|
||||
<internalName>${project.artifactId}</internalName>
|
||||
<originalFilename>ani-rss-launcher.exe</originalFilename>
|
||||
<originalFilename>ani-rss.exe</originalFilename>
|
||||
<language>SIMPLIFIED_CHINESE</language>
|
||||
</versionInfo>
|
||||
</configuration>
|
||||
@@ -144,4 +197,5 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package ani.rss;
|
||||
|
||||
import ani.rss.entity.Global;
|
||||
import ani.rss.util.other.MenuUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
import java.security.Security;
|
||||
import java.util.List;
|
||||
|
||||
@EnableScheduling
|
||||
@SpringBootApplication
|
||||
public class AniRssApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Global.ARGS = List.of(ObjectUtil.defaultIfNull(args, new String[]{}));
|
||||
loadProperty();
|
||||
MenuUtil.start();
|
||||
SpringApplication.run(AniRssApplication.class, args);
|
||||
}
|
||||
|
||||
public static void loadProperty() {
|
||||
// 启用Basic认证
|
||||
System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
|
||||
// DNS解析成功过期时间
|
||||
Security.setProperty("networkaddress.cache.ttl", "30");
|
||||
// DNS解析失败过期时间
|
||||
Security.setProperty("networkaddress.cache.negative.ttl", "5");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package ani.rss;
|
||||
|
||||
import ani.rss.commons.ExceptionUtils;
|
||||
import ani.rss.commons.MavenUtils;
|
||||
import ani.rss.entity.Global;
|
||||
import ani.rss.other.Cron;
|
||||
import ani.rss.service.TaskService;
|
||||
import ani.rss.util.other.AniUtil;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import ani.rss.util.other.MenuUtil;
|
||||
import ani.rss.web.util.ServerUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RuntimeUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.security.Security;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class ApplicationMain {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Global.ARGS = List.of(ObjectUtil.defaultIfNull(args, new String[]{}));
|
||||
loadProperty();
|
||||
try {
|
||||
ConfigUtil.load();
|
||||
ConfigUtil.backup();
|
||||
MenuUtil.start();
|
||||
ServerUtil.start();
|
||||
|
||||
AniUtil.load();
|
||||
TaskService.start();
|
||||
String version = MavenUtils.getVersion();
|
||||
log.info("version {}", version);
|
||||
|
||||
Cron.start();
|
||||
} catch (Exception e) {
|
||||
String message = ExceptionUtils.getMessage(e);
|
||||
log.error(message, e);
|
||||
System.exit(1);
|
||||
}
|
||||
RuntimeUtil.addShutdownHook(() -> log.info("程序退出..."));
|
||||
}
|
||||
|
||||
public static void loadProperty() {
|
||||
// 启用Basic认证
|
||||
System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
|
||||
// DNS解析成功过期时间
|
||||
Security.setProperty("networkaddress.cache.ttl", "30");
|
||||
// DNS解析失败过期时间
|
||||
Security.setProperty("networkaddress.cache.negative.ttl", "5");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.util.other.UpdateUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 关于
|
||||
*/
|
||||
@Auth
|
||||
@Slf4j
|
||||
@Path("/about")
|
||||
public class AboutAction implements BaseAction {
|
||||
|
||||
@Override
|
||||
public void doAction(HttpServerRequest req, HttpServerResponse res) {
|
||||
resultSuccess(UpdateUtil.about());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.entity.Config;
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.entity.TryOut;
|
||||
import ani.rss.util.basic.HttpReq;
|
||||
import ani.rss.util.other.AfdianUtil;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import ani.rss.web.auth.enums.AuthType;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 爱发电
|
||||
*/
|
||||
@Auth(type = {
|
||||
AuthType.IP_WHITE_LIST,
|
||||
AuthType.HEADER,
|
||||
AuthType.FORM
|
||||
})
|
||||
@Slf4j
|
||||
@Path("/afdian")
|
||||
public class AfdianAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
String type = request.getParam("type");
|
||||
|
||||
if (type.equals("verifyNo")) {
|
||||
Config config = getBody(Config.class);
|
||||
String outTradeNo = config.getOutTradeNo();
|
||||
Result<Void> result = AfdianUtil.verifyNo(outTradeNo);
|
||||
result(result);
|
||||
|
||||
int code = result.getCode();
|
||||
if (code == 200) {
|
||||
Long time = DateUtil.offsetYear(new Date(), 999).getTime();
|
||||
ConfigUtil.CONFIG.setOutTradeNo(outTradeNo)
|
||||
.setExpirationTime(time)
|
||||
.setTryOut(false);
|
||||
ConfigUtil.sync();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type.equals("tryOut")) {
|
||||
Config config = getBody(Config.class);
|
||||
if (AfdianUtil.verifyExpirationTime()) {
|
||||
resultError(result ->
|
||||
result
|
||||
.setMessage("还在试用中!")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
String githubToken = config.getGithubToken();
|
||||
Assert.notBlank(githubToken, "GithubToken 不能为空");
|
||||
|
||||
Boolean ok = HttpReq.get("https://api.github.com/user/starred/wushuo894/ani-rss")
|
||||
.header("Authorization", "Bearer " + githubToken)
|
||||
.thenFunction(HttpResponse::isOk);
|
||||
|
||||
Assert.isTrue(ok, "未点击star");
|
||||
|
||||
TryOut tryOut = AfdianUtil.getTryOut();
|
||||
|
||||
Boolean enable = tryOut.getEnable();
|
||||
Boolean renewal = tryOut.getRenewal();
|
||||
Integer day = tryOut.getDay();
|
||||
String message = tryOut.getMessage();
|
||||
|
||||
Assert.isTrue(enable, message);
|
||||
|
||||
if (config.getTryOut()) {
|
||||
// 已经有过试用
|
||||
Assert.isTrue(renewal, message);
|
||||
}
|
||||
|
||||
long time = DateUtil.offsetDay(new Date(), day).getTime();
|
||||
ConfigUtil.CONFIG
|
||||
.setGithubToken(githubToken)
|
||||
.setExpirationTime(time)
|
||||
.setTryOut(true);
|
||||
ConfigUtil.sync();
|
||||
resultSuccess(result ->
|
||||
result
|
||||
.setMessage(message)
|
||||
.setData(time)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.entity.BgmInfo;
|
||||
import ani.rss.util.other.AniUtil;
|
||||
import ani.rss.util.other.BgmUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.lang.Opt;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import wushuo.tmdb.api.entity.Tmdb;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* bgm
|
||||
*/
|
||||
@Auth
|
||||
@Slf4j
|
||||
@Path("/bgm")
|
||||
public class BgmAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
String type = request.getParam("type");
|
||||
switch (type) {
|
||||
case "search" -> {
|
||||
// 搜索
|
||||
String name = request.getParam("name");
|
||||
resultSuccess(BgmUtil.search(name));
|
||||
}
|
||||
case "getAniBySubjectId" -> {
|
||||
// 将指定id的BGM番剧转换为订阅
|
||||
String id = request.getParam("id");
|
||||
BgmInfo bgmInfo = BgmUtil.getBgmInfo(id, true);
|
||||
Ani ani = BgmUtil.toAni(bgmInfo, AniUtil.createAni());
|
||||
ani
|
||||
.setCustomDownloadPath(true);
|
||||
resultSuccess(ani);
|
||||
}
|
||||
case "getTitle" -> {
|
||||
// 获取BGM标题
|
||||
Ani ani = getBody(Ani.class);
|
||||
Tmdb tmdb = ani.getTmdb();
|
||||
BgmInfo bgmInfo = BgmUtil.getBgmInfo(ani);
|
||||
resultSuccess(BgmUtil.getFinalName(bgmInfo, tmdb));
|
||||
}
|
||||
case "rate" -> {
|
||||
// 评分
|
||||
Ani ani = getBody(Ani.class);
|
||||
String subjectId = BgmUtil.getSubjectId(ani);
|
||||
Integer score = Opt.ofNullable(ani.getScore())
|
||||
.map(Double::intValue)
|
||||
.orElse(null);
|
||||
resultSuccess(result -> {
|
||||
result.setData(BgmUtil.rate(subjectId, score))
|
||||
.setMessage("保存评分成功");
|
||||
if (Objects.isNull(score)) {
|
||||
result.setMessage("");
|
||||
}
|
||||
});
|
||||
}
|
||||
case "me" -> {
|
||||
// 获取当前BGM账号信息
|
||||
Long expiresDays = BgmUtil.getExpiresDays();
|
||||
JsonObject me = BgmUtil.me();
|
||||
me.addProperty("expires_days", expiresDays);
|
||||
resultSuccess(me);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.commons.GsonStatic;
|
||||
import ani.rss.entity.Config;
|
||||
import ani.rss.util.basic.HttpReq;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* BGM 授权回调
|
||||
*/
|
||||
@Auth
|
||||
@Path("/bgm/oauth/callback")
|
||||
public class BgmCallbackAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
String code = request.getParam("code");
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
String bgmAppID = config.getBgmAppID();
|
||||
String bgmAppSecret = config.getBgmAppSecret();
|
||||
String bgmRedirectUri = config.getBgmRedirectUri();
|
||||
|
||||
Map<String, String> map = Map.of(
|
||||
"grant_type", "authorization_code",
|
||||
"client_id", bgmAppID,
|
||||
"client_secret", bgmAppSecret,
|
||||
"code", code,
|
||||
"redirect_uri", bgmRedirectUri
|
||||
);
|
||||
|
||||
HttpReq.post("https://bgm.tv/oauth/access_token")
|
||||
.body(GsonStatic.toJson(map))
|
||||
.then(res -> {
|
||||
HttpReq.assertStatus(res);
|
||||
JsonObject jsonObject = GsonStatic.fromJson(res.body(), JsonObject.class);
|
||||
String accessToken = jsonObject.get("access_token").getAsString();
|
||||
String refreshToken = jsonObject.get("refresh_token").getAsString();
|
||||
config.setBgmToken(accessToken)
|
||||
.setBgmRefreshToken(refreshToken);
|
||||
});
|
||||
ConfigUtil.sync();
|
||||
|
||||
resultSuccessMsg("授权成功, 现在你可以关闭此窗口");
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.commons.FileUtils;
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.service.ClearService;
|
||||
import ani.rss.util.other.AniUtil;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 缓存清理
|
||||
*/
|
||||
@Slf4j
|
||||
@Auth
|
||||
@Path("/clearCache")
|
||||
public class ClearCacheAction implements BaseAction {
|
||||
|
||||
@Override
|
||||
public synchronized void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
File configDir = ConfigUtil.getConfigDir();
|
||||
String configDirStr = FileUtils.getAbsolutePath(configDir);
|
||||
|
||||
Set<String> covers = AniUtil.ANI_LIST
|
||||
.stream()
|
||||
.map(Ani::getCover)
|
||||
.map(s -> FileUtils.getAbsolutePath(new File(configDirStr + "/files/" + s)))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
FileUtil.mkdir(configDirStr + "/files");
|
||||
FileUtil.mkdir(configDirStr + "/img");
|
||||
|
||||
Set<File> files = FileUtil.loopFiles(configDirStr + "/files")
|
||||
.stream()
|
||||
.filter(file -> {
|
||||
String fileName = FileUtils.getAbsolutePath(file);
|
||||
return !covers.contains(fileName);
|
||||
}).collect(Collectors.toSet());
|
||||
long filesSize = files.stream()
|
||||
.mapToLong(File::length)
|
||||
.sum();
|
||||
long imgSize = FileUtil.size(new File(configDirStr + "/img"));
|
||||
|
||||
long sumSize = filesSize + imgSize;
|
||||
|
||||
if (sumSize < 1) {
|
||||
resultSuccessMsg("清理完成, 共清理{}MB", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
for (File file : files) {
|
||||
FileUtil.del(file);
|
||||
ClearService.clearParentFile(file);
|
||||
}
|
||||
|
||||
FileUtil.del(configDirStr + "/img");
|
||||
|
||||
resultSuccessMsg("清理完成, 共清理{}MB", NumberUtil.decimalFormat("0.00", sumSize / 1024.0 / 1024.0));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.commons.MavenUtils;
|
||||
import ani.rss.entity.Config;
|
||||
import ani.rss.entity.Login;
|
||||
import ani.rss.service.TaskService;
|
||||
import ani.rss.util.other.AfdianUtil;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import ani.rss.util.other.TorrentUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.bean.copier.CopyOptions;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 设置
|
||||
*/
|
||||
@Auth
|
||||
@Path("/config")
|
||||
public class ConfigAction implements BaseAction {
|
||||
|
||||
@Override
|
||||
public void doAction(HttpServerRequest req, HttpServerResponse res) throws IOException {
|
||||
String method = req.getMethod();
|
||||
if (method.equals("GET")) {
|
||||
String version = MavenUtils.getVersion();
|
||||
String buildInfo = buildInfo();
|
||||
Config config = ObjectUtil.clone(ConfigUtil.CONFIG);
|
||||
config.getLogin().setPassword("");
|
||||
config.setVersion(version)
|
||||
.setBuildInfo(buildInfo)
|
||||
.setVerifyExpirationTime(AfdianUtil.verifyExpirationTime());
|
||||
resultSuccess(config);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!method.equals("POST")) {
|
||||
return;
|
||||
}
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
Login login = config.getLogin();
|
||||
String username = login.getUsername();
|
||||
String password = login.getPassword();
|
||||
Integer renameSleepSeconds = config.getRenameSleepSeconds();
|
||||
Integer sleep = config.getRssSleepMinutes();
|
||||
String download = config.getDownloadToolType();
|
||||
|
||||
Config newConfig = getBody(Config.class);
|
||||
newConfig.setExpirationTime(null)
|
||||
.setOutTradeNo(null)
|
||||
.setTryOut(null);
|
||||
|
||||
CopyOptions copyOptions = CopyOptions
|
||||
.create()
|
||||
.setIgnoreNullValue(true);
|
||||
|
||||
BeanUtil.copyProperties(
|
||||
newConfig,
|
||||
config,
|
||||
copyOptions
|
||||
);
|
||||
|
||||
String loginPassword = config.getLogin().getPassword();
|
||||
// 密码未发生修改
|
||||
if (StrUtil.isBlank(loginPassword)) {
|
||||
config.getLogin().setPassword(password);
|
||||
}
|
||||
String loginUsername = config.getLogin().getUsername();
|
||||
if (StrUtil.isBlank(loginUsername)) {
|
||||
config.getLogin().setUsername(username);
|
||||
}
|
||||
|
||||
Boolean proxy = config.getProxy();
|
||||
if (proxy) {
|
||||
String proxyHost = config.getProxyHost();
|
||||
Integer proxyPort = config.getProxyPort();
|
||||
if (StrUtil.isBlank(proxyHost) || Objects.isNull(proxyPort)) {
|
||||
resultErrorMsg("代理参数不完整");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ConfigUtil.sync();
|
||||
Integer newRenameSleepSeconds = config.getRenameSleepSeconds();
|
||||
Integer newSleep = config.getRssSleepMinutes();
|
||||
|
||||
// 时间间隔发生改变,重启任务
|
||||
if (
|
||||
!Objects.equals(newSleep, sleep) ||
|
||||
!Objects.equals(newRenameSleepSeconds, renameSleepSeconds)
|
||||
) {
|
||||
TaskService.restart();
|
||||
}
|
||||
// 下载工具发生改变
|
||||
if (!download.equals(config.getDownloadToolType())) {
|
||||
TorrentUtil.load();
|
||||
}
|
||||
|
||||
resultSuccessMsg("修改成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建信息
|
||||
*/
|
||||
public String buildInfo() {
|
||||
String buildInfo = "";
|
||||
try {
|
||||
buildInfo = ResourceUtil.readUtf8Str("build_info");
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return buildInfo;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.util.other.AniUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 刷新封面
|
||||
*/
|
||||
@Slf4j
|
||||
@Auth
|
||||
@Path("/cover")
|
||||
public class CoverAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
Ani ani = getBody(Ani.class);
|
||||
String s = AniUtil.saveJpg(ani.getImage(), true);
|
||||
resultSuccess(s);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.Header;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 自定义css
|
||||
*/
|
||||
@Slf4j
|
||||
@Auth(value = false)
|
||||
@Path("/custom.css")
|
||||
public class CustomCssAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
response.setHeader(Header.CACHE_CONTROL, "no-store, no-cache, must-revalidate, max-age=0");
|
||||
response.setHeader(Header.PRAGMA, "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
|
||||
String customCss = ConfigUtil.CONFIG.getCustomCss();
|
||||
customCss = StrUtil.blankToDefault(customCss, "/* empty css */");
|
||||
String contentType = "text/css";
|
||||
response.write(customCss, contentType);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.Header;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 自定义js
|
||||
*/
|
||||
@Slf4j
|
||||
@Auth(value = false)
|
||||
@Path("/custom.js")
|
||||
public class CustomJsAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
response.setHeader(Header.CACHE_CONTROL, "no-store, no-cache, must-revalidate, max-age=0");
|
||||
response.setHeader(Header.PRAGMA, "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
|
||||
String customJs = ConfigUtil.CONFIG.getCustomJs();
|
||||
customJs = StrUtil.blankToDefault(customJs, "// empty js");
|
||||
String contentType = "application/javascript; charset=utf-8";
|
||||
response.write(customJs, contentType);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.text.StrFormatter;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.ZipUtil;
|
||||
import cn.hutool.http.Header;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.Cleanup;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 下载日志
|
||||
*/
|
||||
@Auth
|
||||
@Path("/downloadLogs")
|
||||
public class DownloadLogsAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
File configDir = ConfigUtil.getConfigDir();
|
||||
String logsPath = configDir + "/logs";
|
||||
|
||||
String filename = "logs.zip";
|
||||
|
||||
String contentType = getContentType(filename);
|
||||
|
||||
response.setContentType(contentType);
|
||||
response.setHeader(Header.CONTENT_DISPOSITION, StrFormatter.format("inline; filename=\"{}\"", filename));
|
||||
|
||||
@Cleanup
|
||||
OutputStream outputStream = response.getOut();
|
||||
|
||||
ZipUtil.zip(outputStream, StandardCharsets.UTF_8, false, name -> {
|
||||
if (FileUtil.isDirectory(name)) {
|
||||
return true;
|
||||
}
|
||||
String extName = FileUtil.extName(name);
|
||||
if (StrUtil.isBlank(extName)) {
|
||||
return false;
|
||||
}
|
||||
return extName.equals("log");
|
||||
}, new File(logsPath));
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.service.DownloadService;
|
||||
import ani.rss.util.other.AniUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 获取下载位置
|
||||
*/
|
||||
@Auth
|
||||
@Path("/downloadPath")
|
||||
public class DownloadPathAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
Ani ani = getBody(Ani.class);
|
||||
String downloadPath = DownloadService.getDownloadPath(ani);
|
||||
|
||||
boolean change = false;
|
||||
Optional<Ani> first = AniUtil.ANI_LIST.stream()
|
||||
.filter(it -> it.getId().equals(ani.getId()))
|
||||
.findFirst();
|
||||
if (first.isPresent()) {
|
||||
Ani oldAni = ObjectUtil.clone(first.get());
|
||||
// 只在名称改变时移动
|
||||
oldAni.setSeason(ani.getSeason());
|
||||
String oldDownloadPath = DownloadService.getDownloadPath(oldAni);
|
||||
change = !downloadPath.equals(oldDownloadPath);
|
||||
}
|
||||
|
||||
resultSuccess(Map.of(
|
||||
"change", change,
|
||||
"downloadPath", downloadPath
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.download.BaseDownload;
|
||||
import ani.rss.entity.Config;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 测试下载工具
|
||||
*/
|
||||
@Auth
|
||||
@Path("/downloadLoginTest")
|
||||
public class DownloadTestAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
Config config = getBody(Config.class);
|
||||
ConfigUtil.format(config);
|
||||
String download = config.getDownloadToolType();
|
||||
Class<Object> loadClass = ClassUtil.loadClass("ani.rss.download." + download);
|
||||
BaseDownload baseDownload = (BaseDownload) ReflectUtil.newInstance(loadClass);
|
||||
Boolean login = baseDownload.login(true, config);
|
||||
if (login) {
|
||||
resultSuccessMsg("登录成功");
|
||||
return;
|
||||
}
|
||||
resultErrorMsg("登录失败");
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.entity.EmbyViews;
|
||||
import ani.rss.entity.NotificationConfig;
|
||||
import ani.rss.util.other.EmbyUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Emby
|
||||
*/
|
||||
@Auth
|
||||
@Path("/emby")
|
||||
public class EmbyAction implements BaseAction {
|
||||
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
NotificationConfig notificationConfig = getBody(NotificationConfig.class);
|
||||
String type = request.getParam("type");
|
||||
|
||||
if (type.equals("getViews")) {
|
||||
List<EmbyViews> views = EmbyUtil.getViews(notificationConfig);
|
||||
resultSuccess(views);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type.equals("refresh")) {
|
||||
EmbyUtil.refresh(notificationConfig);
|
||||
resultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.commons.ExceptionUtils;
|
||||
import ani.rss.util.basic.HttpReq;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import ani.rss.web.util.ServerUtil;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.text.StrFormatter;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.http.Header;
|
||||
import cn.hutool.http.HttpConnection;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.Cleanup;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 文件
|
||||
*/
|
||||
@Slf4j
|
||||
@Auth
|
||||
@Path("/file")
|
||||
public class FileAction implements BaseAction {
|
||||
|
||||
public void getImg(String url, Consumer<InputStream> consumer) {
|
||||
URI host = URLUtil.getHost(URLUtil.url(url));
|
||||
HttpReq.get(url)
|
||||
.then(res -> {
|
||||
HttpConnection httpConnection = (HttpConnection) ReflectUtil.getFieldValue(res, "httpConnection");
|
||||
URI host1 = URLUtil.getHost(httpConnection.getUrl());
|
||||
if (host.toString().equals(host1.toString())) {
|
||||
try {
|
||||
@Cleanup
|
||||
InputStream inputStream = res.bodyStream();
|
||||
consumer.accept(inputStream);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
String newUrl = url.replace(host.toString(), host1.toString());
|
||||
getImg(newUrl, consumer);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理图片文件
|
||||
*
|
||||
* @param img 图片名
|
||||
*/
|
||||
public void doImg(String img) {
|
||||
HttpServerResponse response = ServerUtil.RESPONSE.get();
|
||||
|
||||
// 30 天
|
||||
long maxAge = 86400 * 30;
|
||||
|
||||
response.setHeader(Header.CACHE_CONTROL, "private, max-age=" + maxAge);
|
||||
|
||||
String contentType = getContentType(URLUtil.getPath(img));
|
||||
|
||||
File configDir = ConfigUtil.getConfigDir();
|
||||
|
||||
File file = new File(URLUtil.getPath(img));
|
||||
configDir = new File(configDir + "/img/" + file.getParentFile().getName());
|
||||
FileUtil.mkdir(configDir);
|
||||
|
||||
File imgFile = new File(configDir, file.getName());
|
||||
if (imgFile.exists()) {
|
||||
try {
|
||||
@Cleanup
|
||||
InputStream inputStream = FileUtil.getInputStream(imgFile);
|
||||
response.write(inputStream, (int) imgFile.length(), contentType);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
getImg(img, is -> {
|
||||
try {
|
||||
FileUtil.writeFromStream(is, imgFile, true);
|
||||
@Cleanup
|
||||
BufferedInputStream inputStream = FileUtil.getInputStream(imgFile);
|
||||
response.write(inputStream, (int) imgFile.length(), contentType);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理文件
|
||||
*
|
||||
* @param filename 文件名
|
||||
*/
|
||||
private void doFile(String filename) {
|
||||
HttpServerRequest request = ServerUtil.REQUEST.get();
|
||||
HttpServerResponse response = ServerUtil.RESPONSE.get();
|
||||
|
||||
File file = new File(filename);
|
||||
if (!file.exists()) {
|
||||
File configDir = ConfigUtil.getConfigDir();
|
||||
file = new File(configDir + "/files/" + filename);
|
||||
if (!file.exists()) {
|
||||
BaseAction.writeNotFound();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasRange = false;
|
||||
long fileLength = file.length();
|
||||
long start = 0;
|
||||
long end = fileLength - 1;
|
||||
|
||||
String contentType = getContentType(file.getName());
|
||||
|
||||
response.setHeader(Header.CONTENT_DISPOSITION, StrFormatter.format("inline; filename=\"{}\"", URLUtil.encode(file.getName())));
|
||||
if (contentType.startsWith("video/")) {
|
||||
response.setContentType(contentType);
|
||||
response.setHeader("Accept-Ranges", "bytes");
|
||||
String rangeHeader = request.getHeader("Range");
|
||||
if (StrUtil.isNotBlank(rangeHeader) && rangeHeader.startsWith("bytes=")) {
|
||||
String[] range = rangeHeader.substring(6).split("-");
|
||||
if (range.length > 0) {
|
||||
start = Long.parseLong(range[0]);
|
||||
}
|
||||
if (range.length > 1) {
|
||||
end = Long.parseLong(range[1]);
|
||||
} else {
|
||||
long maxEnd = start + (1024 * 1024 * 10);
|
||||
end = Math.min(end, maxEnd);
|
||||
}
|
||||
}
|
||||
response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileLength);
|
||||
hasRange = true;
|
||||
} else {
|
||||
long maxAge = 0;
|
||||
|
||||
// 小于或者等于 3M 缓存
|
||||
if (fileLength <= 1024 * 1024 * 3) {
|
||||
// 30 天
|
||||
maxAge = 86400 * 30;
|
||||
}
|
||||
|
||||
response.setHeader(Header.CACHE_CONTROL, "private, max-age=" + maxAge);
|
||||
response.setContentType(contentType);
|
||||
}
|
||||
|
||||
try {
|
||||
if (hasRange) {
|
||||
long length = end - start;
|
||||
response.send(206, length);
|
||||
@Cleanup
|
||||
OutputStream out = response.getOut();
|
||||
@Cleanup
|
||||
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
|
||||
randomAccessFile.seek(start);
|
||||
@Cleanup
|
||||
FileChannel channel = randomAccessFile.getChannel();
|
||||
@Cleanup
|
||||
InputStream inputStream = Channels.newInputStream(channel);
|
||||
IoUtil.copy(inputStream, out, 40960, length, null);
|
||||
} else {
|
||||
@Cleanup
|
||||
InputStream inputStream = FileUtil.getInputStream(file);
|
||||
response.write(inputStream, (int) fileLength);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String message = ExceptionUtils.getMessage(e);
|
||||
log.debug(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
String img = request.getParam("img");
|
||||
if (StrUtil.isNotBlank(img)) {
|
||||
if (Base64.isBase64(img)) {
|
||||
img = Base64.decodeStr(img);
|
||||
}
|
||||
doImg(img);
|
||||
return;
|
||||
}
|
||||
|
||||
String filename = request.getParam("filename");
|
||||
|
||||
if (StrUtil.isBlank(filename)) {
|
||||
BaseAction.writeNotFound();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Base64.isBase64(filename)) {
|
||||
filename = Base64.decodeStr(filename);
|
||||
}
|
||||
|
||||
doFile(filename);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.dto.ImportAniDataDTO;
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.util.other.AniUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.lang.UUID;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.Synchronized;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 导入订阅
|
||||
*/
|
||||
@Slf4j
|
||||
@Auth
|
||||
@Path("/ani/import")
|
||||
public class ImportAction implements BaseAction {
|
||||
|
||||
static final List<Ani> ANI_LIST = AniUtil.ANI_LIST;
|
||||
|
||||
@Override
|
||||
@Synchronized("ANI_LIST")
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
ImportAniDataDTO dto = getBody(ImportAniDataDTO.class);
|
||||
List<Ani> aniList = dto.getAniList();
|
||||
if (aniList.isEmpty()) {
|
||||
resultErrorMsg("导入列表为空");
|
||||
return;
|
||||
}
|
||||
|
||||
ImportAniDataDTO.Conflict conflict = dto.getConflict();
|
||||
|
||||
for (Ani ani : aniList) {
|
||||
AniUtil.verify(ani);
|
||||
|
||||
String title = ani.getTitle();
|
||||
int season = ani.getSeason();
|
||||
Optional<Ani> first = AniUtil.ANI_LIST.stream()
|
||||
.filter(it -> it.getTitle().equals(title) && it.getSeason() == season)
|
||||
.findFirst();
|
||||
|
||||
if (first.isEmpty()) {
|
||||
String image = ani.getImage();
|
||||
String cover = AniUtil.saveJpg(image);
|
||||
ani.setCover(cover)
|
||||
.setId(UUID.fastUUID().toString());
|
||||
ANI_LIST.add(ani);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (conflict == ImportAniDataDTO.Conflict.SKIP) {
|
||||
log.info("存在冲突,已跳过 {} 第{}季", title, season);
|
||||
continue;
|
||||
}
|
||||
|
||||
log.info("存在冲突,已替换 {} 第{}季", title, season);
|
||||
String image = ani.getImage();
|
||||
String cover = AniUtil.saveJpg(image);
|
||||
ani.setCover(cover);
|
||||
|
||||
String[] ignoreProperties = new String[]{"id", "currentEpisodeNumber", "lastDownloadTime"};
|
||||
BeanUtil.copyProperties(ani, first.get(), ignoreProperties);
|
||||
}
|
||||
|
||||
AniUtil.sync();
|
||||
resultSuccessMsg("导入成功");
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.entity.Item;
|
||||
import ani.rss.service.DownloadService;
|
||||
import ani.rss.util.other.ItemsUtil;
|
||||
import ani.rss.util.other.TorrentUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 预览订阅
|
||||
*/
|
||||
@Auth
|
||||
@Path("/items")
|
||||
public class ItemsAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
Ani ani = getBody(Ani.class);
|
||||
List<Item> items = ItemsUtil.getItems(ani);
|
||||
|
||||
String downloadPath = DownloadService.getDownloadPath(ani);
|
||||
|
||||
for (Item item : items) {
|
||||
item.setLocal(false);
|
||||
File torrent = TorrentUtil.getTorrent(ani, item);
|
||||
if (torrent.exists()) {
|
||||
item.setLocal(true);
|
||||
continue;
|
||||
}
|
||||
if (DownloadService.itemDownloaded(ani, item, false)) {
|
||||
item.setLocal(true);
|
||||
}
|
||||
}
|
||||
|
||||
List<Integer> omitList = ItemsUtil.omitList(ani, items);
|
||||
|
||||
resultSuccess(Map.of(
|
||||
"downloadPath", downloadPath,
|
||||
"items", items,
|
||||
"omitList", omitList
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.entity.Log;
|
||||
import ani.rss.util.basic.LogUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.http.Method;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.Synchronized;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 日志
|
||||
*/
|
||||
@Slf4j
|
||||
@Auth
|
||||
@Path("/logs")
|
||||
public class LogsAction implements BaseAction {
|
||||
List<Log> LOG_LIST = LogUtil.LOG_LIST;
|
||||
|
||||
@Override
|
||||
@Synchronized("LOG_LIST")
|
||||
public void doAction(HttpServerRequest req, HttpServerResponse res) {
|
||||
String method = req.getMethod();
|
||||
if (Method.DELETE.name().equals(method)) {
|
||||
LOG_LIST.clear();
|
||||
log.info("清理日志");
|
||||
resultSuccess();
|
||||
return;
|
||||
}
|
||||
resultSuccess(LOG_LIST);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.entity.Mikan;
|
||||
import ani.rss.util.other.MikanUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Mikan搜索
|
||||
*/
|
||||
@Auth
|
||||
@Path("/mikan")
|
||||
public class MikanAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
String text = request.getParam("text");
|
||||
Mikan.Season season = getBody(Mikan.Season.class);
|
||||
resultSuccess(MikanUtil.list(text, season));
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.commons.GsonStatic;
|
||||
import ani.rss.entity.Mikan;
|
||||
import ani.rss.entity.TorrentsInfo;
|
||||
import ani.rss.util.other.MikanUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Mikan字幕组
|
||||
*/
|
||||
@Auth
|
||||
@Path("/mikan/group")
|
||||
public class MikanGroupAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
String url = request.getParam("url");
|
||||
List<Mikan.Group> groups = MikanUtil.getGroups(url);
|
||||
|
||||
List<String> regexItemList = List.of(
|
||||
"1920[Xx]1080", "3840[Xx]2160", "1080[Pp]", "4[Kk]", "720[Pp]",
|
||||
"繁", "简", "日",
|
||||
"cht|Cht|CHT", "chs|Chs|CHS", "hevc|Hevc|HEVC",
|
||||
"10bit|10Bit|10BIT", "h265|H265", "h264|H264",
|
||||
"内嵌", "内封", "外挂",
|
||||
"mp4|MP4", "mkv|MKV"
|
||||
);
|
||||
|
||||
for (Mikan.Group group : groups) {
|
||||
Set<String> tags = new HashSet<>();
|
||||
List<List<Mikan.RegexItem>> regexList = new ArrayList<>();
|
||||
List<TorrentsInfo> items = group.getItems();
|
||||
for (TorrentsInfo item : items) {
|
||||
String name = item.getName();
|
||||
List<Mikan.RegexItem> regexItems = new ArrayList<>();
|
||||
for (String regex : regexItemList) {
|
||||
if (!ReUtil.contains(regex, name)) {
|
||||
continue;
|
||||
}
|
||||
String label = ReUtil.get(regex, name, 0);
|
||||
label = label.toUpperCase();
|
||||
Mikan.RegexItem regexItem = new Mikan.RegexItem(label, regex);
|
||||
regexItems.add(regexItem);
|
||||
tags.add(label);
|
||||
}
|
||||
regexItems = CollUtil.distinct(regexItems, GsonStatic::toJson, true);
|
||||
regexList.add(regexItems);
|
||||
}
|
||||
|
||||
regexList = CollUtil.distinct(regexList, GsonStatic::toJson, true);
|
||||
group.setRegexList(regexList)
|
||||
.setTags(tags);
|
||||
}
|
||||
resultSuccess(groups);
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.entity.PlayItem;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.matthewn4444.ebml.EBMLReader;
|
||||
import com.matthewn4444.ebml.subtitles.Subtitles;
|
||||
import lombok.Cleanup;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 视频操作
|
||||
*/
|
||||
@Auth
|
||||
@Slf4j
|
||||
@Path("/playitem")
|
||||
public class PlayItemAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
JsonObject jsonObject = getBody(JsonObject.class);
|
||||
String type = jsonObject.get("type").getAsString();
|
||||
String file = jsonObject.get("file").getAsString();
|
||||
if ("getSubtitles".equalsIgnoreCase(type)) {
|
||||
getSubtitles(file);
|
||||
return;
|
||||
}
|
||||
|
||||
resultErrorMsg("未知操作");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取内封字幕并返回到客户端
|
||||
*
|
||||
* @param file
|
||||
* @throws IOException
|
||||
*/
|
||||
public void getSubtitles(String file) throws IOException {
|
||||
Assert.notBlank(file);
|
||||
|
||||
if (Base64.isBase64(file)) {
|
||||
file = Base64.decodeStr(file);
|
||||
}
|
||||
|
||||
List<PlayItem.Subtitles> subtitlesList = new ArrayList<>();
|
||||
|
||||
String extName = FileUtil.extName(file);
|
||||
if (StrUtil.isBlank(extName)) {
|
||||
resultSuccess(subtitlesList);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!"mkv".equals(extName)) {
|
||||
resultSuccess(subtitlesList);
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.isTrue(FileUtil.exist(file), "视频文件不存在");
|
||||
|
||||
@Cleanup
|
||||
EBMLReader reader = new EBMLReader(file);
|
||||
if (!reader.readHeader()) {
|
||||
resultSuccess(subtitlesList);
|
||||
return;
|
||||
}
|
||||
reader.readTracks();
|
||||
reader.readCues();
|
||||
|
||||
for (int i = 0; i < reader.getCuesCount(); i++) {
|
||||
reader.readSubtitlesInCueFrame(i);
|
||||
}
|
||||
|
||||
List<Subtitles> subtitles = reader.getSubtitles();
|
||||
for (Subtitles subtitle : subtitles) {
|
||||
String name = subtitle.getName();
|
||||
String presentableName = subtitle.getPresentableName();
|
||||
String contents = subtitle.getContentsToVTT();
|
||||
PlayItem.Subtitles sub = new PlayItem.Subtitles();
|
||||
sub.setContent(contents)
|
||||
.setName(name)
|
||||
.setHtml(presentableName)
|
||||
.setUrl("")
|
||||
.setType("vtt");
|
||||
subtitlesList.add(sub);
|
||||
}
|
||||
|
||||
resultSuccess(subtitlesList);
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.entity.Config;
|
||||
import ani.rss.entity.ProxyTest;
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.util.basic.HttpReq;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.text.StrFormatter;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jsoup.Jsoup;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 代理
|
||||
*/
|
||||
@Slf4j
|
||||
@Auth
|
||||
@Path("/proxy")
|
||||
public class ProxyAction implements BaseAction {
|
||||
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
String url = request.getParam("url");
|
||||
Config config = getBody(Config.class);
|
||||
url = Base64.decodeStr(url);
|
||||
|
||||
log.info(url);
|
||||
|
||||
HttpRequest httpRequest = HttpReq.get(url);
|
||||
HttpReq.setProxy(httpRequest, config);
|
||||
|
||||
ProxyTest proxyTest = new ProxyTest();
|
||||
Result<ProxyTest> result = Result.success(proxyTest);
|
||||
|
||||
long start = LocalDateTimeUtil.toEpochMilli(LocalDateTimeUtil.now());
|
||||
try {
|
||||
httpRequest
|
||||
.then(res -> {
|
||||
int status = res.getStatus();
|
||||
proxyTest.setStatus(status);
|
||||
|
||||
String title = Jsoup.parse(res.body())
|
||||
.title();
|
||||
result.setMessage(StrFormatter.format("测试成功 {}", title));
|
||||
});
|
||||
} catch (Exception e) {
|
||||
result.setMessage(e.getMessage())
|
||||
.setCode(HttpStatus.HTTP_INTERNAL_ERROR);
|
||||
}
|
||||
|
||||
long end = LocalDateTimeUtil.toEpochMilli(LocalDateTimeUtil.now());
|
||||
proxyTest.setTime(end - start);
|
||||
result(result);
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.commons.ExceptionUtils;
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.util.other.AniUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* 根据rss解析为订阅
|
||||
*/
|
||||
@Slf4j
|
||||
@Auth
|
||||
@Path("/rss")
|
||||
public class RssAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest req, HttpServerResponse res) throws IOException {
|
||||
if (!req.getMethod().equals("POST")) {
|
||||
return;
|
||||
}
|
||||
Ani ani = getBody(Ani.class);
|
||||
String url = ani.getUrl();
|
||||
String type = ani.getType();
|
||||
String bgmUrl = ani.getBgmUrl();
|
||||
Assert.notBlank(url, "RSS地址 不能为空");
|
||||
if (!ReUtil.contains("http(s*)://", url)) {
|
||||
url = "https://" + url;
|
||||
}
|
||||
url = URLUtil.decode(url, "utf-8");
|
||||
try {
|
||||
Ani newAni = AniUtil.getAni(url, type, bgmUrl);
|
||||
resultSuccess(newAni);
|
||||
} catch (Exception e) {
|
||||
String message = ExceptionUtils.getMessage(e);
|
||||
log.error(message, e);
|
||||
resultErrorMsg("RSS解析失败 {}", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.service.ScrapeService;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 刮削
|
||||
*/
|
||||
@Auth
|
||||
@Slf4j
|
||||
@Path("/scrape")
|
||||
public class ScrapeAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
Ani ani = getBody(Ani.class);
|
||||
|
||||
String force = request.getParam("force");
|
||||
|
||||
ThreadUtil.execute(() ->
|
||||
ScrapeService.scrape(ani, Boolean.parseBoolean(force))
|
||||
);
|
||||
|
||||
String title = ani.getTitle();
|
||||
|
||||
resultSuccessMsg("已开始刮削 {}", title);
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.commons.MavenUtils;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import ani.rss.web.util.ServerUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.RuntimeUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 关闭或重启
|
||||
*/
|
||||
@Slf4j
|
||||
@Auth
|
||||
@Path("/stop")
|
||||
public class StopAction implements BaseAction {
|
||||
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
String statusStr = request.getParam("status");
|
||||
int status = Integer.parseInt(statusStr);
|
||||
String s = List.of("重启", "关闭").get(status);
|
||||
log.info("正在{}", s);
|
||||
resultSuccessMsg("正在{}", s);
|
||||
ThreadUtil.execute(() -> {
|
||||
ThreadUtil.sleep(3000);
|
||||
File jar = MavenUtils.getJar();
|
||||
String extName = FileUtil.extName(jar);
|
||||
ServerUtil.stop();
|
||||
if ("exe".equals(extName) && status == 0) {
|
||||
log.info("正在重启 {}", jar.getName());
|
||||
RuntimeUtil.exec(jar.getName());
|
||||
System.exit(status);
|
||||
return;
|
||||
}
|
||||
System.exit(status);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.entity.NotificationConfig;
|
||||
import ani.rss.notification.TelegramNotification;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 电报
|
||||
*/
|
||||
@Auth
|
||||
@Path("/telegram")
|
||||
public class TelegramAction implements BaseAction {
|
||||
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
NotificationConfig notificationConfig = getBody(NotificationConfig.class);
|
||||
String method = request.getParam("method");
|
||||
|
||||
if ("getUpdates".equals(method)) {
|
||||
Map<String, String> map = TelegramNotification.getUpdates(notificationConfig);
|
||||
resultSuccess(map);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import ani.rss.web.auth.fun.IpWhitelist;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 用于检测是否处于白名单内
|
||||
*/
|
||||
@Auth(false)
|
||||
@Path("/test")
|
||||
public class TestAction implements BaseAction {
|
||||
private final IpWhitelist ipWhitelist = new IpWhitelist();
|
||||
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
Boolean b = ipWhitelist.apply(request);
|
||||
if (b) {
|
||||
resultSuccess();
|
||||
return;
|
||||
}
|
||||
resultError();
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.util.other.TmdbUtils;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import wushuo.tmdb.api.entity.Tmdb;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* TMDB
|
||||
*/
|
||||
@Auth
|
||||
@Path("/tmdb")
|
||||
public class ThemoviedbAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
String s = request.getParam("method");
|
||||
if ("getThemoviedbName".equals(s)) {
|
||||
Ani ani = getBody(Ani.class);
|
||||
String themoviedbName = TmdbUtils.getFinalName(ani);
|
||||
Result<Ani> result = new Result<Ani>()
|
||||
.setCode(HttpStatus.HTTP_OK)
|
||||
.setMessage("获取TMDB成功")
|
||||
.setData(ani.setThemoviedbName(themoviedbName));
|
||||
if (StrUtil.isBlank(themoviedbName)) {
|
||||
result.setCode(HttpStatus.HTTP_INTERNAL_ERROR)
|
||||
.setMessage("获取TMDB失败");
|
||||
}
|
||||
result(result);
|
||||
return;
|
||||
}
|
||||
|
||||
if ("getTmdbGroup".equals(s)) {
|
||||
Ani ani = getBody(Ani.class);
|
||||
Tmdb tmdb = ani.getTmdb();
|
||||
Assert.notNull(tmdb, "tmdb is null");
|
||||
Assert.notBlank(tmdb.getId(), "tmdb is null");
|
||||
resultSuccess(TmdbUtils.getTmdbGroup(tmdb));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.commons.FileUtils;
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.util.other.AniUtil;
|
||||
import ani.rss.util.other.TorrentUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import ani.rss.web.util.ServerUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.Method;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 种子管理
|
||||
*/
|
||||
@Slf4j
|
||||
@Auth
|
||||
@Path("/torrent")
|
||||
public class TorrentAction implements BaseAction {
|
||||
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
if (Method.DELETE.name().equals(request.getMethod())) {
|
||||
del();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除缓存种子
|
||||
*/
|
||||
public void del() {
|
||||
HttpServerRequest req = ServerUtil.REQUEST.get();
|
||||
String id = req.getParam("id");
|
||||
String infoHash = req.getParam("infoHash");
|
||||
Optional<Ani> first = AniUtil.ANI_LIST.stream().filter(ani -> id.equals(ani.getId()))
|
||||
.findFirst();
|
||||
if (first.isEmpty()) {
|
||||
resultErrorMsg("此订阅不存在");
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> infoHashList = StrUtil.split(infoHash, ",", true, true);
|
||||
|
||||
Ani ani = first.get();
|
||||
File torrentDir = TorrentUtil.getTorrentDir(ani);
|
||||
File[] files = FileUtils.listFiles(torrentDir);
|
||||
for (File file : files) {
|
||||
String s = FileUtil.mainName(file);
|
||||
if (infoHashList.contains(s)) {
|
||||
log.info("删除种子 {}", file);
|
||||
FileUtil.del(file);
|
||||
}
|
||||
}
|
||||
resultSuccessMsg("删除完成");
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.entity.TorrentsInfo;
|
||||
import ani.rss.util.other.TorrentUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 下载器任务列表
|
||||
*/
|
||||
@Slf4j
|
||||
@Auth
|
||||
@Path("/torrentsInfos")
|
||||
public class TorrentsInfosAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
List<TorrentsInfo> torrentsInfos = TorrentUtil.getTorrentsInfos();
|
||||
resultSuccess(torrentsInfos);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.entity.Config;
|
||||
import ani.rss.other.Cron;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Trackers
|
||||
*/
|
||||
@Auth
|
||||
@Path("/trackersUpdate")
|
||||
public class TrackersUpdateAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
Config config = getBody(Config.class);
|
||||
Cron.updateTrackers(config);
|
||||
resultSuccessMsg("更新完成");
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
import ani.rss.commons.ExceptionUtils;
|
||||
import ani.rss.entity.About;
|
||||
import ani.rss.util.other.UpdateUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* 更新
|
||||
*/
|
||||
@Slf4j
|
||||
@Auth
|
||||
@Path("/update")
|
||||
public class UpdateAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
About about = UpdateUtil.about();
|
||||
try {
|
||||
UpdateUtil.update(about);
|
||||
resultSuccessMsg("更新成功, 正在重启...");
|
||||
} catch (Exception e) {
|
||||
String message = ExceptionUtils.getMessage(e);
|
||||
log.info("更新失败 {}, {}", about.getLatest(), message);
|
||||
resultErrorMsg("更新失败 {}, {}", about.getLatest(), message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package ani.rss.action;
|
||||
|
||||
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.net.multipart.MultipartFormData;
|
||||
import cn.hutool.core.net.multipart.UploadFile;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*/
|
||||
@Slf4j
|
||||
@Auth
|
||||
@Path("/upload")
|
||||
public class UploadAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
String type = request.getParam("type");
|
||||
MultipartFormData multipart = request.getMultipart();
|
||||
UploadFile file = multipart.getFile("file");
|
||||
if (file.size() > 1024 * 1024 * 50) {
|
||||
resultErrorMsg("文件大小超过 50M");
|
||||
return;
|
||||
}
|
||||
byte[] fileContent = file.getFileContent();
|
||||
if ("getBase64".equals(type)) {
|
||||
resultSuccess(Base64.encode(fileContent));
|
||||
return;
|
||||
}
|
||||
|
||||
String s = SecureUtil.md5(new ByteArrayInputStream(fileContent));
|
||||
String fileName = file.getFileName();
|
||||
String saveName = s + "." + FileUtil.extName(fileName);
|
||||
|
||||
File configDir = ConfigUtil.getConfigDir();
|
||||
FileUtil.mkdir(configDir + "/files/" + s.charAt(0));
|
||||
FileUtil.writeBytes(fileContent, configDir + "/files/" + s.charAt(0) + "/" + saveName);
|
||||
resultSuccess(new Result<>().setMessage("上传完成").setData(s.charAt(0) + "/" + saveName));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package ani.rss.web.annotation;
|
||||
package ani.rss.annotation;
|
||||
|
||||
import ani.rss.web.auth.enums.AuthType;
|
||||
import ani.rss.auth.enums.AuthType;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -11,7 +11,7 @@ import java.lang.annotation.Target;
|
||||
* 鉴权
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE, ElementType.FIELD})
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface Auth {
|
||||
boolean value() default true;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package ani.rss.auth;
|
||||
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.entity.Global;
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.exception.ResultException;
|
||||
import ani.rss.util.other.AuthUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
public class AuthAspect {
|
||||
@Before("@annotation(auth)")
|
||||
public void before(JoinPoint joinPoint, Auth auth) throws Exception {
|
||||
HttpServletRequest request = Global.REQUEST.get();
|
||||
if (AuthUtil.test(request, auth)) {
|
||||
// 鉴权通过
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ResultException(
|
||||
Result.error(r ->
|
||||
r.setCode(403)
|
||||
.setMessage("登录已失效")
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package ani.rss.web.auth.enums;
|
||||
package ani.rss.auth.enums;
|
||||
|
||||
import ani.rss.web.auth.fun.ApiKey;
|
||||
import ani.rss.web.auth.fun.Form;
|
||||
import ani.rss.web.auth.fun.Header;
|
||||
import ani.rss.web.auth.fun.IpWhitelist;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import ani.rss.auth.fun.ApiKey;
|
||||
import ani.rss.auth.fun.Form;
|
||||
import ani.rss.auth.fun.Header;
|
||||
import ani.rss.auth.fun.IpWhitelist;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@@ -21,5 +21,5 @@ public enum AuthType {
|
||||
IP_WHITE_LIST(IpWhitelist.class);
|
||||
|
||||
@Getter
|
||||
private final Class<? extends Function<HttpServerRequest, Boolean>> clazz;
|
||||
private final Class<? extends Function<HttpServletRequest, Boolean>> clazz;
|
||||
}
|
||||
@@ -1,24 +1,24 @@
|
||||
package ani.rss.web.auth.fun;
|
||||
package ani.rss.auth.fun;
|
||||
|
||||
import ani.rss.entity.Config;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* api key 鉴权
|
||||
*/
|
||||
public class ApiKey implements Function<HttpServerRequest, Boolean> {
|
||||
public class ApiKey implements Function<HttpServletRequest, Boolean> {
|
||||
@Override
|
||||
public Boolean apply(HttpServerRequest request) {
|
||||
public Boolean apply(HttpServletRequest request) {
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
String apiKey = config.getApiKey();
|
||||
if (StrUtil.isBlank(apiKey)) {
|
||||
return false;
|
||||
}
|
||||
String s = StrUtil.blankToDefault(request.getParam("s"), request.getHeader("s"));
|
||||
String s = StrUtil.blankToDefault(request.getParameter("s"), request.getHeader("s"));
|
||||
return StrUtil.equals(apiKey, s);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
package ani.rss.web.auth.fun;
|
||||
package ani.rss.auth.fun;
|
||||
|
||||
import ani.rss.entity.Login;
|
||||
import ani.rss.web.util.AuthUtil;
|
||||
import ani.rss.util.other.AuthUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 表单鉴权
|
||||
*/
|
||||
public class Form implements Function<HttpServerRequest, Boolean> {
|
||||
public class Form implements Function<HttpServletRequest, Boolean> {
|
||||
@Override
|
||||
public Boolean apply(HttpServerRequest request) {
|
||||
String s = request.getParam("s");
|
||||
public Boolean apply(HttpServletRequest request) {
|
||||
String s = request.getParameter("s");
|
||||
Login login = AuthUtil.getLogin();
|
||||
String auth = AuthUtil.getAuth(login);
|
||||
return StrUtil.equals(auth, s);
|
||||
@@ -1,18 +1,18 @@
|
||||
package ani.rss.web.auth.fun;
|
||||
package ani.rss.auth.fun;
|
||||
|
||||
import ani.rss.entity.Login;
|
||||
import ani.rss.web.util.AuthUtil;
|
||||
import ani.rss.util.other.AuthUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 请求头鉴权
|
||||
*/
|
||||
public class Header implements Function<HttpServerRequest, Boolean> {
|
||||
public class Header implements Function<HttpServletRequest, Boolean> {
|
||||
@Override
|
||||
public Boolean apply(HttpServerRequest request) {
|
||||
public Boolean apply(HttpServletRequest request) {
|
||||
String s = request.getHeader("Authorization");
|
||||
if (StrUtil.isBlank(s)) {
|
||||
return false;
|
||||
@@ -1,15 +1,15 @@
|
||||
package ani.rss.web.auth.fun;
|
||||
package ani.rss.auth.fun;
|
||||
|
||||
import ani.rss.commons.CacheUtils;
|
||||
import ani.rss.entity.Config;
|
||||
import ani.rss.util.basic.CidrRangeChecker;
|
||||
import ani.rss.util.other.AuthUtil;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import ani.rss.web.util.AuthUtil;
|
||||
import cn.hutool.core.lang.PatternPool;
|
||||
import cn.hutool.core.net.Ipv4Util;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.List;
|
||||
@@ -18,9 +18,9 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Slf4j
|
||||
public class IpWhitelist implements Function<HttpServerRequest, Boolean> {
|
||||
public class IpWhitelist implements Function<HttpServletRequest, Boolean> {
|
||||
@Override
|
||||
public Boolean apply(HttpServerRequest request) {
|
||||
public Boolean apply(HttpServletRequest request) {
|
||||
String ip = AuthUtil.getIp();
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
String ipWhitelistStr = config.getIpWhitelistStr();
|
||||
@@ -88,7 +88,7 @@ public class FileUtils {
|
||||
|
||||
public static String normalize(String path) {
|
||||
path = path.trim();
|
||||
String s = cn.hutool.core.io.FileUtil.normalize(path);
|
||||
String s = FileUtil.normalize(path);
|
||||
while (s.endsWith("/")) {
|
||||
s = s.substring(0, s.length() - 1);
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import java.util.TimeZone;
|
||||
|
||||
@Slf4j
|
||||
public class GsonStatic {
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.disableHtmlEscaping()
|
||||
.disableJdkUnsafe()
|
||||
.disableInnerClassSerialization()
|
||||
@@ -4,10 +4,13 @@ import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.XmlUtil;
|
||||
import cn.hutool.system.OsInfo;
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import lombok.Cleanup;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -75,8 +78,9 @@ public class MavenUtils {
|
||||
}
|
||||
File file = new File("pom.xml");
|
||||
if (file.exists()) {
|
||||
String s = FileUtil.readUtf8String(file);
|
||||
version = ReUtil.get("<version>(.*?)</version>", s, 1);
|
||||
Document document = XmlUtil.readXML(file);
|
||||
Element element = XmlUtil.getElement(document.getDocumentElement(), "version");
|
||||
version = element.getTextContent();
|
||||
}
|
||||
return version;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package ani.rss.other;
|
||||
package ani.rss.config;
|
||||
|
||||
import ani.rss.download.BaseDownload;
|
||||
import ani.rss.entity.About;
|
||||
@@ -10,22 +10,68 @@ import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.cron.CronUtil;
|
||||
import cn.hutool.http.ContentType;
|
||||
import cn.hutool.http.Header;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 定时任务
|
||||
*/
|
||||
@Slf4j
|
||||
public class Cron {
|
||||
public static void updateTrackers(Config config) {
|
||||
@Component
|
||||
public class CronConfig {
|
||||
@Scheduled(cron = "0 0 0 * * *")
|
||||
public void backupConfig() {
|
||||
ConfigUtil.backup();
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 0 1 * * *")
|
||||
public void autoTrackersUpdate() {
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
Boolean autoTrackersUpdate = config.getAutoTrackersUpdate();
|
||||
if (!autoTrackersUpdate) {
|
||||
// 未开启自动更新 Trackers
|
||||
return;
|
||||
}
|
||||
log.info("定时任务 开始更新 Trackers");
|
||||
try {
|
||||
updateTrackers(config);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 0 6 * * *")
|
||||
public void autoUpdate() {
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
Boolean autoUpdate = config.getAutoUpdate();
|
||||
if (!autoUpdate) {
|
||||
// 未开启 自动更新
|
||||
return;
|
||||
}
|
||||
log.info("定时任务 自动更新");
|
||||
try {
|
||||
About about = UpdateUtil.about();
|
||||
Boolean update = about.getUpdate();
|
||||
autoUpdate = about.getAutoUpdate();
|
||||
if (!autoUpdate) {
|
||||
// 禁止非跨小版本的自动更新
|
||||
return;
|
||||
}
|
||||
if (update) {
|
||||
log.info("检测到可更新版本 v{}", about.getLatest());
|
||||
}
|
||||
UpdateUtil.update(about);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateTrackers(Config config) {
|
||||
String trackersUpdateUrls = config.getTrackersUpdateUrls();
|
||||
Assert.notBlank(trackersUpdateUrls, "Trackers更新地址 为空");
|
||||
|
||||
@@ -75,54 +121,4 @@ public class Cron {
|
||||
baseDownload.updateTrackers(trackers);
|
||||
}
|
||||
|
||||
public static void autoUpdate(Config config) {
|
||||
About about = UpdateUtil.about();
|
||||
Boolean update = about.getUpdate();
|
||||
Boolean autoUpdate = about.getAutoUpdate();
|
||||
if (!autoUpdate) {
|
||||
// 禁止非跨小版本的自动更新
|
||||
return;
|
||||
}
|
||||
if (update) {
|
||||
log.info("检测到可更新版本 v{}", about.getLatest());
|
||||
}
|
||||
UpdateUtil.update(about);
|
||||
}
|
||||
|
||||
public static void start() {
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
|
||||
// 自动备份设置
|
||||
CronUtil.schedule("0 0 * * *", (Runnable) ConfigUtil::backup);
|
||||
|
||||
CronUtil.schedule("0 1 * * *", (Runnable) () -> {
|
||||
Boolean autoTrackersUpdate = config.getAutoTrackersUpdate();
|
||||
if (!autoTrackersUpdate) {
|
||||
// 未开启自动更新 Trackers
|
||||
return;
|
||||
}
|
||||
log.info("定时任务 开始更新 Trackers");
|
||||
try {
|
||||
Cron.updateTrackers(config);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
CronUtil.schedule("0 6 * * *", (Runnable) () -> {
|
||||
Boolean autoUpdate = config.getAutoUpdate();
|
||||
if (!autoUpdate) {
|
||||
// 未开启 自动更新
|
||||
return;
|
||||
}
|
||||
log.info("定时任务 自动更新");
|
||||
try {
|
||||
Cron.autoUpdate(config);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
|
||||
CronUtil.start();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package ani.rss.config;
|
||||
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.exception.ResultException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class CustomExceptionHandler {
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public Result<Void> handleException(IllegalArgumentException e) {
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(ResultException.class)
|
||||
public Result<Void> handleException(ResultException e) {
|
||||
return e.getResult();
|
||||
}
|
||||
|
||||
@ExceptionHandler({NoResourceFoundException.class, HttpRequestMethodNotSupportedException.class})
|
||||
public Result<Void> handleException(NoResourceFoundException e) {
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public Result<Void> handleException(Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.error();
|
||||
}
|
||||
|
||||
}
|
||||
51
ani-rss-application/src/main/java/ani/rss/config/Runner.java
Normal file
51
ani-rss-application/src/main/java/ani/rss/config/Runner.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package ani.rss.config;
|
||||
|
||||
import ani.rss.commons.ExceptionUtils;
|
||||
import ani.rss.commons.MavenUtils;
|
||||
import ani.rss.service.TaskService;
|
||||
import ani.rss.util.other.AniUtil;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import cn.hutool.core.net.NetUtil;
|
||||
import cn.hutool.core.util.RuntimeUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class Runner implements ApplicationRunner {
|
||||
|
||||
@Value("${server.port}")
|
||||
private String port;
|
||||
|
||||
@Override
|
||||
public void run(@NonNull ApplicationArguments args) {
|
||||
try {
|
||||
ConfigUtil.load();
|
||||
ConfigUtil.backup();
|
||||
|
||||
AniUtil.load();
|
||||
TaskService.start();
|
||||
String version = MavenUtils.getVersion();
|
||||
log.info("version {}", version);
|
||||
|
||||
|
||||
for (String ip : NetUtil.localIpv4s()) {
|
||||
InetSocketAddress inetSocketAddress = new InetSocketAddress(ip, Integer.parseInt(port));
|
||||
if (NetUtil.isOpen(inetSocketAddress, 100)) {
|
||||
log.info("http://{}:{}", ip, port);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String message = ExceptionUtils.getMessage(e);
|
||||
log.error(message, e);
|
||||
System.exit(1);
|
||||
}
|
||||
RuntimeUtil.addShutdownHook(() -> log.info("程序退出..."));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package ani.rss.config;
|
||||
|
||||
import ani.rss.commons.MavenUtils;
|
||||
import io.swagger.v3.oas.models.ExternalDocumentation;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class SwaggerConfig {
|
||||
@Bean
|
||||
public OpenAPI springShopOpenAPI() {
|
||||
Info info = new Info();
|
||||
|
||||
License license = new License()
|
||||
.name("GPL-2.0")
|
||||
.url("https://github.com/wushuo894/ani-rss/blob/master/LICENSE");
|
||||
|
||||
String version = MavenUtils.getVersion();
|
||||
|
||||
info.title("ANI-RSS")
|
||||
.contact(new Contact())
|
||||
.description("基于RSS自动追番、订阅、下载、刮削")
|
||||
.version("v" + version)
|
||||
.license(license);
|
||||
|
||||
ExternalDocumentation externalDocumentation = new ExternalDocumentation()
|
||||
.description("外部文档")
|
||||
.url("https://docs.wushuo.top/");
|
||||
|
||||
|
||||
return new OpenAPI()
|
||||
.info(info)
|
||||
.externalDocs(externalDocumentation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package ani.rss.config;
|
||||
|
||||
import ani.rss.entity.Global;
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class WebFilter implements Filter {
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
|
||||
Global.REQUEST.set((HttpServletRequest) request);
|
||||
Global.RESPONSE.set((HttpServletResponse) response);
|
||||
try {
|
||||
filterChain.doFilter(request, response);
|
||||
} finally {
|
||||
Global.REQUEST.remove();
|
||||
Global.RESPONSE.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package ani.rss.config;
|
||||
|
||||
import ani.rss.commons.GsonStatic;
|
||||
import com.google.gson.Gson;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.converter.HttpMessageConverters;
|
||||
import org.springframework.http.converter.json.GsonHttpMessageConverter;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
@Override
|
||||
public void configurePathMatch(PathMatchConfigurer configurer) {
|
||||
configurer.addPathPrefix("/api", c -> c.isAnnotationPresent(RestController.class));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Gson gson() {
|
||||
return GsonStatic.GSON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureMessageConverters(HttpMessageConverters.ServerBuilder builder) {
|
||||
builder.configureMessageConvertersList(converters -> {
|
||||
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
|
||||
converter.setGson(gson());
|
||||
converter.setDefaultCharset(StandardCharsets.UTF_8);
|
||||
converters.add(0, converter);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.auth.fun.IpWhitelist;
|
||||
import ani.rss.commons.ExceptionUtils;
|
||||
import ani.rss.commons.MavenUtils;
|
||||
import ani.rss.entity.About;
|
||||
import ani.rss.entity.Global;
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.util.other.UpdateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.RuntimeUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
public class AboutController extends BaseController {
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "查看关于信息")
|
||||
@PostMapping("/about")
|
||||
public Result<About> about() {
|
||||
return Result.success(UpdateUtil.about());
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "停止服务")
|
||||
@PostMapping("/stop")
|
||||
public Result<Void> stop(@RequestParam("status") Integer status) {
|
||||
String s = List.of("重启", "关闭").get(status);
|
||||
log.info("正在{}", s);
|
||||
ThreadUtil.execute(() -> {
|
||||
ThreadUtil.sleep(3000);
|
||||
File jar = MavenUtils.getJar();
|
||||
String extName = FileUtil.extName(jar);
|
||||
if ("exe".equals(extName) && status == 0) {
|
||||
log.info("正在重启 {}", jar.getName());
|
||||
RuntimeUtil.exec(jar.getName());
|
||||
System.exit(status);
|
||||
return;
|
||||
}
|
||||
System.exit(status);
|
||||
});
|
||||
return Result.success("正在{}", s);
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "更新")
|
||||
@PostMapping("/update")
|
||||
public Result<Void> update() {
|
||||
About about = UpdateUtil.about();
|
||||
try {
|
||||
UpdateUtil.update(about);
|
||||
return Result.success("更新成功, 正在重启...");
|
||||
} catch (Exception e) {
|
||||
String message = ExceptionUtils.getMessage(e);
|
||||
log.info("更新失败 {}, {}", about.getLatest(), message);
|
||||
return Result.success("更新失败 {}, {}", about.getLatest(), message);
|
||||
}
|
||||
}
|
||||
|
||||
private final IpWhitelist ipWhitelist = new IpWhitelist();
|
||||
|
||||
@Operation(summary = "IP白名单测试")
|
||||
@PostMapping("/testIpWhitelist")
|
||||
public Result<Void> testIpWhitelist() {
|
||||
HttpServletRequest request = Global.REQUEST.get();
|
||||
Boolean b = ipWhitelist.apply(request);
|
||||
if (b) {
|
||||
return Result.success();
|
||||
}
|
||||
return Result.error();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.entity.Config;
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.entity.TryOut;
|
||||
import ani.rss.util.basic.HttpReq;
|
||||
import ani.rss.util.other.AfdianUtil;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Hidden
|
||||
@RestController
|
||||
public class AfdianController extends BaseController {
|
||||
|
||||
@Auth
|
||||
@PostMapping("/verifyNo")
|
||||
public Result<Void> verifyNo(@RequestBody Config config) {
|
||||
String outTradeNo = config.getOutTradeNo();
|
||||
Result<Void> result = AfdianUtil.verifyNo(outTradeNo);
|
||||
|
||||
int code = result.getCode();
|
||||
if (code == 200) {
|
||||
Long time = DateUtil.offsetYear(new Date(), 999).getTime();
|
||||
ConfigUtil.CONFIG.setOutTradeNo(outTradeNo)
|
||||
.setExpirationTime(time)
|
||||
.setTryOut(false);
|
||||
ConfigUtil.sync();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Auth
|
||||
@PostMapping("/tryOut")
|
||||
public Result<Long> tryOut(@RequestBody Config config) {
|
||||
if (AfdianUtil.verifyExpirationTime()) {
|
||||
return Result.error("还在试用中!");
|
||||
}
|
||||
|
||||
String githubToken = config.getGithubToken();
|
||||
Assert.notBlank(githubToken, "GithubToken 不能为空");
|
||||
|
||||
Boolean ok = HttpReq.get("https://api.github.com/user/starred/wushuo894/ani-rss")
|
||||
.header("Authorization", "Bearer " + githubToken)
|
||||
.thenFunction(HttpResponse::isOk);
|
||||
|
||||
Assert.isTrue(ok, "未点击star");
|
||||
|
||||
TryOut tryOut = AfdianUtil.getTryOut();
|
||||
|
||||
Boolean enable = tryOut.getEnable();
|
||||
Boolean renewal = tryOut.getRenewal();
|
||||
Integer day = tryOut.getDay();
|
||||
String message = tryOut.getMessage();
|
||||
|
||||
Assert.isTrue(enable, message);
|
||||
|
||||
if (config.getTryOut()) {
|
||||
// 已经有过试用
|
||||
Assert.isTrue(renewal, message);
|
||||
}
|
||||
|
||||
long time = DateUtil.offsetDay(new Date(), day).getTime();
|
||||
ConfigUtil.CONFIG
|
||||
.setGithubToken(githubToken)
|
||||
.setExpirationTime(time)
|
||||
.setTryOut(true);
|
||||
ConfigUtil.sync();
|
||||
return Result.success(time).setMessage(message);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package ani.rss.action;
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.commons.ExceptionUtils;
|
||||
import ani.rss.commons.FileUtils;
|
||||
import ani.rss.dto.ImportAniDataDTO;
|
||||
import ani.rss.entity.*;
|
||||
import ani.rss.enums.SortTypeEnum;
|
||||
import ani.rss.service.AniService;
|
||||
@@ -9,10 +11,6 @@ import ani.rss.service.ClearService;
|
||||
import ani.rss.service.DownloadService;
|
||||
import ani.rss.task.RssTask;
|
||||
import ani.rss.util.other.*;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import ani.rss.web.util.ServerUtil;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.comparator.PinyinComparator;
|
||||
@@ -21,80 +19,35 @@ import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.lang.UUID;
|
||||
import cn.hutool.core.text.StrFormatter;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.extra.pinyin.PinyinUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.ToLongFunction;
|
||||
|
||||
/**
|
||||
* 订阅 增删改查
|
||||
*/
|
||||
@Auth
|
||||
@Slf4j
|
||||
@Path("/ani")
|
||||
public class AniAction implements BaseAction {
|
||||
|
||||
@RestController
|
||||
public class AniController extends BaseController {
|
||||
public static final AtomicBoolean DOWNLOAD = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* 手动刷新订阅
|
||||
*/
|
||||
private void refreshAni() {
|
||||
Ani ani = getBody(Ani.class);
|
||||
|
||||
if (Objects.isNull(ani)) {
|
||||
// 未传Body, 刷新所有订阅
|
||||
RssTask.sync();
|
||||
ThreadUtil.execute(() -> RssTask.download(new AtomicBoolean(true)));
|
||||
resultSuccessMsg("已开始刷新RSS");
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Ani> first = AniUtil.ANI_LIST.stream()
|
||||
.filter(it -> it.getId().equals(ani.getId()))
|
||||
.findFirst();
|
||||
if (first.isEmpty()) {
|
||||
resultErrorMsg("修改失败");
|
||||
return;
|
||||
}
|
||||
synchronized (DOWNLOAD) {
|
||||
if (DOWNLOAD.get()) {
|
||||
resultErrorMsg("存在未完成任务,请等待...");
|
||||
return;
|
||||
}
|
||||
DOWNLOAD.set(true);
|
||||
}
|
||||
Ani downloadAni = first.get();
|
||||
ThreadUtil.execute(() -> {
|
||||
try {
|
||||
if (TorrentUtil.login()) {
|
||||
DownloadService.downloadAni(downloadAni);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String message = ExceptionUtils.getMessage(e);
|
||||
log.error(message, e);
|
||||
}
|
||||
DOWNLOAD.set(false);
|
||||
});
|
||||
resultSuccessMsg("已开始刷新RSS {}", downloadAni.getTitle());
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加订阅
|
||||
*/
|
||||
private void post() {
|
||||
Ani ani = getBody(Ani.class);
|
||||
@Auth
|
||||
@Operation(summary = "添加订阅")
|
||||
@PostMapping("/addAni")
|
||||
public Result<Void> addAni(@RequestBody Ani ani) {
|
||||
ani.setTitle(ani.getTitle().trim())
|
||||
.setUrl(ani.getUrl().trim());
|
||||
AniUtil.verify(ani);
|
||||
@@ -104,8 +57,7 @@ public class AniAction implements BaseAction {
|
||||
.findFirst();
|
||||
|
||||
if (first.isPresent()) {
|
||||
resultErrorMsg("此订阅已存在");
|
||||
return;
|
||||
return Result.error("此订阅已存在");
|
||||
}
|
||||
|
||||
first = AniUtil.ANI_LIST.stream()
|
||||
@@ -122,8 +74,7 @@ public class AniAction implements BaseAction {
|
||||
AniUtil.ANI_LIST.remove(first.get());
|
||||
log.info("自动替换 {} 第{}季", title, season);
|
||||
} else {
|
||||
resultErrorMsg("订阅标题重复");
|
||||
return;
|
||||
return Result.error("订阅标题重复");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,15 +99,15 @@ public class AniAction implements BaseAction {
|
||||
}
|
||||
});
|
||||
}
|
||||
resultSuccessMsg("添加订阅成功");
|
||||
log.info("添加订阅 {} {} {}", title, ani.getUrl(), ani.getId());
|
||||
|
||||
return Result.success("添加订阅成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改订阅
|
||||
*/
|
||||
private void put() {
|
||||
Ani ani = getBody(Ani.class);
|
||||
@Auth
|
||||
@Operation(summary = "修改订阅")
|
||||
@PostMapping("/setAni")
|
||||
public Result<Void> setAni(@RequestBody Ani ani) {
|
||||
ani.setTitle(ani.getTitle().trim())
|
||||
.setUrl(ani.getUrl().trim());
|
||||
AniUtil.verify(ani);
|
||||
@@ -165,19 +116,17 @@ public class AniAction implements BaseAction {
|
||||
.filter(it -> it.getTitle().equals(ani.getTitle()) && it.getSeason().equals(ani.getSeason()))
|
||||
.findFirst();
|
||||
if (first.isPresent()) {
|
||||
resultErrorMsg("订阅标题重复");
|
||||
return;
|
||||
return Result.error("订阅标题重复");
|
||||
}
|
||||
|
||||
first = AniUtil.ANI_LIST.stream()
|
||||
.filter(it -> it.getId().equals(ani.getId()))
|
||||
.findFirst();
|
||||
if (first.isEmpty()) {
|
||||
resultErrorMsg("修改失败");
|
||||
return;
|
||||
return Result.error("修改失败");
|
||||
}
|
||||
HttpServerRequest request = ServerUtil.REQUEST.get();
|
||||
String move = request.getParam("move");
|
||||
HttpServletRequest request = Global.REQUEST.get();
|
||||
String move = request.getParameter("move");
|
||||
if (Boolean.parseBoolean(move)) {
|
||||
Ani get = ObjectUtil.clone(first.get());
|
||||
ThreadUtil.execute(() -> {
|
||||
@@ -237,14 +186,74 @@ public class AniAction implements BaseAction {
|
||||
FileUtil.move(torrentDir, newTorrentDir.getParentFile(), true);
|
||||
}
|
||||
AniUtil.sync();
|
||||
resultSuccessMsg("修改成功");
|
||||
|
||||
log.info("修改订阅 {} {} {}", ani.getTitle(), ani.getUrl(), ani.getId());
|
||||
return Result.success("修改成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回订阅列表
|
||||
*/
|
||||
private void get() {
|
||||
@Auth
|
||||
@Operation(summary = "删除订阅")
|
||||
@PostMapping("/deleteAni")
|
||||
public Result<Void> deleteAni(@RequestBody List<String> ids, @RequestParam("deleteFiles") Boolean deleteFiles) {
|
||||
Assert.notEmpty(ids, "未选择订阅");
|
||||
List<Ani> anis = AniUtil.ANI_LIST.stream()
|
||||
.filter(it -> ids.contains(it.getId()))
|
||||
.toList();
|
||||
if (anis.isEmpty()) {
|
||||
return Result.error("删除失败");
|
||||
}
|
||||
for (Ani ani : anis) {
|
||||
AniUtil.ANI_LIST.remove(ani);
|
||||
}
|
||||
|
||||
AniUtil.sync();
|
||||
ThreadUtil.execute(() -> {
|
||||
for (Ani ani : anis) {
|
||||
File torrentDir = TorrentUtil.getTorrentDir(ani);
|
||||
FileUtil.del(torrentDir);
|
||||
ClearService.clearParentFile(torrentDir);
|
||||
log.info("删除订阅 {} {} {}", ani.getTitle(), ani.getUrl(), ani.getId());
|
||||
}
|
||||
if (!deleteFiles) {
|
||||
// 不删除本地文件
|
||||
return;
|
||||
}
|
||||
|
||||
List<File> files = anis
|
||||
.stream()
|
||||
.map(DownloadService::getDownloadPath)
|
||||
.map(File::new)
|
||||
.toList();
|
||||
|
||||
Boolean login = TorrentUtil.login();
|
||||
List<TorrentsInfo> torrentsInfos = new ArrayList<>();
|
||||
if (login) {
|
||||
torrentsInfos = TorrentUtil.getTorrentsInfos();
|
||||
}
|
||||
for (File file : files) {
|
||||
String path = FileUtils.getAbsolutePath(file);
|
||||
for (TorrentsInfo torrentsInfo : torrentsInfos) {
|
||||
String downloadDir = torrentsInfo.getDownloadDir();
|
||||
if (downloadDir.equals(path)) {
|
||||
TorrentUtil.delete(torrentsInfo, true, true);
|
||||
}
|
||||
}
|
||||
if (!file.exists()) {
|
||||
continue;
|
||||
}
|
||||
ThreadUtil.sleep(3000);
|
||||
log.info("删除 {}", file);
|
||||
FileUtil.del(file);
|
||||
ClearService.clearParentFile(file);
|
||||
}
|
||||
});
|
||||
return Result.success("删除订阅成功");
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "订阅列表")
|
||||
@PostMapping("/listAni")
|
||||
public Result<List<Ani>> list() {
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
|
||||
SortTypeEnum sortType = config.getSortType();
|
||||
@@ -297,101 +306,13 @@ public class AniAction implements BaseAction {
|
||||
}).reversed());
|
||||
}
|
||||
|
||||
resultSuccess(list);
|
||||
return Result.success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除订阅
|
||||
*/
|
||||
public void delete() {
|
||||
JsonArray jsonArray = getBody(JsonArray.class);
|
||||
List<String> ids = jsonArray.asList()
|
||||
.stream().map(JsonElement::getAsString)
|
||||
.toList();
|
||||
Assert.notEmpty(ids, "未选择订阅");
|
||||
List<Ani> anis = AniUtil.ANI_LIST.stream()
|
||||
.filter(it -> ids.contains(it.getId()))
|
||||
.toList();
|
||||
if (anis.isEmpty()) {
|
||||
resultErrorMsg("删除失败");
|
||||
return;
|
||||
}
|
||||
for (Ani ani : anis) {
|
||||
AniUtil.ANI_LIST.remove(ani);
|
||||
}
|
||||
|
||||
AniUtil.sync();
|
||||
resultSuccessMsg("删除订阅成功");
|
||||
HttpServerRequest request = ServerUtil.REQUEST.get();
|
||||
String deleteFiles = request.getParam("deleteFiles");
|
||||
ThreadUtil.execute(() -> {
|
||||
for (Ani ani : anis) {
|
||||
File torrentDir = TorrentUtil.getTorrentDir(ani);
|
||||
FileUtil.del(torrentDir);
|
||||
ClearService.clearParentFile(torrentDir);
|
||||
log.info("删除订阅 {} {} {}", ani.getTitle(), ani.getUrl(), ani.getId());
|
||||
}
|
||||
if (!Boolean.parseBoolean(deleteFiles)) {
|
||||
// 不删除本地文件
|
||||
return;
|
||||
}
|
||||
|
||||
List<File> files = anis
|
||||
.stream()
|
||||
.map(DownloadService::getDownloadPath)
|
||||
.map(File::new)
|
||||
.toList();
|
||||
|
||||
Boolean login = TorrentUtil.login();
|
||||
List<TorrentsInfo> torrentsInfos = new ArrayList<>();
|
||||
if (login) {
|
||||
torrentsInfos = TorrentUtil.getTorrentsInfos();
|
||||
}
|
||||
for (File file : files) {
|
||||
String path = FileUtils.getAbsolutePath(file);
|
||||
for (TorrentsInfo torrentsInfo : torrentsInfos) {
|
||||
String downloadDir = torrentsInfo.getDownloadDir();
|
||||
if (downloadDir.equals(path)) {
|
||||
TorrentUtil.delete(torrentsInfo, true, true);
|
||||
}
|
||||
}
|
||||
if (!file.exists()) {
|
||||
continue;
|
||||
}
|
||||
ThreadUtil.sleep(3000);
|
||||
log.info("删除 {}", file);
|
||||
FileUtil.del(file);
|
||||
ClearService.clearParentFile(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void batchEnable(boolean enable) {
|
||||
List<String> ids = getBody(JsonArray.class)
|
||||
.asList()
|
||||
.stream()
|
||||
.map(JsonElement::getAsString)
|
||||
.toList();
|
||||
Assert.notEmpty(ids, "未选择订阅");
|
||||
for (Ani ani : AniUtil.ANI_LIST) {
|
||||
String id = ani.getId();
|
||||
if (!ids.contains(id)) {
|
||||
continue;
|
||||
}
|
||||
ani.setEnable(enable);
|
||||
}
|
||||
AniUtil.sync();
|
||||
resultSuccessMsg("修改完成");
|
||||
}
|
||||
|
||||
private void updateTotalEpisodeNumber() {
|
||||
HttpServerRequest request = ServerUtil.REQUEST.get();
|
||||
boolean force = Boolean.parseBoolean(request.getParam("force"));
|
||||
List<String> ids = getBody(JsonArray.class)
|
||||
.asList()
|
||||
.stream()
|
||||
.map(JsonElement::getAsString)
|
||||
.toList();
|
||||
@Auth
|
||||
@Operation(summary = "更新总集数")
|
||||
@PostMapping("/updateTotalEpisodeNumber")
|
||||
public Result<Void> updateTotalEpisodeNumber(@RequestParam("force") Boolean force, @RequestBody List<String> ids) {
|
||||
Assert.notEmpty(ids, "未选择订阅");
|
||||
ThreadUtil.execute(() -> {
|
||||
log.info("开始手动更新总集数");
|
||||
@@ -416,53 +337,196 @@ public class AniAction implements BaseAction {
|
||||
AniUtil.sync();
|
||||
log.info("手动更新总集数完成 共更新{}条订阅", count);
|
||||
});
|
||||
resultSuccessMsg("已开始更新总集数");
|
||||
return Result.success("已开始更新总集数");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAction(HttpServerRequest req, HttpServerResponse res) {
|
||||
String method = req.getMethod();
|
||||
String type = StrUtil.blankToDefault(req.getParam("type"), "");
|
||||
switch (type) {
|
||||
case "refreshAni" -> {
|
||||
// 刷新订阅
|
||||
refreshAni();
|
||||
return;
|
||||
@Auth
|
||||
@Operation(summary = "批量 启用/禁用 订阅")
|
||||
@PostMapping("/batchEnable")
|
||||
public Result<Void> batchEnable(@RequestParam("value") Boolean value, @RequestBody List<String> ids) {
|
||||
Assert.notEmpty(ids, "未选择订阅");
|
||||
for (Ani ani : AniUtil.ANI_LIST) {
|
||||
String id = ani.getId();
|
||||
if (!ids.contains(id)) {
|
||||
continue;
|
||||
}
|
||||
case "batchEnable" -> {
|
||||
// 批量 启用/禁用
|
||||
boolean enable = Boolean.parseBoolean(req.getParam("value"));
|
||||
batchEnable(enable);
|
||||
return;
|
||||
ani.setEnable(value);
|
||||
}
|
||||
AniUtil.sync();
|
||||
return Result.success("修改完成");
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "手动刷新订阅")
|
||||
@PostMapping("/refreshAll")
|
||||
public Result<Void> refreshAni() {
|
||||
// 未传Body, 刷新所有订阅
|
||||
RssTask.sync();
|
||||
ThreadUtil.execute(() -> RssTask.download(new AtomicBoolean(true)));
|
||||
return Result.success("已开始刷新RSS");
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "手动刷新订阅")
|
||||
@PostMapping("/refreshAni")
|
||||
public Result<Void> refreshAni(@RequestBody Ani ani) {
|
||||
Optional<Ani> first = AniUtil.ANI_LIST.stream()
|
||||
.filter(it -> it.getId().equals(ani.getId()))
|
||||
.findFirst();
|
||||
if (first.isEmpty()) {
|
||||
return Result.error("修改失败");
|
||||
}
|
||||
synchronized (DOWNLOAD) {
|
||||
if (DOWNLOAD.get()) {
|
||||
return Result.error("存在未完成任务,请等待...");
|
||||
}
|
||||
case "updateTotalEpisodeNumber" -> {
|
||||
// 更新总集数
|
||||
updateTotalEpisodeNumber();
|
||||
return;
|
||||
DOWNLOAD.set(true);
|
||||
}
|
||||
Ani downloadAni = first.get();
|
||||
ThreadUtil.execute(() -> {
|
||||
try {
|
||||
if (TorrentUtil.login()) {
|
||||
DownloadService.downloadAni(downloadAni);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String message = ExceptionUtils.getMessage(e);
|
||||
log.error(message, e);
|
||||
}
|
||||
DOWNLOAD.set(false);
|
||||
});
|
||||
return Result.success("已开始刷新RSS {}", downloadAni.getTitle());
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "将RSS转换为订阅")
|
||||
@PostMapping("/rssToAni")
|
||||
public Result<Ani> rssToAni(@RequestBody Ani ani) {
|
||||
String url = ani.getUrl();
|
||||
String type = ani.getType();
|
||||
String bgmUrl = ani.getBgmUrl();
|
||||
Assert.notBlank(url, "RSS地址 不能为空");
|
||||
if (!ReUtil.contains("http(s*)://", url)) {
|
||||
url = "https://" + url;
|
||||
}
|
||||
url = URLUtil.decode(url, "utf-8");
|
||||
try {
|
||||
Ani newAni = AniUtil.getAni(url, type, bgmUrl);
|
||||
return Result.success(newAni);
|
||||
} catch (Exception e) {
|
||||
String message = ExceptionUtils.getMessage(e);
|
||||
log.error(message, e);
|
||||
return Result.error("RSS解析失败 {}", message);
|
||||
}
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "预览订阅")
|
||||
@PostMapping("/previewAni")
|
||||
public Result<Map<String, Object>> previewAni(@RequestBody Ani ani) {
|
||||
List<Item> items = ItemsUtil.getItems(ani);
|
||||
|
||||
String downloadPath = DownloadService.getDownloadPath(ani);
|
||||
|
||||
for (Item item : items) {
|
||||
item.setLocal(false);
|
||||
File torrent = TorrentUtil.getTorrent(ani, item);
|
||||
if (torrent.exists()) {
|
||||
item.setLocal(true);
|
||||
continue;
|
||||
}
|
||||
if (DownloadService.itemDownloaded(ani, item, false)) {
|
||||
item.setLocal(true);
|
||||
}
|
||||
}
|
||||
|
||||
switch (method) {
|
||||
case "POST": {
|
||||
// 添加订阅
|
||||
post();
|
||||
return;
|
||||
}
|
||||
case "PUT": {
|
||||
// 修改订阅
|
||||
put();
|
||||
return;
|
||||
}
|
||||
case "GET": {
|
||||
// 获取订阅列表
|
||||
get();
|
||||
return;
|
||||
}
|
||||
case "DELETE": {
|
||||
// 删除订阅
|
||||
delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
List<Integer> omitList = ItemsUtil.omitList(ani, items);
|
||||
|
||||
Map<String, Object> map = Map.of(
|
||||
"downloadPath", downloadPath,
|
||||
"items", items,
|
||||
"omitList", omitList
|
||||
);
|
||||
return Result.success(map);
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "获取订阅的下载位置")
|
||||
@PostMapping("/downloadPath")
|
||||
public Result<Map<String, Object>> downloadPath(@RequestBody Ani ani) {
|
||||
String downloadPath = DownloadService.getDownloadPath(ani);
|
||||
|
||||
boolean change = false;
|
||||
Optional<Ani> first = AniUtil.ANI_LIST.stream()
|
||||
.filter(it -> it.getId().equals(ani.getId()))
|
||||
.findFirst();
|
||||
if (first.isPresent()) {
|
||||
Ani oldAni = ObjectUtil.clone(first.get());
|
||||
// 只在名称改变时移动
|
||||
oldAni.setSeason(ani.getSeason());
|
||||
String oldDownloadPath = DownloadService.getDownloadPath(oldAni);
|
||||
change = !downloadPath.equals(oldDownloadPath);
|
||||
}
|
||||
|
||||
Map<String, Object> map = Map.of(
|
||||
"change", change,
|
||||
"downloadPath", downloadPath
|
||||
);
|
||||
return Result.success(map);
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "导入订阅")
|
||||
@PostMapping("/importAni")
|
||||
public Result<Void> importAni(@RequestBody ImportAniDataDTO dto) {
|
||||
List<Ani> aniList = dto.getAniList();
|
||||
if (aniList.isEmpty()) {
|
||||
return Result.error("导入列表为空");
|
||||
}
|
||||
|
||||
ImportAniDataDTO.Conflict conflict = dto.getConflict();
|
||||
|
||||
for (Ani ani : aniList) {
|
||||
AniUtil.verify(ani);
|
||||
|
||||
String title = ani.getTitle();
|
||||
int season = ani.getSeason();
|
||||
Optional<Ani> first = AniUtil.ANI_LIST.stream()
|
||||
.filter(it -> it.getTitle().equals(title) && it.getSeason() == season)
|
||||
.findFirst();
|
||||
|
||||
if (first.isEmpty()) {
|
||||
String image = ani.getImage();
|
||||
String cover = AniUtil.saveJpg(image);
|
||||
ani.setCover(cover)
|
||||
.setId(UUID.fastUUID().toString());
|
||||
AniUtil.ANI_LIST.add(ani);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (conflict == ImportAniDataDTO.Conflict.SKIP) {
|
||||
log.info("存在冲突,已跳过 {} 第{}季", title, season);
|
||||
continue;
|
||||
}
|
||||
|
||||
log.info("存在冲突,已替换 {} 第{}季", title, season);
|
||||
String image = ani.getImage();
|
||||
String cover = AniUtil.saveJpg(image);
|
||||
ani.setCover(cover);
|
||||
|
||||
String[] ignoreProperties = new String[]{"id", "currentEpisodeNumber", "lastDownloadTime"};
|
||||
BeanUtil.copyProperties(ani, first.get(), ignoreProperties);
|
||||
}
|
||||
|
||||
AniUtil.sync();
|
||||
return Result.success("导入成功");
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "刷新封面")
|
||||
@PostMapping("/refreshCover")
|
||||
public Result<String> refreshCover(@RequestBody Ani ani) {
|
||||
String s = AniUtil.saveJpg(ani.getImage(), true);
|
||||
return Result.success(r -> r.setData(s));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.entity.Global;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.ContentType;
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.Cleanup;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class BaseController {
|
||||
/**
|
||||
* 根据文件扩展名获得ContentType
|
||||
*
|
||||
* @param filename 文件名
|
||||
* @return ContentType
|
||||
*/
|
||||
public static String getContentType(String filename) {
|
||||
if (StrUtil.isBlank(filename)) {
|
||||
return ContentType.OCTET_STREAM.getValue();
|
||||
}
|
||||
|
||||
String extName = FileUtil.extName(filename);
|
||||
|
||||
if (StrUtil.isBlank(extName)) {
|
||||
return ContentType.OCTET_STREAM.getValue();
|
||||
}
|
||||
|
||||
if (extName.equalsIgnoreCase("mkv")) {
|
||||
return "video/x-matroska";
|
||||
}
|
||||
|
||||
String mimeType = FileUtil.getMimeType(filename);
|
||||
if (StrUtil.isNotBlank(mimeType)) {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
return ContentType.OCTET_STREAM.getValue();
|
||||
}
|
||||
|
||||
public static void writeNotFound() {
|
||||
writeHtml(HttpStatus.HTTP_NOT_FOUND, "404 Not Found !");
|
||||
}
|
||||
|
||||
public static void writeHtml(Integer status, String text) {
|
||||
HttpServletResponse response = Global.RESPONSE.get();
|
||||
String html = ResourceUtil.readUtf8Str("template.html");
|
||||
html = html.replace("${text}", text);
|
||||
try {
|
||||
response.setStatus(status);
|
||||
response.setContentType("text/html;charset=UTF-8");
|
||||
|
||||
@Cleanup
|
||||
OutputStream outputStream = response.getOutputStream();
|
||||
IoUtil.writeUtf8(outputStream, true, html);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.commons.GsonStatic;
|
||||
import ani.rss.entity.*;
|
||||
import ani.rss.util.basic.HttpReq;
|
||||
import ani.rss.util.other.AniUtil;
|
||||
import ani.rss.util.other.BgmUtil;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import cn.hutool.core.lang.Opt;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import wushuo.tmdb.api.entity.Tmdb;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
public class BgmController extends BaseController {
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "搜索BGM条目")
|
||||
@PostMapping("/searchBgm")
|
||||
public Result<List<BgmInfo>> searchBgm(@RequestParam("name") String name) {
|
||||
List<BgmInfo> search = BgmUtil.search(name);
|
||||
return Result.success(search);
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "将指定id的BGM番剧转换为订阅")
|
||||
@PostMapping("/getAniBySubjectId")
|
||||
public Result<Ani> getAniBySubjectId(@RequestParam("id") String id) {
|
||||
BgmInfo bgmInfo = BgmUtil.getBgmInfo(id, true);
|
||||
Ani ani = BgmUtil.toAni(bgmInfo, AniUtil.createAni());
|
||||
ani
|
||||
.setCustomDownloadPath(true);
|
||||
return Result.success(ani);
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "获取BGM标题")
|
||||
@PostMapping("/getBgmTitle")
|
||||
public Result<String> getBgmTitle(@RequestBody Ani ani) {
|
||||
Tmdb tmdb = ani.getTmdb();
|
||||
BgmInfo bgmInfo = BgmUtil.getBgmInfo(ani);
|
||||
String finalName = BgmUtil.getFinalName(bgmInfo, tmdb);
|
||||
return Result.success(r -> r.setData(finalName));
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "评分")
|
||||
@PostMapping("/rate")
|
||||
public Result<Integer> rate(@RequestBody Ani ani) {
|
||||
String subjectId = BgmUtil.getSubjectId(ani);
|
||||
Integer score = Opt.ofNullable(ani.getScore())
|
||||
.map(Double::intValue)
|
||||
.orElse(null);
|
||||
|
||||
Integer rate = BgmUtil.rate(subjectId, score);
|
||||
return Result.success(rate).setMessage("保存评分成功");
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "获取当前BGM账号信息")
|
||||
@PostMapping("/meBgm")
|
||||
public Result<BgmMe> meBgm() {
|
||||
int expiresDays = BgmUtil.getExpiresDays();
|
||||
BgmMe me = BgmUtil.me();
|
||||
me.setExpiresDays(expiresDays);
|
||||
return Result.success(me);
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "BGM授权回调")
|
||||
@PostMapping("/bgm/oauth/callback")
|
||||
public Result<Void> callback(@RequestParam("code") String code) {
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
String bgmAppID = config.getBgmAppID();
|
||||
String bgmAppSecret = config.getBgmAppSecret();
|
||||
String bgmRedirectUri = config.getBgmRedirectUri();
|
||||
|
||||
Map<String, String> map = Map.of(
|
||||
"grant_type", "authorization_code",
|
||||
"client_id", bgmAppID,
|
||||
"client_secret", bgmAppSecret,
|
||||
"code", code,
|
||||
"redirect_uri", bgmRedirectUri
|
||||
);
|
||||
|
||||
HttpReq.post("https://bgm.tv/oauth/access_token")
|
||||
.body(GsonStatic.toJson(map))
|
||||
.then(res -> {
|
||||
HttpReq.assertStatus(res);
|
||||
JsonObject jsonObject = GsonStatic.fromJson(res.body(), JsonObject.class);
|
||||
String accessToken = jsonObject.get("access_token").getAsString();
|
||||
String refreshToken = jsonObject.get("refresh_token").getAsString();
|
||||
config.setBgmToken(accessToken)
|
||||
.setBgmRefreshToken(refreshToken);
|
||||
});
|
||||
ConfigUtil.sync();
|
||||
return Result.success("授权成功, 现在你可以关闭此窗口");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package ani.rss.action;
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.commons.FileUtils;
|
||||
import ani.rss.download.qBittorrent;
|
||||
import ani.rss.entity.*;
|
||||
@@ -8,9 +9,6 @@ import ani.rss.util.basic.HttpReq;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import ani.rss.util.other.RenameUtil;
|
||||
import ani.rss.util.other.TorrentUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
@@ -19,24 +17,162 @@ import cn.hutool.core.text.StrFormatter;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.*;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.bittorrent.TorrentFile;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 合集
|
||||
*/
|
||||
@Slf4j
|
||||
@Auth
|
||||
@Path("/collection")
|
||||
public class CollectionAction implements BaseAction {
|
||||
@RestController
|
||||
public class CollectionController extends BaseController {
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "开始下载合集")
|
||||
@PostMapping("/startCollection")
|
||||
public Result<Void> startCollection(@RequestBody CollectionInfo collectionInfo) throws IOException {
|
||||
String torrent = collectionInfo.getTorrent();
|
||||
File tempFile = FileUtil.createTempFile();
|
||||
Base64.decodeToFile(torrent, tempFile);
|
||||
TorrentFile torrentFile;
|
||||
try {
|
||||
torrentFile = new TorrentFile(tempFile);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
Ani ani = collectionInfo.getAni();
|
||||
String title = ani.getTitle();
|
||||
String subgroup = ani.getSubgroup();
|
||||
String downloadPath = ani.getDownloadPath();
|
||||
|
||||
String name = StrFormatter.format("[{}] {} 第{}季", subgroup, title, ani.getSeason());
|
||||
download(name, tempFile, downloadPath, List.of("ANI-RSS合集下载", subgroup));
|
||||
|
||||
TorrentsInfo torrentsInfo = new TorrentsInfo()
|
||||
.setHash(torrentFile.getHexHash());
|
||||
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
|
||||
List<qBittorrent.FileEntity> files = new ArrayList<>();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ThreadUtil.sleep(500);
|
||||
try {
|
||||
files.addAll(qBittorrent.files(torrentsInfo, false, config));
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
if (!files.isEmpty()) {
|
||||
// 添加下载完成
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> reNameMap = preview(collectionInfo)
|
||||
.stream()
|
||||
.map(item -> {
|
||||
Optional<qBittorrent.FileEntity> fileEntity = files.stream()
|
||||
.filter(f -> new File(f.getName()).getName().equals(new File(item.getTitle()).getName()))
|
||||
.filter(f -> f.getSize().longValue() == item.getLength())
|
||||
.findFirst();
|
||||
if (fileEntity.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String oldPath = fileEntity.get().getName();
|
||||
return item.setTitle(oldPath);
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(
|
||||
Item::getTitle,
|
||||
Item::getReName
|
||||
));
|
||||
|
||||
String host = config.getDownloadToolHost();
|
||||
|
||||
for (int i = 0; i < 30; i++) {
|
||||
for (qBittorrent.FileEntity file : files) {
|
||||
String oldPath = file.getName();
|
||||
String newPath = reNameMap.get(oldPath);
|
||||
|
||||
if (!reNameMap.containsKey(oldPath)) {
|
||||
if (!reNameMap.containsValue(oldPath) && file.getPriority() > 0) {
|
||||
HttpReq.post(host + "/api/v2/torrents/filePrio")
|
||||
.form("hash", torrentFile.getHexHash())
|
||||
.form("id", file.getIndex())
|
||||
.form("priority", 0)
|
||||
.thenFunction(HttpResponse::isOk);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
log.info("重命名 {} ==> {}", oldPath, newPath);
|
||||
HttpReq.post(host + "/api/v2/torrents/renameFile")
|
||||
.form("hash", torrentFile.getHexHash())
|
||||
.form("oldPath", oldPath)
|
||||
.form("newPath", newPath)
|
||||
.thenFunction(HttpResponse::isOk);
|
||||
}
|
||||
files.clear();
|
||||
files.addAll(qBittorrent.files(torrentsInfo, false, config));
|
||||
|
||||
if (CollUtil.containsAll(files.stream()
|
||||
.map(qBittorrent.FileEntity::getName)
|
||||
.toList(), reNameMap.values())) {
|
||||
// 所有命名已完成
|
||||
break;
|
||||
}
|
||||
// 命名有遗漏 继续
|
||||
ThreadUtil.sleep(1000);
|
||||
}
|
||||
|
||||
qBittorrent.start(torrentsInfo, config);
|
||||
return Result.success("已经开始下载合集");
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "合集预览")
|
||||
@PostMapping("/previewCollection")
|
||||
public Result<List<Item>> previewCollection(@RequestBody CollectionInfo collectionInfo) {
|
||||
List<Item> preview = preview(collectionInfo);
|
||||
preview = CollUtil.sort(new ArrayList<>(preview), Comparator.comparingDouble(it -> {
|
||||
Double episode = it.getEpisode();
|
||||
return ObjectUtil.defaultIfNull(episode, 0.0);
|
||||
}));
|
||||
return Result.success(preview);
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "获取合集字幕组")
|
||||
@PostMapping("/getCollectionSubgroup")
|
||||
public Result<String> getCollectionSubgroup(@RequestBody CollectionInfo collectionInfo) {
|
||||
List<Item> preview = preview(collectionInfo);
|
||||
preview = CollUtil.sort(new ArrayList<>(preview), Comparator.comparingDouble(it -> {
|
||||
Double episode = it.getEpisode();
|
||||
return ObjectUtil.defaultIfNull(episode, 0.0);
|
||||
}));
|
||||
String subgroup = "未知字幕组";
|
||||
String reg = "^\\[(.+?)]";
|
||||
for (Item item : preview) {
|
||||
String name = new File(item.getTitle()).getName();
|
||||
if (!ReUtil.contains(reg, name)) {
|
||||
continue;
|
||||
}
|
||||
subgroup = ReUtil.get(reg, name, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
Result<String> result = Result.success();
|
||||
result.setData(subgroup);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static synchronized List<Item> preview(CollectionInfo collectionInfo) {
|
||||
String torrent = collectionInfo.getTorrent();
|
||||
File tempFile = FileUtil.createTempFile();
|
||||
@@ -185,139 +321,4 @@ public class CollectionAction implements BaseAction {
|
||||
.then(HttpReq::assertStatus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) {
|
||||
String type = request.getParam("type");
|
||||
|
||||
CollectionInfo collectionInfo = getBody(CollectionInfo.class);
|
||||
String torrent = collectionInfo.getTorrent();
|
||||
|
||||
// 预览
|
||||
if (type.equals("preview") || type.equals("subgroup")) {
|
||||
List<Item> preview = preview(collectionInfo);
|
||||
preview = CollUtil.sort(new ArrayList<>(preview), Comparator.comparingDouble(it -> {
|
||||
Double episode = it.getEpisode();
|
||||
return ObjectUtil.defaultIfNull(episode, 0.0);
|
||||
}));
|
||||
|
||||
if (type.equals("subgroup")) {
|
||||
String subgroup = "未知字幕组";
|
||||
String reg = "^\\[(.+?)]";
|
||||
for (Item item : preview) {
|
||||
String name = new File(item.getTitle()).getName();
|
||||
if (!ReUtil.contains(reg, name)) {
|
||||
continue;
|
||||
}
|
||||
subgroup = ReUtil.get(reg, name, 1);
|
||||
break;
|
||||
}
|
||||
resultSuccess(subgroup);
|
||||
return;
|
||||
}
|
||||
resultSuccess(preview);
|
||||
}
|
||||
|
||||
// 开始下载
|
||||
if (!type.equals("start")) {
|
||||
return;
|
||||
}
|
||||
|
||||
File tempFile = FileUtil.createTempFile();
|
||||
Base64.decodeToFile(torrent, tempFile);
|
||||
TorrentFile torrentFile;
|
||||
try {
|
||||
torrentFile = new TorrentFile(tempFile);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
Ani ani = collectionInfo.getAni();
|
||||
String title = ani.getTitle();
|
||||
String subgroup = ani.getSubgroup();
|
||||
String downloadPath = ani.getDownloadPath();
|
||||
|
||||
String name = StrFormatter.format("[{}] {} 第{}季", subgroup, title, ani.getSeason());
|
||||
download(name, tempFile, downloadPath, List.of("ANI-RSS合集下载", subgroup));
|
||||
|
||||
TorrentsInfo torrentsInfo = new TorrentsInfo()
|
||||
.setHash(torrentFile.getHexHash());
|
||||
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
|
||||
List<qBittorrent.FileEntity> files = new ArrayList<>();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ThreadUtil.sleep(500);
|
||||
try {
|
||||
files.addAll(qBittorrent.files(torrentsInfo, false, config));
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
if (!files.isEmpty()) {
|
||||
// 添加下载完成
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> reNameMap = preview(collectionInfo)
|
||||
.stream()
|
||||
.map(item -> {
|
||||
Optional<qBittorrent.FileEntity> fileEntity = files.stream()
|
||||
.filter(f -> new File(f.getName()).getName().equals(new File(item.getTitle()).getName()))
|
||||
.filter(f -> f.getSize().longValue() == item.getLength())
|
||||
.findFirst();
|
||||
if (fileEntity.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String oldPath = fileEntity.get().getName();
|
||||
return item.setTitle(oldPath);
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(
|
||||
Item::getTitle,
|
||||
Item::getReName
|
||||
));
|
||||
|
||||
String host = config.getDownloadToolHost();
|
||||
|
||||
for (int i = 0; i < 30; i++) {
|
||||
for (qBittorrent.FileEntity file : files) {
|
||||
String oldPath = file.getName();
|
||||
String newPath = reNameMap.get(oldPath);
|
||||
|
||||
if (!reNameMap.containsKey(oldPath)) {
|
||||
if (!reNameMap.containsValue(oldPath) && file.getPriority() > 0) {
|
||||
HttpReq.post(host + "/api/v2/torrents/filePrio")
|
||||
.form("hash", torrentFile.getHexHash())
|
||||
.form("id", file.getIndex())
|
||||
.form("priority", 0)
|
||||
.thenFunction(HttpResponse::isOk);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
log.info("重命名 {} ==> {}", oldPath, newPath);
|
||||
HttpReq.post(host + "/api/v2/torrents/renameFile")
|
||||
.form("hash", torrentFile.getHexHash())
|
||||
.form("oldPath", oldPath)
|
||||
.form("newPath", newPath)
|
||||
.thenFunction(HttpResponse::isOk);
|
||||
}
|
||||
files.clear();
|
||||
files.addAll(qBittorrent.files(torrentsInfo, false, config));
|
||||
|
||||
if (CollUtil.containsAll(files.stream()
|
||||
.map(qBittorrent.FileEntity::getName)
|
||||
.toList(), reNameMap.values())) {
|
||||
// 所有命名已完成
|
||||
break;
|
||||
}
|
||||
// 命名有遗漏 继续
|
||||
ThreadUtil.sleep(1000);
|
||||
}
|
||||
|
||||
qBittorrent.start(torrentsInfo, config);
|
||||
resultSuccess(result ->
|
||||
result.setMessage("已经开始下载合集")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.commons.FileUtils;
|
||||
import ani.rss.commons.MavenUtils;
|
||||
import ani.rss.config.CronConfig;
|
||||
import ani.rss.download.BaseDownload;
|
||||
import ani.rss.entity.*;
|
||||
import ani.rss.service.ClearService;
|
||||
import ani.rss.service.TaskService;
|
||||
import ani.rss.util.basic.HttpReq;
|
||||
import ani.rss.util.other.AfdianUtil;
|
||||
import ani.rss.util.other.AniUtil;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import ani.rss.util.other.TorrentUtil;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.bean.copier.CopyOptions;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.text.StrFormatter;
|
||||
import cn.hutool.core.util.*;
|
||||
import cn.hutool.http.Header;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.Cleanup;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
public class ConfigController extends BaseController {
|
||||
|
||||
private final CronConfig cronConfig;
|
||||
|
||||
/**
|
||||
* 构建信息
|
||||
*/
|
||||
public String buildInfo() {
|
||||
String buildInfo = "";
|
||||
try {
|
||||
buildInfo = ResourceUtil.readUtf8Str("build_info");
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return buildInfo;
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "获取设置")
|
||||
@PostMapping("/config")
|
||||
public Result<Config> config() {
|
||||
String version = MavenUtils.getVersion();
|
||||
String buildInfo = buildInfo();
|
||||
Config config = ObjectUtil.clone(ConfigUtil.CONFIG);
|
||||
config.getLogin().setPassword("");
|
||||
config.setVersion(version)
|
||||
.setBuildInfo(buildInfo)
|
||||
.setVerifyExpirationTime(AfdianUtil.verifyExpirationTime());
|
||||
return Result.success(config);
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "修改设置")
|
||||
@PostMapping("/setConfig")
|
||||
public Result<Void> setConfig(@RequestBody Config newConfig) {
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
Login login = config.getLogin();
|
||||
String username = login.getUsername();
|
||||
String password = login.getPassword();
|
||||
Integer renameSleepSeconds = config.getRenameSleepSeconds();
|
||||
Integer sleep = config.getRssSleepMinutes();
|
||||
String download = config.getDownloadToolType();
|
||||
|
||||
newConfig.setExpirationTime(null)
|
||||
.setOutTradeNo(null)
|
||||
.setTryOut(null);
|
||||
|
||||
CopyOptions copyOptions = CopyOptions
|
||||
.create()
|
||||
.setIgnoreNullValue(true);
|
||||
|
||||
BeanUtil.copyProperties(
|
||||
newConfig,
|
||||
config,
|
||||
copyOptions
|
||||
);
|
||||
|
||||
String loginPassword = config.getLogin().getPassword();
|
||||
// 密码未发生修改
|
||||
if (StrUtil.isBlank(loginPassword)) {
|
||||
config.getLogin().setPassword(password);
|
||||
}
|
||||
String loginUsername = config.getLogin().getUsername();
|
||||
if (StrUtil.isBlank(loginUsername)) {
|
||||
config.getLogin().setUsername(username);
|
||||
}
|
||||
|
||||
Boolean proxy = config.getProxy();
|
||||
if (proxy) {
|
||||
String proxyHost = config.getProxyHost();
|
||||
Integer proxyPort = config.getProxyPort();
|
||||
if (StrUtil.isBlank(proxyHost) || Objects.isNull(proxyPort)) {
|
||||
return Result.error("代理参数不完整");
|
||||
}
|
||||
}
|
||||
|
||||
ConfigUtil.sync();
|
||||
Integer newRenameSleepSeconds = config.getRenameSleepSeconds();
|
||||
Integer newSleep = config.getRssSleepMinutes();
|
||||
|
||||
// 时间间隔发生改变,重启任务
|
||||
if (
|
||||
!Objects.equals(newSleep, sleep) ||
|
||||
!Objects.equals(newRenameSleepSeconds, renameSleepSeconds)
|
||||
) {
|
||||
TaskService.restart();
|
||||
}
|
||||
// 下载工具发生改变
|
||||
if (!download.equals(config.getDownloadToolType())) {
|
||||
TorrentUtil.load();
|
||||
}
|
||||
|
||||
return Result.success("修改成功");
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "清理缓存")
|
||||
@PostMapping("/clearCache")
|
||||
public Result<Void> clearCache() {
|
||||
File configDir = ConfigUtil.getConfigDir();
|
||||
String configDirStr = FileUtils.getAbsolutePath(configDir);
|
||||
|
||||
Set<String> covers = AniUtil.ANI_LIST
|
||||
.stream()
|
||||
.map(Ani::getCover)
|
||||
.map(s -> FileUtils.getAbsolutePath(new File(configDirStr + "/files/" + s)))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
FileUtil.mkdir(configDirStr + "/files");
|
||||
FileUtil.mkdir(configDirStr + "/img");
|
||||
|
||||
Set<File> files = FileUtil.loopFiles(configDirStr + "/files")
|
||||
.stream()
|
||||
.filter(file -> {
|
||||
String fileName = FileUtils.getAbsolutePath(file);
|
||||
return !covers.contains(fileName);
|
||||
}).collect(Collectors.toSet());
|
||||
long filesSize = files.stream()
|
||||
.mapToLong(File::length)
|
||||
.sum();
|
||||
long imgSize = FileUtil.size(new File(configDirStr + "/img"));
|
||||
|
||||
long sumSize = filesSize + imgSize;
|
||||
|
||||
if (sumSize < 1) {
|
||||
return Result.success("清理完成, 共清理{}MB", 0);
|
||||
}
|
||||
|
||||
for (File file : files) {
|
||||
FileUtil.del(file);
|
||||
ClearService.clearParentFile(file);
|
||||
}
|
||||
|
||||
FileUtil.del(configDirStr + "/img");
|
||||
|
||||
String mb = NumberUtil.decimalFormat("0.00", sumSize / 1024.0 / 1024.0);
|
||||
|
||||
return Result.success("清理完成, 共清理{}MB", mb);
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "更新trackers")
|
||||
@PostMapping("/trackersUpdate")
|
||||
public Result<Void> trackersUpdate(@RequestBody Config config) {
|
||||
cronConfig.updateTrackers(config);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "代理测试")
|
||||
@PostMapping("/testProxy")
|
||||
public Result<ProxyTest> testProxy(@RequestParam("url") String url, @RequestBody Config config) {
|
||||
url = Base64.decodeStr(url);
|
||||
|
||||
log.info(url);
|
||||
|
||||
HttpRequest httpRequest = HttpReq.get(url);
|
||||
HttpReq.setProxy(httpRequest, config);
|
||||
|
||||
ProxyTest proxyTest = new ProxyTest();
|
||||
Result<ProxyTest> result = Result.success(proxyTest);
|
||||
|
||||
long start = LocalDateTimeUtil.toEpochMilli(LocalDateTimeUtil.now());
|
||||
try {
|
||||
httpRequest
|
||||
.then(res -> {
|
||||
int status = res.getStatus();
|
||||
proxyTest.setStatus(status);
|
||||
|
||||
String title = Jsoup.parse(res.body())
|
||||
.title();
|
||||
result.setMessage(StrFormatter.format("测试成功 {}", title));
|
||||
});
|
||||
} catch (Exception e) {
|
||||
result.setMessage(e.getMessage())
|
||||
.setCode(HttpStatus.HTTP_INTERNAL_ERROR);
|
||||
}
|
||||
|
||||
long end = LocalDateTimeUtil.toEpochMilli(LocalDateTimeUtil.now());
|
||||
proxyTest.setTime(end - start);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "下载器测试")
|
||||
@PostMapping("/downloadLoginTest")
|
||||
public Result<Void> downloadLoginTest(@RequestBody Config config) {
|
||||
ConfigUtil.format(config);
|
||||
String download = config.getDownloadToolType();
|
||||
Class<Object> loadClass = ClassUtil.loadClass("ani.rss.download." + download);
|
||||
BaseDownload baseDownload = (BaseDownload) ReflectUtil.newInstance(loadClass);
|
||||
Boolean login = baseDownload.login(true, config);
|
||||
if (login) {
|
||||
return Result.success("登录成功");
|
||||
}
|
||||
return Result.error("登录失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "自定义JS")
|
||||
@GetMapping("/custom.js")
|
||||
public void customJs() throws IOException {
|
||||
HttpServletResponse response = Global.RESPONSE.get();
|
||||
response.setHeader(Header.CACHE_CONTROL.toString(), "no-store, no-cache, must-revalidate, max-age=0");
|
||||
response.setHeader(Header.PRAGMA.toString(), "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
|
||||
String customJs = ConfigUtil.CONFIG.getCustomJs();
|
||||
customJs = StrUtil.blankToDefault(customJs, "// empty js");
|
||||
String contentType = "application/javascript; charset=utf-8";
|
||||
|
||||
response.setContentType(contentType);
|
||||
@Cleanup
|
||||
OutputStream outputStream = response.getOutputStream();
|
||||
IoUtil.writeUtf8(outputStream, true, customJs);
|
||||
}
|
||||
|
||||
@Operation(summary = "自定义CSS")
|
||||
@GetMapping("/custom.css")
|
||||
public void customCss() throws IOException {
|
||||
HttpServletResponse response = Global.RESPONSE.get();
|
||||
response.setHeader(Header.CACHE_CONTROL.toString(), "no-store, no-cache, must-revalidate, max-age=0");
|
||||
response.setHeader(Header.PRAGMA.toString(), "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
|
||||
String customCss = ConfigUtil.CONFIG.getCustomCss();
|
||||
customCss = StrUtil.blankToDefault(customCss, "/* empty css */");
|
||||
String contentType = "text/css";
|
||||
|
||||
response.setContentType(contentType);
|
||||
@Cleanup
|
||||
OutputStream outputStream = response.getOutputStream();
|
||||
IoUtil.writeUtf8(outputStream, true, customCss);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,38 @@
|
||||
package ani.rss.action;
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.commons.GsonStatic;
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.entity.Config;
|
||||
import ani.rss.entity.EmbyWebHook;
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.entity.*;
|
||||
import ani.rss.enums.StringEnum;
|
||||
import ani.rss.service.DownloadService;
|
||||
import ani.rss.util.other.*;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import ani.rss.web.auth.enums.AuthType;
|
||||
import ani.rss.web.util.AuthUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.thread.ExecutorBuilder;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.Synchronized;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import wushuo.tmdb.api.entity.Tmdb;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
/**
|
||||
* WebHook
|
||||
*/
|
||||
@Slf4j
|
||||
@Auth(type = AuthType.API_KEY)
|
||||
@Path("/web_hook")
|
||||
public class WebHookAction implements BaseAction {
|
||||
@RestController
|
||||
@RequestMapping
|
||||
public class EmbyController extends BaseController {
|
||||
@Auth
|
||||
@Operation(summary = "获取媒体库")
|
||||
@PostMapping("/getEmbyViews")
|
||||
public Result<List<EmbyViews>> getEmbyViews(@RequestBody NotificationConfig notificationConfig) {
|
||||
List<EmbyViews> views = EmbyUtil.getViews(notificationConfig);
|
||||
return Result.success(views);
|
||||
}
|
||||
|
||||
private static final ExecutorService EXECUTOR = ExecutorBuilder.create()
|
||||
.setCorePoolSize(1)
|
||||
@@ -43,25 +40,19 @@ public class WebHookAction implements BaseAction {
|
||||
.setWorkQueue(new LinkedBlockingQueue<>(256))
|
||||
.build();
|
||||
|
||||
@Override
|
||||
@Synchronized("EXECUTOR")
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
String body = getBody();
|
||||
|
||||
Assert.notBlank(body, "WebHook body is empty");
|
||||
|
||||
log.debug("webhook: {}", body);
|
||||
@Auth
|
||||
@Operation(summary = "BGM自动点格子")
|
||||
@PostMapping("/embyWebHook")
|
||||
public Result<Void> embyWebHook(@RequestBody EmbyWebHook embyWebHook) {
|
||||
log.debug("webhook: {}", embyWebHook);
|
||||
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
String bgmToken = config.getBgmToken();
|
||||
if (StrUtil.isBlank(bgmToken)) {
|
||||
log.info("bgmToken 为空");
|
||||
response.sendOk();
|
||||
return;
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
EmbyWebHook embyWebHook = GsonStatic.fromJson(body, EmbyWebHook.class);
|
||||
|
||||
String event = embyWebHook.getEvent();
|
||||
|
||||
if (List.of("system.webhooktest", "system.notificationtest").contains(event)) {
|
||||
@@ -83,8 +74,7 @@ public class WebHookAction implements BaseAction {
|
||||
String ip = AuthUtil.getIp();
|
||||
log.info(s, ip, id, name, version);
|
||||
// 测试
|
||||
response.sendOk();
|
||||
return;
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
EmbyWebHook.Item item = embyWebHook.getItem();
|
||||
@@ -92,32 +82,27 @@ public class WebHookAction implements BaseAction {
|
||||
String seriesName = item.getSeriesName();
|
||||
String fileName = item.getFileName();
|
||||
if (!ReUtil.contains(StringEnum.SEASON_REG, fileName)) {
|
||||
response.sendOk();
|
||||
return;
|
||||
return Result.success();
|
||||
}
|
||||
// 季
|
||||
int season = Integer.parseInt(ReUtil.get(StringEnum.SEASON_REG, fileName, 1));
|
||||
|
||||
// 番外
|
||||
if (season < 1) {
|
||||
response.sendOk();
|
||||
return;
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
// 集 x.5
|
||||
double episode = Double.parseDouble(ReUtil.get(StringEnum.SEASON_REG, fileName, 2));
|
||||
if (ItemsUtil.is5(episode)) {
|
||||
response.sendOk();
|
||||
return;
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
response.sendOk();
|
||||
|
||||
int type = getType(embyWebHook);
|
||||
|
||||
if (type < 0) {
|
||||
// 播放状态未正确获取
|
||||
return;
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
EXECUTOR.execute(() -> {
|
||||
@@ -142,6 +127,8 @@ public class WebHookAction implements BaseAction {
|
||||
BgmUtil.collections(subjectId);
|
||||
BgmUtil.collectionsEpisodes(episodeId, type);
|
||||
});
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -0,0 +1,130 @@
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.commons.ExceptionUtils;
|
||||
import ani.rss.entity.Global;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.text.StrFormatter;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.http.Header;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.Cleanup;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
public class FileController extends BaseController {
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "获取文件")
|
||||
@GetMapping("/file")
|
||||
public void file(@RequestParam("filename") String filename) {
|
||||
if (Base64.isBase64(filename)) {
|
||||
filename = filename.replace(" ", "+");
|
||||
filename = Base64.decodeStr(filename);
|
||||
}
|
||||
|
||||
doFile(filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理文件
|
||||
*
|
||||
* @param filename 文件名
|
||||
*/
|
||||
private void doFile(String filename) {
|
||||
HttpServletRequest request = Global.REQUEST.get();
|
||||
HttpServletResponse response = Global.RESPONSE.get();
|
||||
|
||||
File file = new File(filename);
|
||||
if (!file.exists()) {
|
||||
File configDir = ConfigUtil.getConfigDir();
|
||||
file = new File(configDir + "/files/" + filename);
|
||||
if (!file.exists()) {
|
||||
writeNotFound();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasRange = false;
|
||||
long fileLength = file.length();
|
||||
long start = 0;
|
||||
long end = fileLength - 1;
|
||||
|
||||
String contentType = getContentType(file.getName());
|
||||
|
||||
response.setHeader(Header.CONTENT_DISPOSITION.toString(), StrFormatter.format("inline; filename=\"{}\"", URLUtil.encode(file.getName())));
|
||||
if (contentType.startsWith("video/")) {
|
||||
response.setContentType(contentType);
|
||||
response.setHeader("Accept-Ranges", "bytes");
|
||||
String rangeHeader = request.getHeader("Range");
|
||||
if (StrUtil.isNotBlank(rangeHeader) && rangeHeader.startsWith("bytes=")) {
|
||||
String[] range = rangeHeader.substring(6).split("-");
|
||||
if (range.length > 0) {
|
||||
start = Long.parseLong(range[0]);
|
||||
}
|
||||
if (range.length > 1) {
|
||||
end = Long.parseLong(range[1]);
|
||||
} else {
|
||||
long maxEnd = start + (1024 * 1024 * 10);
|
||||
end = Math.min(end, maxEnd);
|
||||
}
|
||||
}
|
||||
response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileLength);
|
||||
hasRange = true;
|
||||
} else {
|
||||
long maxAge = 0;
|
||||
|
||||
// 小于或者等于 3M 缓存
|
||||
if (fileLength <= 1024 * 1024 * 3) {
|
||||
// 30 天
|
||||
maxAge = 86400 * 30;
|
||||
}
|
||||
|
||||
response.setHeader(Header.CACHE_CONTROL.toString(), "private, max-age=" + maxAge);
|
||||
response.setContentType(contentType);
|
||||
}
|
||||
|
||||
try {
|
||||
if (hasRange) {
|
||||
long length = end - start;
|
||||
response.setStatus(206);
|
||||
@Cleanup
|
||||
OutputStream out = response.getOutputStream();
|
||||
@Cleanup
|
||||
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
|
||||
randomAccessFile.seek(start);
|
||||
@Cleanup
|
||||
FileChannel channel = randomAccessFile.getChannel();
|
||||
@Cleanup
|
||||
InputStream inputStream = Channels.newInputStream(channel);
|
||||
IoUtil.copy(inputStream, out, 40960, length, null);
|
||||
} else {
|
||||
@Cleanup
|
||||
InputStream inputStream = FileUtil.getInputStream(file);
|
||||
@Cleanup
|
||||
OutputStream outputStream = response.getOutputStream();
|
||||
IoUtil.copy(inputStream, outputStream);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String message = ExceptionUtils.getMessage(e);
|
||||
log.debug(message, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,29 @@
|
||||
package ani.rss.action;
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.commons.CacheUtils;
|
||||
import ani.rss.entity.Config;
|
||||
import ani.rss.entity.Login;
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.util.other.AuthUtil;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import ani.rss.web.util.AuthUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static ani.rss.web.util.AuthUtil.limitLoginAttempts;
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
@Slf4j
|
||||
@Auth(value = false)
|
||||
@Path("/login")
|
||||
public class LoginAction implements BaseAction {
|
||||
@RestController
|
||||
public class LoginController extends BaseController {
|
||||
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
limitLoginAttempts(false);
|
||||
@Operation(summary = "登录")
|
||||
@PostMapping("/login")
|
||||
public Result<String> login(@RequestBody Login myLogin) {
|
||||
AuthUtil.limitLoginAttempts(false);
|
||||
|
||||
Login myLogin = getBody(Login.class);
|
||||
Config config = ConfigUtil.CONFIG;
|
||||
Login login = config.getLogin();
|
||||
|
||||
@@ -57,25 +49,25 @@ public class LoginAction implements BaseAction {
|
||||
clearLimitLoginAttempts();
|
||||
log.info("登录成功 {} ip: {}", username, ip);
|
||||
String s = AuthUtil.getAuth(myLogin);
|
||||
resultSuccess(s);
|
||||
return;
|
||||
return new Result<String>()
|
||||
.setCode(200)
|
||||
.setMessage("登录成功")
|
||||
.setData(s);
|
||||
}
|
||||
limitLoginAttempts(true);
|
||||
AuthUtil.limitLoginAttempts(true);
|
||||
log.warn("登陆失败 {} ip: {}", myUsername, ip);
|
||||
ThreadUtil.sleep(RandomUtil.randomInt(500, 5000));
|
||||
resultErrorMsg("用户名或密码错误");
|
||||
return Result.error("用户名或密码错误");
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除限制尝试次数
|
||||
*/
|
||||
public static void clearLimitLoginAttempts() {
|
||||
private void clearLimitLoginAttempts() {
|
||||
String ip = AuthUtil.getIp();
|
||||
String key = "LimitLoginAttempts#" + ip;
|
||||
if (CacheUtils.containsKey(key)) {
|
||||
CacheUtils.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.entity.Global;
|
||||
import ani.rss.entity.Log;
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.util.basic.LogUtil;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.text.StrFormatter;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.ZipUtil;
|
||||
import cn.hutool.http.Header;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.Cleanup;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
public class LogsController extends BaseController {
|
||||
List<Log> LOG_LIST = LogUtil.LOG_LIST;
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "日志")
|
||||
@PostMapping("/logs")
|
||||
public Result<List<Log>> list() {
|
||||
return Result.success(LOG_LIST);
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "清理日志")
|
||||
@PostMapping("/clearLogs")
|
||||
public Result<Void> clearLogs() {
|
||||
LOG_LIST.clear();
|
||||
log.info("清理日志");
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "下载日志")
|
||||
@GetMapping("/downloadLogs")
|
||||
public void downloadLogs() throws IOException {
|
||||
File configDir = ConfigUtil.getConfigDir();
|
||||
String logsPath = configDir + "/logs";
|
||||
|
||||
String filename = "logs.zip";
|
||||
|
||||
String contentType = getContentType(filename);
|
||||
|
||||
HttpServletResponse response = Global.RESPONSE.get();
|
||||
|
||||
response.setContentType(contentType);
|
||||
response.setHeader(Header.CONTENT_DISPOSITION.toString(), StrFormatter.format("inline; filename=\"{}\"", filename));
|
||||
|
||||
@Cleanup
|
||||
OutputStream outputStream = response.getOutputStream();
|
||||
|
||||
ZipUtil.zip(outputStream, StandardCharsets.UTF_8, false, name -> {
|
||||
if (FileUtil.isDirectory(name)) {
|
||||
return true;
|
||||
}
|
||||
String extName = FileUtil.extName(name);
|
||||
if (StrUtil.isBlank(extName)) {
|
||||
return false;
|
||||
}
|
||||
return extName.equals("log");
|
||||
}, new File(logsPath));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.commons.GsonStatic;
|
||||
import ani.rss.entity.Global;
|
||||
import ani.rss.entity.Mikan;
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.entity.TorrentsInfo;
|
||||
import ani.rss.util.basic.HttpReq;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import ani.rss.util.other.MikanUtil;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.http.Header;
|
||||
import cn.hutool.http.HttpConnection;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.Cleanup;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@RestController
|
||||
public class MikanController extends BaseController {
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "获取Mikan番剧列表")
|
||||
@PostMapping("/mikan")
|
||||
public Result<Mikan> mikan(@RequestParam("text") String text, @RequestBody Mikan.Season season) {
|
||||
Mikan list = MikanUtil.list(text, season);
|
||||
return Result.success(list);
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "获取Mikan番剧的字幕组列表")
|
||||
@PostMapping("/mikanGroup")
|
||||
public Result<List<Mikan.Group>> mikanGroup(@RequestParam("url") String url) {
|
||||
List<Mikan.Group> groups = MikanUtil.getGroups(url);
|
||||
|
||||
List<String> regexItemList = List.of(
|
||||
"1920[Xx]1080", "3840[Xx]2160", "1080[Pp]", "4[Kk]", "720[Pp]",
|
||||
"繁", "简", "日",
|
||||
"cht|Cht|CHT", "chs|Chs|CHS", "hevc|Hevc|HEVC",
|
||||
"10bit|10Bit|10BIT", "h265|H265", "h264|H264",
|
||||
"内嵌", "内封", "外挂",
|
||||
"mp4|MP4", "mkv|MKV"
|
||||
);
|
||||
|
||||
for (Mikan.Group group : groups) {
|
||||
Set<String> tags = new HashSet<>();
|
||||
List<List<Mikan.RegexItem>> regexList = new ArrayList<>();
|
||||
List<TorrentsInfo> items = group.getItems();
|
||||
for (TorrentsInfo item : items) {
|
||||
String name = item.getName();
|
||||
List<Mikan.RegexItem> regexItems = new ArrayList<>();
|
||||
for (String regex : regexItemList) {
|
||||
if (!ReUtil.contains(regex, name)) {
|
||||
continue;
|
||||
}
|
||||
String label = ReUtil.get(regex, name, 0);
|
||||
label = label.toUpperCase();
|
||||
Mikan.RegexItem regexItem = new Mikan.RegexItem(label, regex);
|
||||
regexItems.add(regexItem);
|
||||
tags.add(label);
|
||||
}
|
||||
regexItems = CollUtil.distinct(regexItems, GsonStatic::toJson, true);
|
||||
regexList.add(regexItems);
|
||||
}
|
||||
|
||||
regexList = CollUtil.distinct(regexList, GsonStatic::toJson, true);
|
||||
group.setRegexList(regexList)
|
||||
.setTags(tags);
|
||||
}
|
||||
return Result.success(groups);
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "获取Mikan封面")
|
||||
@GetMapping("/mikanCover")
|
||||
public void MikanCover(@RequestParam("img") String img) {
|
||||
if (Base64.isBase64(img)) {
|
||||
img = img.replace(" ", "+");
|
||||
img = Base64.decodeStr(img);
|
||||
}
|
||||
HttpServletResponse response = Global.RESPONSE.get();
|
||||
|
||||
// 30 天
|
||||
long maxAge = 86400 * 30;
|
||||
|
||||
response.setHeader(Header.CACHE_CONTROL.toString(), "private, max-age=" + maxAge);
|
||||
|
||||
String contentType = getContentType(URLUtil.getPath(img));
|
||||
|
||||
File configDir = ConfigUtil.getConfigDir();
|
||||
|
||||
File file = new File(URLUtil.getPath(img));
|
||||
configDir = new File(configDir + "/img/" + file.getParentFile().getName());
|
||||
FileUtil.mkdir(configDir);
|
||||
|
||||
File imgFile = new File(configDir, file.getName());
|
||||
if (imgFile.exists()) {
|
||||
try {
|
||||
response.setContentType(contentType);
|
||||
@Cleanup
|
||||
InputStream inputStream = FileUtil.getInputStream(imgFile);
|
||||
@Cleanup
|
||||
OutputStream outputStream = response.getOutputStream();
|
||||
IoUtil.copy(inputStream, outputStream);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
getImg(img, is -> {
|
||||
try {
|
||||
response.setContentType(contentType);
|
||||
FileUtil.writeFromStream(is, imgFile, true);
|
||||
@Cleanup
|
||||
BufferedInputStream inputStream = FileUtil.getInputStream(imgFile);
|
||||
@Cleanup
|
||||
ServletOutputStream outputStream = response.getOutputStream();
|
||||
IoUtil.copy(inputStream, outputStream);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void getImg(String url, Consumer<InputStream> consumer) {
|
||||
URI host = URLUtil.getHost(URLUtil.url(url));
|
||||
HttpReq.get(url)
|
||||
.then(res -> {
|
||||
HttpConnection httpConnection = (HttpConnection) ReflectUtil.getFieldValue(res, "httpConnection");
|
||||
URI host1 = URLUtil.getHost(httpConnection.getUrl());
|
||||
if (host.toString().equals(host1.toString())) {
|
||||
try {
|
||||
@Cleanup
|
||||
InputStream inputStream = res.bodyStream();
|
||||
consumer.accept(inputStream);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
String newUrl = url.replace(host.toString(), host1.toString());
|
||||
getImg(newUrl, consumer);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,53 +1,35 @@
|
||||
package ani.rss.action;
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.entity.BgmInfo;
|
||||
import ani.rss.entity.NotificationConfig;
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.enums.NotificationStatusEnum;
|
||||
import ani.rss.enums.NotificationTypeEnum;
|
||||
import ani.rss.notification.BaseNotification;
|
||||
import ani.rss.notification.TelegramNotification;
|
||||
import ani.rss.util.other.AniUtil;
|
||||
import ani.rss.util.other.BgmUtil;
|
||||
import ani.rss.util.other.NotificationUtil;
|
||||
import ani.rss.util.other.TmdbUtils;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import wushuo.tmdb.api.entity.Tmdb;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 通知
|
||||
*/
|
||||
@Auth
|
||||
@Path("/notification")
|
||||
public class NotificationAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
String type = request.getParam("type");
|
||||
if ("test".equals(type)) {
|
||||
test();
|
||||
return;
|
||||
}
|
||||
@RestController
|
||||
public class NotificationController extends BaseController {
|
||||
|
||||
if ("add".equals(type)) {
|
||||
add();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void add() {
|
||||
NotificationConfig notificationConfig = NotificationConfig.createNotificationConfig();
|
||||
resultSuccess(notificationConfig);
|
||||
}
|
||||
|
||||
private void test() {
|
||||
NotificationConfig notificationConfig = getBody(NotificationConfig.class);
|
||||
@Auth
|
||||
@Operation(summary = "测试通知")
|
||||
@PostMapping("/testNotification")
|
||||
public Result<Void> testNotification(@RequestBody NotificationConfig notificationConfig) {
|
||||
NotificationTypeEnum notificationType = notificationConfig.getNotificationType();
|
||||
Class<? extends BaseNotification> aClass = NotificationUtil.NOTIFICATION_MAP.get(notificationType);
|
||||
BaseNotification baseNotification = ReflectUtil.newInstance(aClass);
|
||||
@@ -69,10 +51,26 @@ public class NotificationAction implements BaseAction {
|
||||
|
||||
try {
|
||||
baseNotification.test(notificationConfig, ani, "test", NotificationStatusEnum.DOWNLOAD_START);
|
||||
resultSuccess();
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
resultErrorMsg(e.getMessage());
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "新的通知")
|
||||
@PostMapping("/newNotification")
|
||||
public Result<NotificationConfig> newNotification() {
|
||||
NotificationConfig notificationConfig = NotificationConfig.createNotificationConfig();
|
||||
return Result.success(notificationConfig);
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "获取TG最近消息")
|
||||
@PostMapping("/getTgUpdates")
|
||||
public Result<Map<String, String>> getUpdates(@RequestBody NotificationConfig notificationConfig) {
|
||||
Map<String, String> map = TelegramNotification.getUpdates(notificationConfig);
|
||||
return Result.success(map);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +1,98 @@
|
||||
package ani.rss.action;
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.commons.FileUtils;
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.entity.PlayItem;
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.enums.StringEnum;
|
||||
import ani.rss.service.DownloadService;
|
||||
import ani.rss.util.other.AniUtil;
|
||||
import ani.rss.web.action.BaseAction;
|
||||
import ani.rss.web.annotation.Auth;
|
||||
import ani.rss.web.annotation.Path;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.matthewn4444.ebml.EBMLReader;
|
||||
import com.matthewn4444.ebml.subtitles.Subtitles;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.Cleanup;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 视频列表
|
||||
*/
|
||||
@Auth
|
||||
@Slf4j
|
||||
@Path("/playlist")
|
||||
public class PlaylistAction implements BaseAction {
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
||||
Ani ani = getBody(Ani.class);
|
||||
@RestController
|
||||
public class PlayController extends BaseController {
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "获取内封字幕")
|
||||
@PostMapping("/getSubtitles")
|
||||
public Result<List<PlayItem.Subtitles>> getSubtitles(@RequestBody Map<String, String> map) throws IOException {
|
||||
String file = map.get("file");
|
||||
Assert.notBlank(file);
|
||||
|
||||
if (Base64.isBase64(file)) {
|
||||
file = Base64.decodeStr(file);
|
||||
}
|
||||
|
||||
List<PlayItem.Subtitles> subtitlesList = new ArrayList<>();
|
||||
|
||||
String extName = FileUtil.extName(file);
|
||||
if (StrUtil.isBlank(extName)) {
|
||||
return Result.success(subtitlesList);
|
||||
}
|
||||
|
||||
if (!"mkv".equals(extName)) {
|
||||
return Result.success(subtitlesList);
|
||||
}
|
||||
|
||||
Assert.isTrue(FileUtil.exist(file), "视频文件不存在");
|
||||
|
||||
@Cleanup
|
||||
EBMLReader reader = new EBMLReader(file);
|
||||
if (!reader.readHeader()) {
|
||||
return Result.success(subtitlesList);
|
||||
}
|
||||
reader.readTracks();
|
||||
reader.readCues();
|
||||
|
||||
for (int i = 0; i < reader.getCuesCount(); i++) {
|
||||
reader.readSubtitlesInCueFrame(i);
|
||||
}
|
||||
|
||||
List<Subtitles> subtitles = reader.getSubtitles();
|
||||
for (Subtitles subtitle : subtitles) {
|
||||
String name = subtitle.getName();
|
||||
String presentableName = subtitle.getPresentableName();
|
||||
String contents = subtitle.getContentsToVTT();
|
||||
PlayItem.Subtitles sub = new PlayItem.Subtitles();
|
||||
sub.setContent(contents)
|
||||
.setName(name)
|
||||
.setHtml(presentableName)
|
||||
.setUrl("")
|
||||
.setType("vtt");
|
||||
subtitlesList.add(sub);
|
||||
}
|
||||
|
||||
return Result.success(subtitlesList);
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "获取视频列表")
|
||||
@PostMapping("/playList")
|
||||
public Result<List<PlayItem>> playList(@RequestBody Ani ani) {
|
||||
String url = ani.getUrl();
|
||||
Optional<Ani> first = AniUtil.ANI_LIST
|
||||
.stream()
|
||||
.filter(it -> url.equals(it.getUrl()))
|
||||
.findFirst();
|
||||
if (first.isEmpty()) {
|
||||
resultError();
|
||||
return;
|
||||
return Result.error();
|
||||
}
|
||||
ani = first.get();
|
||||
|
||||
@@ -49,7 +102,7 @@ public class PlaylistAction implements BaseAction {
|
||||
// 按照集数排序
|
||||
CollUtil.sort(collect, Comparator.comparingDouble(PlayItem::getEpisode));
|
||||
|
||||
resultSuccess(collect);
|
||||
return Result.success(collect);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -0,0 +1,29 @@
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.service.ScrapeService;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class ScrapeController extends BaseController {
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "刮削")
|
||||
@PostMapping("/scrape")
|
||||
public Result<Void> scrape(@RequestParam("force") Boolean force, @RequestBody Ani ani) {
|
||||
ThreadUtil.execute(() ->
|
||||
ScrapeService.scrape(ani, force)
|
||||
);
|
||||
|
||||
String title = ani.getTitle();
|
||||
|
||||
return Result.success("已开始刮削 {}", title);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.util.other.TmdbUtils;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import wushuo.tmdb.api.entity.Tmdb;
|
||||
import wushuo.tmdb.api.entity.TmdbGroup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
public class ThemoviedbController extends BaseController {
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "获取TMDB标题")
|
||||
@PostMapping("/getThemoviedbName")
|
||||
public Result<Ani> getThemoviedbName(@RequestBody Ani ani) {
|
||||
String themoviedbName = TmdbUtils.getFinalName(ani);
|
||||
Result<Ani> result = new Result<Ani>()
|
||||
.setCode(HttpStatus.HTTP_OK)
|
||||
.setMessage("获取TMDB成功")
|
||||
.setData(ani.setThemoviedbName(themoviedbName));
|
||||
if (StrUtil.isBlank(themoviedbName)) {
|
||||
result.setCode(HttpStatus.HTTP_INTERNAL_ERROR)
|
||||
.setMessage("获取TMDB失败");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "获取TMDB剧集组")
|
||||
@PostMapping("/getThemoviedbGroup")
|
||||
public Result<List<TmdbGroup>> getThemoviedbGroup(@RequestBody Ani ani) {
|
||||
Tmdb tmdb = ani.getTmdb();
|
||||
Assert.notNull(tmdb, "tmdb is null");
|
||||
Assert.notBlank(tmdb.getId(), "tmdb is null");
|
||||
List<TmdbGroup> tmdbGroup = TmdbUtils.getTmdbGroup(tmdb);
|
||||
return Result.success(tmdbGroup);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.commons.FileUtils;
|
||||
import ani.rss.entity.Ani;
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.util.other.AniUtil;
|
||||
import ani.rss.util.other.TorrentUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
public class TorrentController extends BaseController {
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "删除缓存种子")
|
||||
@PostMapping("/deleteTorrent")
|
||||
public Result<Void> del(@RequestParam("id") String id, @RequestParam("hash") String hash) {
|
||||
Optional<Ani> first = AniUtil.ANI_LIST.stream()
|
||||
.filter(ani -> id.equals(ani.getId()))
|
||||
.findFirst();
|
||||
if (first.isEmpty()) {
|
||||
return Result.error("此订阅不存在");
|
||||
}
|
||||
|
||||
List<String> hashList = StrUtil.split(hash, ",", true, true);
|
||||
Ani ani = first.get();
|
||||
File torrentDir = TorrentUtil.getTorrentDir(ani);
|
||||
File[] files = FileUtils.listFiles(torrentDir);
|
||||
for (File file : files) {
|
||||
String name = FileUtil.mainName(file);
|
||||
if (hashList.contains(name)) {
|
||||
log.info("删除种子 {}", file);
|
||||
FileUtil.del(file);
|
||||
}
|
||||
}
|
||||
return Result.success("删除完成");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.entity.TorrentsInfo;
|
||||
import ani.rss.util.other.TorrentUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
public class TorrentsInfosController extends BaseController {
|
||||
|
||||
@Auth
|
||||
@Operation(summary = "下载列表")
|
||||
@PostMapping("/torrentsInfos")
|
||||
public Result<List<TorrentsInfo>> torrentsInfos() {
|
||||
List<TorrentsInfo> torrentsInfos = TorrentUtil.getTorrentsInfos();
|
||||
return Result.success(torrentsInfos);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package ani.rss.controller;
|
||||
|
||||
import ani.rss.annotation.Auth;
|
||||
import ani.rss.entity.Global;
|
||||
import ani.rss.entity.Result;
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@RestController
|
||||
public class UploadController extends BaseController {
|
||||
@Auth
|
||||
@Operation(summary = "上传文件")
|
||||
@PostMapping("/upload")
|
||||
public Result<Object> upload(@RequestParam("file") MultipartFile file) throws IOException {
|
||||
HttpServletRequest request = Global.REQUEST.get();
|
||||
String type = request.getParameter("type");
|
||||
byte[] fileContent = file.getBytes();
|
||||
if ("getBase64".equals(type)) {
|
||||
return Result.success(r ->
|
||||
r.setData(Base64.encode(fileContent))
|
||||
);
|
||||
}
|
||||
|
||||
String s = SecureUtil.md5(new ByteArrayInputStream(fileContent));
|
||||
String fileName = file.getOriginalFilename();
|
||||
String saveName = s + "." + FileUtil.extName(fileName);
|
||||
|
||||
File configDir = ConfigUtil.getConfigDir();
|
||||
FileUtil.mkdir(configDir + "/files/" + s.charAt(0));
|
||||
FileUtil.writeBytes(fileContent, configDir + "/files/" + s.charAt(0) + "/" + saveName);
|
||||
return new Result<>()
|
||||
.setMessage("上传完成")
|
||||
.setData(s.charAt(0) + "/" + saveName);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,9 @@ import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
@@ -28,6 +30,8 @@ import java.util.*;
|
||||
* Aria2
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class Aria2 implements BaseDownload {
|
||||
private Config config;
|
||||
|
||||
@@ -25,13 +25,17 @@ import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.Method;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class OpenList implements BaseDownload {
|
||||
private Config config;
|
||||
|
||||
@@ -22,7 +22,9 @@ import cn.hutool.http.HttpResponse;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
@@ -34,15 +36,15 @@ import java.util.Set;
|
||||
* Transmission
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class Transmission implements BaseDownload {
|
||||
private String host = "";
|
||||
private String authorization = "";
|
||||
private String sessionId = "";
|
||||
private Config config;
|
||||
|
||||
@Override
|
||||
public Boolean login(Boolean test, Config config) {
|
||||
this.config = config;
|
||||
String username = config.getDownloadToolUsername();
|
||||
String password = config.getDownloadToolPassword();
|
||||
host = config.getDownloadToolHost();
|
||||
@@ -24,8 +24,10 @@ import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
@@ -34,7 +36,10 @@ import java.util.*;
|
||||
* qBittorrent
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class qBittorrent implements BaseDownload {
|
||||
|
||||
private Config config;
|
||||
|
||||
/**
|
||||
@@ -1,5 +1,6 @@
|
||||
package ani.rss.entity;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@@ -11,39 +12,47 @@ import java.util.Date;
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "关于")
|
||||
public class About implements Serializable {
|
||||
/**
|
||||
* 版本
|
||||
*/
|
||||
@Schema(description = "版本")
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* 最新版本
|
||||
*/
|
||||
@Schema(description = "最新版本")
|
||||
private String latest;
|
||||
|
||||
/**
|
||||
* 是否需要更新
|
||||
*/
|
||||
@Schema(description = "是否需要更新")
|
||||
private Boolean update;
|
||||
|
||||
/**
|
||||
* 是否允许自动更新
|
||||
*/
|
||||
@Schema(description = "是否允许自动更新")
|
||||
private Boolean autoUpdate;
|
||||
|
||||
/**
|
||||
* 下载地址
|
||||
*/
|
||||
@Schema(description = "下载地址")
|
||||
private String downloadUrl;
|
||||
|
||||
/**
|
||||
* 更新内容
|
||||
*/
|
||||
@Schema(description = "更新内容")
|
||||
private String markdownBody;
|
||||
|
||||
/**
|
||||
* 发布时间
|
||||
*/
|
||||
@Schema(description = "发布时间")
|
||||
private Date date;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package ani.rss.entity;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import wushuo.tmdb.api.entity.Tmdb;
|
||||
@@ -13,20 +14,24 @@ import java.util.List;
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "订阅")
|
||||
public class Ani implements Serializable {
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@Schema(description = "id")
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 不在页面显示
|
||||
*/
|
||||
@Schema(description = "不在页面显示")
|
||||
private String mikanTitle;
|
||||
|
||||
/**
|
||||
* RSS URL
|
||||
*/
|
||||
@Schema(description = "RSS URL")
|
||||
private String url;
|
||||
|
||||
private Boolean exists;
|
||||
@@ -34,239 +39,288 @@ public class Ani implements Serializable {
|
||||
/**
|
||||
* 备用rss
|
||||
*/
|
||||
@Schema(description = "备用rss")
|
||||
private List<StandbyRss> standbyRssList;
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
@Schema(description = "标题")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 日语标题 来源于BGM
|
||||
*/
|
||||
@Schema(description = "日语标题 来源于BGM")
|
||||
private String jpTitle;
|
||||
|
||||
/**
|
||||
* 剧集偏移
|
||||
*/
|
||||
@Schema(description = "剧集偏移")
|
||||
private Integer offset;
|
||||
|
||||
/**
|
||||
* 年度
|
||||
*/
|
||||
@Schema(description = "年度")
|
||||
private Integer year;
|
||||
|
||||
/**
|
||||
* 月
|
||||
*/
|
||||
@Schema(description = "月")
|
||||
private Integer month;
|
||||
|
||||
/**
|
||||
* 日
|
||||
*/
|
||||
@Schema(description = "日")
|
||||
private Integer date;
|
||||
|
||||
/**
|
||||
* 星期 1表示周日,2表示周一
|
||||
*/
|
||||
@Schema(description = "星期 1表示周日,2表示周一")
|
||||
private Integer week;
|
||||
|
||||
/**
|
||||
* 季度
|
||||
*/
|
||||
@Schema(description = "季度")
|
||||
private Integer season;
|
||||
|
||||
/**
|
||||
* 封面本地保存位置
|
||||
*/
|
||||
@Schema(description = "封面本地保存位置")
|
||||
private String cover;
|
||||
|
||||
/**
|
||||
* 图片 https://
|
||||
*/
|
||||
@Schema(description = "图片 https://")
|
||||
private String image;
|
||||
|
||||
/**
|
||||
* 字幕组
|
||||
*/
|
||||
@Schema(description = "字幕组")
|
||||
private String subgroup;
|
||||
|
||||
/**
|
||||
* 匹配
|
||||
*/
|
||||
@Schema(description = "匹配")
|
||||
private List<String> match;
|
||||
|
||||
/**
|
||||
* 排除
|
||||
*/
|
||||
@Schema(description = "排除")
|
||||
private List<String> exclude;
|
||||
|
||||
/**
|
||||
* 是否启用全局排除
|
||||
*/
|
||||
@Schema(description = "是否启用全局排除")
|
||||
private Boolean globalExclude;
|
||||
|
||||
/**
|
||||
* 剧场版 or OVA
|
||||
*/
|
||||
@Schema(description = "剧场版 or OVA")
|
||||
private Boolean ova;
|
||||
|
||||
/**
|
||||
* 拼音
|
||||
*/
|
||||
@Schema(description = "拼音")
|
||||
private String pinyin;
|
||||
|
||||
/**
|
||||
* 拼音
|
||||
*/
|
||||
@Schema(description = "拼音首字母")
|
||||
private String pinyinInitials;
|
||||
|
||||
/**
|
||||
* 启用
|
||||
*/
|
||||
@Schema(description = "启用")
|
||||
private Boolean enable;
|
||||
|
||||
/**
|
||||
* 当前集数
|
||||
*/
|
||||
@Schema(description = "当前集数")
|
||||
private Integer currentEpisodeNumber;
|
||||
|
||||
/**
|
||||
* 总集数
|
||||
*/
|
||||
@Schema(description = "总集数")
|
||||
private Integer totalEpisodeNumber;
|
||||
|
||||
@Schema(description = "TheMovieDB 名称")
|
||||
private String themoviedbName;
|
||||
|
||||
@Schema(description = "类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "BGM 地址")
|
||||
private String bgmUrl;
|
||||
|
||||
/**
|
||||
* 自定义下载位置
|
||||
*/
|
||||
@Schema(description = "自定义下载位置")
|
||||
private Boolean customDownloadPath;
|
||||
|
||||
/**
|
||||
* 自定义下载位置
|
||||
*/
|
||||
@Schema(description = "自定义下载位置路径")
|
||||
private String downloadPath;
|
||||
|
||||
/**
|
||||
* 评分
|
||||
*/
|
||||
@Schema(description = "评分")
|
||||
private Double score;
|
||||
|
||||
/**
|
||||
* 自定义集数获取规则
|
||||
*/
|
||||
@Schema(description = "自定义集数获取规则")
|
||||
private Boolean customEpisode;
|
||||
|
||||
/**
|
||||
* 自定义集数获取规则
|
||||
*/
|
||||
@Schema(description = "自定义集数获取规则表达式")
|
||||
private String customEpisodeStr;
|
||||
|
||||
/**
|
||||
* 自定义集数获取规则 groupIndex
|
||||
*/
|
||||
@Schema(description = "自定义集数获取规则 groupIndex")
|
||||
private Integer customEpisodeGroupIndex;
|
||||
|
||||
/**
|
||||
* 遗漏检测
|
||||
*/
|
||||
@Schema(description = "遗漏检测")
|
||||
private Boolean omit;
|
||||
|
||||
/**
|
||||
* 只下载最新集
|
||||
*/
|
||||
@Schema(description = "只下载最新集")
|
||||
private Boolean downloadNew;
|
||||
|
||||
/**
|
||||
* 不进行下载的集
|
||||
*/
|
||||
@Schema(description = "不进行下载的集")
|
||||
private List<Double> notDownload;
|
||||
|
||||
/**
|
||||
* tmdb 相关信息
|
||||
*/
|
||||
@Schema(description = "TMDB 相关信息")
|
||||
private Tmdb tmdb;
|
||||
|
||||
/**
|
||||
* 自动上传
|
||||
*/
|
||||
@Schema(description = "自动上传")
|
||||
private Boolean upload;
|
||||
|
||||
/**
|
||||
* 摸鱼
|
||||
*/
|
||||
@Schema(description = "摸鱼")
|
||||
private Boolean procrastinating;
|
||||
|
||||
/**
|
||||
* 自定义重命名模版
|
||||
*/
|
||||
@Schema(description = "自定义重命名模版开关")
|
||||
private Boolean customRenameTemplateEnable;
|
||||
|
||||
/**
|
||||
* 自定义重命名模版
|
||||
*/
|
||||
@Schema(description = "自定义重命名模版")
|
||||
private String customRenameTemplate;
|
||||
|
||||
/**
|
||||
* 自定义优先保留开关
|
||||
*/
|
||||
@Schema(description = "自定义优先保留开关")
|
||||
private Boolean customPriorityKeywordsEnable;
|
||||
|
||||
/**
|
||||
* 自定义优先保留关键词列表
|
||||
*/
|
||||
@Schema(description = "自定义优先保留关键词列表")
|
||||
private List<String> customPriorityKeywords;
|
||||
|
||||
/**
|
||||
* 上次下载完成时间
|
||||
*/
|
||||
@Schema(description = "上次下载完成时间")
|
||||
private Long lastDownloadTime;
|
||||
|
||||
/**
|
||||
* 自定义上传
|
||||
*/
|
||||
@SerializedName(value = "customUploadEnable", alternate = "customAlistPath")
|
||||
@Schema(description = "自定义上传开关")
|
||||
private Boolean customUploadEnable;
|
||||
|
||||
/**
|
||||
* 自定义上传
|
||||
*/
|
||||
@SerializedName(value = "customUploadPathTarget", alternate = "alistPath")
|
||||
@Schema(description = "自定义上传目标路径")
|
||||
private String customUploadPathTarget;
|
||||
|
||||
/**
|
||||
* 消息通知
|
||||
*/
|
||||
@Schema(description = "消息通知")
|
||||
private Boolean message;
|
||||
|
||||
/**
|
||||
* 完结迁移
|
||||
*/
|
||||
@Schema(description = "完结迁移")
|
||||
private Boolean completed;
|
||||
|
||||
/**
|
||||
* 自定义完结迁移
|
||||
*/
|
||||
@Schema(description = "自定义完结迁移开关")
|
||||
private Boolean customCompleted;
|
||||
|
||||
/**
|
||||
* 自定义完结迁移
|
||||
*/
|
||||
@Schema(description = "自定义完结迁移路径模版")
|
||||
private String customCompletedPathTemplate;
|
||||
|
||||
/**
|
||||
* 自定义标签开关
|
||||
*/
|
||||
@Schema(description = "自定义标签开关")
|
||||
private Boolean customTagsEnable;
|
||||
|
||||
/**
|
||||
* 单个订阅自定义标签
|
||||
*/
|
||||
@Schema(description = "单个订阅自定义标签")
|
||||
private List<String> customTags;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ani.rss.entity;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@@ -14,6 +15,7 @@ import java.util.Map;
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "Bgm番剧信息")
|
||||
public class BgmInfo implements Serializable {
|
||||
private String id;
|
||||
|
||||
@@ -22,37 +24,44 @@ public class BgmInfo implements Serializable {
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
@Schema(description = "名称")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 中文名称
|
||||
*/
|
||||
@SerializedName(value = "nameCn", alternate = "name_cn")
|
||||
@Schema(description = "中文名称")
|
||||
private String nameCn;
|
||||
|
||||
/**
|
||||
* 集数
|
||||
*/
|
||||
@Schema(description = "集数")
|
||||
private Integer eps;
|
||||
|
||||
/**
|
||||
* 时间
|
||||
*/
|
||||
@Schema(description = "时间")
|
||||
private Date date;
|
||||
|
||||
/**
|
||||
* 图片
|
||||
*/
|
||||
@Schema(description = "图片")
|
||||
private Images images;
|
||||
|
||||
/**
|
||||
* 季度
|
||||
*/
|
||||
@Schema(description = "季度")
|
||||
private Integer season;
|
||||
|
||||
/**
|
||||
* 平台 OVA/剧场版
|
||||
*/
|
||||
@Schema(description = "平台 OVA/剧场版")
|
||||
private String platform;
|
||||
|
||||
private List<Tag> tags;
|
||||
@@ -64,6 +73,7 @@ public class BgmInfo implements Serializable {
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "封面图片")
|
||||
public static class Images implements Serializable {
|
||||
private String small;
|
||||
private String grid;
|
||||
@@ -77,12 +87,16 @@ public class BgmInfo implements Serializable {
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "标签")
|
||||
public static class Tag implements Serializable {
|
||||
@Schema(description = "标签名")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "计数")
|
||||
private String count;
|
||||
|
||||
@SerializedName(value = "totalCont", alternate = "total_cont")
|
||||
@Schema(description = "总计数")
|
||||
private String totalCont;
|
||||
}
|
||||
|
||||
@@ -91,25 +105,30 @@ public class BgmInfo implements Serializable {
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "评分")
|
||||
public static class Rating implements Serializable {
|
||||
/**
|
||||
* 级别
|
||||
*/
|
||||
@Schema(description = "级别")
|
||||
private Integer rank;
|
||||
|
||||
/**
|
||||
* 评分
|
||||
*/
|
||||
@Schema(description = "评分")
|
||||
private Double score;
|
||||
|
||||
/**
|
||||
* 评分数
|
||||
*/
|
||||
@Schema(description = "评分数")
|
||||
private Integer total;
|
||||
|
||||
/**
|
||||
* 各阶段评分数
|
||||
*/
|
||||
@Schema(description = "各阶段评分数")
|
||||
private Map<String, Integer> count;
|
||||
}
|
||||
}
|
||||
54
ani-rss-application/src/main/java/ani/rss/entity/BgmMe.java
Normal file
54
ani-rss-application/src/main/java/ani/rss/entity/BgmMe.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package ani.rss.entity;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "BGM 用户信息")
|
||||
public class BgmMe implements Serializable {
|
||||
@Schema(description = "头像")
|
||||
private Avatar avatar;
|
||||
@Schema(description = "用户ID")
|
||||
private Integer id;
|
||||
@Schema(description = "签名")
|
||||
private String sign;
|
||||
@Schema(description = "主页地址")
|
||||
private String url;
|
||||
@Schema(description = "用户名")
|
||||
private String username;
|
||||
@Schema(description = "昵称")
|
||||
private String nickname;
|
||||
@SerializedName(value = "userGroup", alternate = "user_group")
|
||||
@Schema(description = "用户组")
|
||||
private String userGroup;
|
||||
@SerializedName(value = "regTime", alternate = "reg_time")
|
||||
@Schema(description = "注册时间")
|
||||
private Date regTime;
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
@SerializedName(value = "timeOffset", alternate = "time_offset")
|
||||
@Schema(description = "时区偏移")
|
||||
private Integer timeOffset;
|
||||
@SerializedName(value = "expiresDays", alternate = "expires_days")
|
||||
@Schema(description = "过期天数")
|
||||
private Integer expiresDays;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "头像")
|
||||
public static class Avatar implements Serializable {
|
||||
@Schema(description = "large")
|
||||
private String large;
|
||||
@Schema(description = "medium")
|
||||
private String medium;
|
||||
@Schema(description = "small")
|
||||
private String small;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package ani.rss.entity;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@@ -10,19 +11,23 @@ import java.io.Serializable;
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "合集")
|
||||
public class CollectionInfo implements Serializable {
|
||||
/**
|
||||
* 种子文件 base64
|
||||
*/
|
||||
@Schema(description = "种子文件 base64")
|
||||
private String torrent;
|
||||
|
||||
/**
|
||||
* 订阅
|
||||
*/
|
||||
@Schema(description = "订阅")
|
||||
private Ani ani;
|
||||
|
||||
/**
|
||||
* bgm
|
||||
*/
|
||||
@Schema(description = "bgm")
|
||||
private BgmInfo bgmInfo;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package ani.rss.entity;
|
||||
|
||||
import ani.rss.enums.BgmTokenTypeEnum;
|
||||
import ani.rss.enums.SortTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@@ -13,305 +14,366 @@ import java.util.List;
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "设置")
|
||||
public class Config implements Serializable {
|
||||
/**
|
||||
* Mikan Host
|
||||
*/
|
||||
@Schema(description = "Mikan Host")
|
||||
private String mikanHost;
|
||||
|
||||
/**
|
||||
* tmdbApi
|
||||
*/
|
||||
@Schema(description = "TMDB API")
|
||||
private String tmdbApi;
|
||||
|
||||
/**
|
||||
* tmdbApiKey
|
||||
*/
|
||||
@Schema(description = "TMDB API Key")
|
||||
private String tmdbApiKey;
|
||||
|
||||
/**
|
||||
* 仅获取动漫
|
||||
*/
|
||||
@Schema(description = "仅获取动漫")
|
||||
private Boolean tmdbAnime;
|
||||
|
||||
/**
|
||||
* 下载工具
|
||||
*/
|
||||
@Schema(description = "下载工具类型")
|
||||
private String downloadToolType;
|
||||
|
||||
/**
|
||||
* 下载重试次数
|
||||
*/
|
||||
@Schema(description = "下载重试次数")
|
||||
private Integer downloadRetry;
|
||||
|
||||
/**
|
||||
* 下载工具 地址
|
||||
*/
|
||||
@Schema(description = "下载工具地址")
|
||||
private String downloadToolHost;
|
||||
|
||||
/**
|
||||
* 下载工具 用户名
|
||||
*/
|
||||
@Schema(description = "下载工具用户名")
|
||||
private String downloadToolUsername;
|
||||
|
||||
/**
|
||||
* 下载工具 密码
|
||||
*/
|
||||
@Schema(description = "下载工具密码")
|
||||
private String downloadToolPassword;
|
||||
|
||||
/**
|
||||
* qb下载时,使用qb自身的保存路径配置(未下载完成的使用临时目录,复制种子文件)
|
||||
*/
|
||||
@Schema(description = "使用 qb 自身保存路径")
|
||||
private Boolean qbUseDownloadPath;
|
||||
|
||||
/**
|
||||
* 分享率
|
||||
*/
|
||||
@Schema(description = "分享率")
|
||||
private Integer ratioLimit;
|
||||
|
||||
/**
|
||||
* 总做种时长
|
||||
*/
|
||||
@Schema(description = "总做种时长")
|
||||
private Integer seedingTimeLimit;
|
||||
|
||||
/**
|
||||
* 非活跃时长
|
||||
*/
|
||||
@Schema(description = "非活跃时长")
|
||||
private Integer inactiveSeedingTimeLimit;
|
||||
|
||||
/**
|
||||
* 下载路径
|
||||
*/
|
||||
@Schema(description = "下载路径模版")
|
||||
private String downloadPathTemplate;
|
||||
|
||||
/**
|
||||
* 剧场版下载路径
|
||||
*/
|
||||
@Schema(description = "剧场版下载路径模版")
|
||||
private String ovaDownloadPathTemplate;
|
||||
|
||||
/**
|
||||
* 自定义标签
|
||||
*/
|
||||
@Schema(description = "自定义标签")
|
||||
private List<String> customTags;
|
||||
|
||||
/*
|
||||
* 优先保留开关
|
||||
*/
|
||||
@Schema(description = "优先保留开关")
|
||||
private Boolean priorityKeywordsEnable;
|
||||
|
||||
/**
|
||||
* 优先保留关键词列表
|
||||
*/
|
||||
@Schema(description = "优先保留关键词列表")
|
||||
private List<String> priorityKeywords;
|
||||
|
||||
/**
|
||||
* 延迟下载
|
||||
*/
|
||||
@Schema(description = "延迟下载(分钟)")
|
||||
private Integer delayedDownload;
|
||||
|
||||
/**
|
||||
* 显示评分
|
||||
*/
|
||||
@Schema(description = "显示评分")
|
||||
private Boolean scoreShow;
|
||||
|
||||
/**
|
||||
* RSS 间隔(分钟)
|
||||
*/
|
||||
@Schema(description = "RSS 间隔(分钟)")
|
||||
private Integer rssSleepMinutes;
|
||||
|
||||
/**
|
||||
* 重命名间隔(秒)
|
||||
*/
|
||||
@Schema(description = "重命名间隔(秒)")
|
||||
private Integer renameSleepSeconds;
|
||||
|
||||
/**
|
||||
* 自动重命名
|
||||
*/
|
||||
@Schema(description = "自动重命名")
|
||||
private Boolean rename;
|
||||
|
||||
/**
|
||||
* rss开关
|
||||
*/
|
||||
@Schema(description = "RSS 开关")
|
||||
private Boolean rss;
|
||||
|
||||
/**
|
||||
* rss 超时时间 秒
|
||||
*/
|
||||
@Schema(description = "RSS 超时时间(秒)")
|
||||
private Integer rssTimeout;
|
||||
|
||||
/**
|
||||
* 文件已下载自动跳过
|
||||
*/
|
||||
@Schema(description = "文件已下载自动跳过")
|
||||
private Boolean fileExist;
|
||||
|
||||
/**
|
||||
* 等待做种完毕
|
||||
*/
|
||||
@Schema(description = "等待做种完毕")
|
||||
private Boolean awaitStalledUP;
|
||||
|
||||
/**
|
||||
* 自动删除已完成任务
|
||||
*/
|
||||
@Schema(description = "自动删除已完成任务")
|
||||
private Boolean delete;
|
||||
|
||||
/**
|
||||
* 仅在主RSS更新后删除备用RSS
|
||||
*/
|
||||
@Schema(description = "主RSS更新后删除备用RSS")
|
||||
private Boolean deleteStandbyRSSOnly;
|
||||
|
||||
/**
|
||||
* 自动推断剧集偏移
|
||||
*/
|
||||
@Schema(description = "自动推断剧集偏移")
|
||||
private Boolean offset;
|
||||
|
||||
/**
|
||||
* 获取标题时带上年份
|
||||
*/
|
||||
@Schema(description = "获取标题时带上年份")
|
||||
private Boolean titleYear;
|
||||
|
||||
/**
|
||||
* 自动禁用已完结番剧的订阅
|
||||
*/
|
||||
@Schema(description = "自动禁用已完结番剧订阅")
|
||||
private Boolean autoDisabled;
|
||||
|
||||
/**
|
||||
* 自动跳过 x.5 集数
|
||||
*/
|
||||
@Schema(description = "自动跳过 x.5 集数")
|
||||
private Boolean skip5;
|
||||
|
||||
/**
|
||||
* 备用RSS
|
||||
*/
|
||||
@Schema(description = "备用RSS")
|
||||
private Boolean standbyRss;
|
||||
|
||||
/**
|
||||
* 多字幕组共存模式
|
||||
*/
|
||||
@Schema(description = "多字幕组共存模式")
|
||||
private Boolean coexist;
|
||||
|
||||
/**
|
||||
* 最大日志条数
|
||||
*/
|
||||
@Schema(description = "最大日志条数")
|
||||
private Integer logsMax;
|
||||
|
||||
/**
|
||||
* DEBUG
|
||||
*/
|
||||
@Schema(description = "DEBUG")
|
||||
private Boolean debug;
|
||||
|
||||
/**
|
||||
* 仅启用主rss摸鱼检测
|
||||
*/
|
||||
@Schema(description = "仅启用主RSS摸鱼检测")
|
||||
private Boolean procrastinatingMasterOnly;
|
||||
|
||||
/**
|
||||
* 代理是否开启
|
||||
*/
|
||||
@Schema(description = "代理是否开启")
|
||||
private Boolean proxy;
|
||||
|
||||
/**
|
||||
* 代理host
|
||||
*/
|
||||
@Schema(description = "代理 host")
|
||||
private String proxyHost;
|
||||
|
||||
/**
|
||||
* 代理端口
|
||||
*/
|
||||
@Schema(description = "代理端口")
|
||||
private Integer proxyPort;
|
||||
|
||||
/**
|
||||
* 代理用户名
|
||||
*/
|
||||
@Schema(description = "代理用户名")
|
||||
private String proxyUsername;
|
||||
|
||||
/**
|
||||
* 代理密码
|
||||
*/
|
||||
@Schema(description = "代理密码")
|
||||
private String proxyPassword;
|
||||
|
||||
/**
|
||||
* 同时下载数量限制
|
||||
*/
|
||||
@Schema(description = "同时下载数量限制")
|
||||
private Integer downloadCount;
|
||||
|
||||
/**
|
||||
* 登录信息
|
||||
*/
|
||||
@Schema(description = "登录信息")
|
||||
private Login login;
|
||||
|
||||
/**
|
||||
* 禁止多端登录
|
||||
*/
|
||||
@Schema(description = "禁止多端登录")
|
||||
private Boolean multiLoginForbidden;
|
||||
|
||||
/**
|
||||
* 登录有效时间/小时
|
||||
*/
|
||||
@Schema(description = "登录有效时间(小时)")
|
||||
private Integer loginEffectiveHours;
|
||||
|
||||
/**
|
||||
* 全局排除
|
||||
*/
|
||||
@Schema(description = "全局排除")
|
||||
private List<String> exclude;
|
||||
|
||||
/**
|
||||
* 默认导入全局排除
|
||||
*/
|
||||
@Schema(description = "默认导入全局排除")
|
||||
private Boolean importExclude;
|
||||
|
||||
/**
|
||||
* 默认启用全局排除
|
||||
*/
|
||||
@Schema(description = "默认启用全局排除")
|
||||
private Boolean enabledExclude;
|
||||
|
||||
/**
|
||||
* BGM日语标题
|
||||
*/
|
||||
@Schema(description = "BGM日语标题")
|
||||
private Boolean bgmJpName;
|
||||
|
||||
/**
|
||||
* tmdb
|
||||
*/
|
||||
@Schema(description = "启用 TMDB")
|
||||
private Boolean tmdb;
|
||||
|
||||
/**
|
||||
* 获取标题时带有tmdbId
|
||||
*/
|
||||
@Schema(description = "标题带 TMDB ID")
|
||||
private Boolean tmdbId;
|
||||
|
||||
/**
|
||||
* 剧集标题是否支持plex命名方式
|
||||
*/
|
||||
@Schema(description = "Plex 命名方式")
|
||||
private Boolean tmdbIdPlexMode;
|
||||
|
||||
/**
|
||||
* tmdb 语言
|
||||
*/
|
||||
@Schema(description = "TMDB 语言")
|
||||
private String tmdbLanguage;
|
||||
|
||||
/**
|
||||
* 获取罗马音
|
||||
*/
|
||||
@Schema(description = "获取罗马音")
|
||||
private Boolean tmdbRomaji;
|
||||
|
||||
/**
|
||||
* 开启ip白名单
|
||||
*/
|
||||
@Schema(description = "开启 IP 白名单")
|
||||
private Boolean ipWhitelist;
|
||||
|
||||
/**
|
||||
* ip白名单
|
||||
*/
|
||||
@Schema(description = "IP 白名单")
|
||||
private String ipWhitelistStr;
|
||||
|
||||
/**
|
||||
* 显示已下载视频列表
|
||||
*/
|
||||
@Schema(description = "显示已下载视频列表")
|
||||
private Boolean showPlaylist;
|
||||
|
||||
/**
|
||||
* 检测遗漏集数
|
||||
*/
|
||||
@Schema(description = "检测遗漏集数")
|
||||
private Boolean omit;
|
||||
|
||||
/**
|
||||
@@ -319,295 +381,354 @@ public class Config implements Serializable {
|
||||
* <p>
|
||||
* INPUT or AUTO
|
||||
*/
|
||||
@Schema(description = "BGM Token 类型")
|
||||
private BgmTokenTypeEnum bgmTokenType;
|
||||
|
||||
/**
|
||||
* bgmToken
|
||||
*/
|
||||
@Schema(description = "BGM Token")
|
||||
private String bgmToken;
|
||||
|
||||
/**
|
||||
* bgmAppID
|
||||
*/
|
||||
@Schema(description = "BGM App ID")
|
||||
private String bgmAppID;
|
||||
|
||||
/**
|
||||
* bgmAppID
|
||||
*/
|
||||
@Schema(description = "BGM App Secret")
|
||||
private String bgmAppSecret;
|
||||
|
||||
/**
|
||||
* bgmRefreshToken
|
||||
*/
|
||||
@Schema(description = "BGM Refresh Token")
|
||||
private String bgmRefreshToken;
|
||||
|
||||
/**
|
||||
* bgmRedirectUri
|
||||
*/
|
||||
@Schema(description = "BGM Redirect URI")
|
||||
private String bgmRedirectUri;
|
||||
|
||||
/**
|
||||
* api key
|
||||
*/
|
||||
@Schema(description = "API Key")
|
||||
private String apiKey;
|
||||
|
||||
/**
|
||||
* 按星期展示
|
||||
*/
|
||||
@Schema(description = "按星期展示")
|
||||
private Boolean weekShow;
|
||||
|
||||
/**
|
||||
* 只下载最新集
|
||||
*/
|
||||
@Schema(description = "只下载最新集")
|
||||
private Boolean downloadNew;
|
||||
|
||||
/**
|
||||
* 仅允许内网ip访问
|
||||
*/
|
||||
@Schema(description = "仅允许内网 IP 访问")
|
||||
private Boolean innerIP;
|
||||
|
||||
/**
|
||||
* 重命名模版
|
||||
*/
|
||||
@Schema(description = "重命名模版")
|
||||
private String renameTemplate;
|
||||
|
||||
/**
|
||||
* 重命名时剔除 年份 如 (2024)
|
||||
*/
|
||||
@Schema(description = "重命名剔除年份")
|
||||
private Boolean renameDelYear;
|
||||
|
||||
/**
|
||||
* 重命名时剔除 tmdbId [tmdbid=242143]
|
||||
*/
|
||||
@Schema(description = "重命名剔除 TMDB ID")
|
||||
private Boolean renameDelTmdbId;
|
||||
|
||||
/**
|
||||
* 校验登录IP
|
||||
*/
|
||||
@Schema(description = "校验登录 IP")
|
||||
private Boolean verifyLoginIp;
|
||||
|
||||
/**
|
||||
* 自动更新 trackers
|
||||
*/
|
||||
@Schema(description = "自动更新 trackers")
|
||||
private Boolean autoTrackersUpdate;
|
||||
|
||||
/**
|
||||
* Trackers更新地址
|
||||
*/
|
||||
@Schema(description = "Trackers 更新地址")
|
||||
private String trackersUpdateUrls;
|
||||
|
||||
/**
|
||||
* 消息模版
|
||||
*/
|
||||
@Schema(description = "消息模版")
|
||||
private String notificationTemplate;
|
||||
|
||||
/**
|
||||
* 自动更新
|
||||
*/
|
||||
@Schema(description = "自动更新")
|
||||
private Boolean autoUpdate;
|
||||
|
||||
/**
|
||||
* 版本
|
||||
*/
|
||||
@Schema(description = "版本")
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* 获取BGM封面图片质量
|
||||
*/
|
||||
@Schema(description = "BGM 封面图片质量")
|
||||
private String bgmImage;
|
||||
|
||||
/**
|
||||
* 自定义CSS
|
||||
*/
|
||||
@Schema(description = "自定义 CSS")
|
||||
private String customCss;
|
||||
|
||||
/**
|
||||
* 自定义JS
|
||||
*/
|
||||
@Schema(description = "自定义 JS")
|
||||
private String customJs;
|
||||
|
||||
/**
|
||||
* 自定义集数获取规则
|
||||
*/
|
||||
@Schema(description = "自定义集数获取规则")
|
||||
private Boolean customEpisode;
|
||||
|
||||
/**
|
||||
* 自定义集数获取规则
|
||||
*/
|
||||
@Schema(description = "自定义集数获取规则表达式")
|
||||
private String customEpisodeStr;
|
||||
|
||||
/**
|
||||
* 自定义集数获取规则 groupIndex
|
||||
*/
|
||||
@Schema(description = "自定义集数获取规则 groupIndex")
|
||||
private Integer customEpisodeGroupIndex;
|
||||
|
||||
/**
|
||||
* OpenList driver
|
||||
*/
|
||||
@Schema(description = "OpenList Driver")
|
||||
private String provider;
|
||||
|
||||
/**
|
||||
* 添加行订阅是是否开启自动上传
|
||||
*/
|
||||
@Schema(description = "新增订阅自动上传")
|
||||
private Boolean upload;
|
||||
|
||||
/**
|
||||
* 上传速度限制
|
||||
*/
|
||||
@Schema(description = "上传速度限制")
|
||||
private Long upLimit;
|
||||
|
||||
/**
|
||||
* 下载速度限制
|
||||
*/
|
||||
@Schema(description = "下载速度限制")
|
||||
private Long dlLimit;
|
||||
|
||||
/**
|
||||
* 捐赠过期时间
|
||||
*/
|
||||
@Schema(description = "捐赠过期时间")
|
||||
private Long expirationTime;
|
||||
|
||||
/**
|
||||
* 爱发电订单号
|
||||
*/
|
||||
@Schema(description = "爱发电订单号")
|
||||
private String outTradeNo;
|
||||
|
||||
/**
|
||||
* 捐赠或试用是否过期
|
||||
*/
|
||||
@Schema(description = "捐赠或试用是否过期")
|
||||
private Boolean verifyExpirationTime;
|
||||
|
||||
/**
|
||||
* 试用
|
||||
*/
|
||||
@Schema(description = "试用")
|
||||
private Boolean tryOut;
|
||||
|
||||
/**
|
||||
* 摸鱼
|
||||
*/
|
||||
@Schema(description = "摸鱼")
|
||||
private Boolean procrastinating;
|
||||
|
||||
/**
|
||||
* 摸鱼天数
|
||||
*/
|
||||
@Schema(description = "摸鱼天数")
|
||||
private Integer procrastinatingDay;
|
||||
|
||||
/**
|
||||
* github 加速
|
||||
*/
|
||||
@Schema(description = "GitHub 加速")
|
||||
private String github;
|
||||
|
||||
/**
|
||||
* 自定义github加速
|
||||
*/
|
||||
@Schema(description = "自定义 GitHub 加速")
|
||||
private Boolean customGithub;
|
||||
|
||||
/**
|
||||
* 自定义github加速网址
|
||||
*/
|
||||
@Schema(description = "自定义 GitHub 加速地址")
|
||||
private String customGithubUrl;
|
||||
|
||||
/**
|
||||
* github Token
|
||||
*/
|
||||
@Schema(description = "GitHub Token")
|
||||
private String githubToken;
|
||||
|
||||
/**
|
||||
* 开启 OpenList 列表刷新
|
||||
*/
|
||||
@Schema(description = "开启 OpenList 列表刷新")
|
||||
private Boolean alistRefresh;
|
||||
|
||||
/**
|
||||
* OpenList 刷新延迟
|
||||
*/
|
||||
@Schema(description = "OpenList 刷新延迟")
|
||||
private Long alistRefreshDelayed;
|
||||
|
||||
/**
|
||||
* 自动更新总集数信息
|
||||
*/
|
||||
@Schema(description = "自动更新总集数信息")
|
||||
private Boolean updateTotalEpisodeNumber;
|
||||
|
||||
/**
|
||||
* 强制更新总集数信息
|
||||
*/
|
||||
@Schema(description = "强制更新总集数信息")
|
||||
private Boolean forceUpdateTotalEpisodeNumber;
|
||||
|
||||
/**
|
||||
* OpenList 离线超时 分钟
|
||||
*/
|
||||
@Schema(description = "OpenList 离线超时(分钟)")
|
||||
private Integer alistDownloadTimeout;
|
||||
|
||||
/**
|
||||
* OpenList 下载重试次数
|
||||
*/
|
||||
@Schema(description = "OpenList 下载重试次数")
|
||||
private Long alistDownloadRetryNumber;
|
||||
|
||||
/**
|
||||
* 设置备份
|
||||
*/
|
||||
@Schema(description = "设置备份")
|
||||
private Boolean configBackup;
|
||||
|
||||
/**
|
||||
* 备份天数
|
||||
*/
|
||||
@Schema(description = "备份天数")
|
||||
private Integer configBackupDay;
|
||||
|
||||
/**
|
||||
* 展示最后更新时间
|
||||
*/
|
||||
@Schema(description = "展示最后更新时间")
|
||||
private Boolean showLastDownloadTime;
|
||||
|
||||
/**
|
||||
* 番剧完结迁移
|
||||
*/
|
||||
@Schema(description = "番剧完结迁移")
|
||||
private Boolean completed;
|
||||
|
||||
/**
|
||||
* 番剧完结迁移位置
|
||||
*/
|
||||
@Schema(description = "番剧完结迁移位置")
|
||||
private String completedPathTemplate;
|
||||
|
||||
/**
|
||||
* 通知
|
||||
*/
|
||||
@Schema(description = "通知配置列表")
|
||||
private List<NotificationConfig> notificationConfigList;
|
||||
|
||||
/**
|
||||
* 添加订阅时自动复制主rss至备用rss
|
||||
*/
|
||||
@Schema(description = "添加订阅时复制主RSS至备用")
|
||||
private Boolean copyMasterToStandby;
|
||||
|
||||
/**
|
||||
* 排序方式
|
||||
*/
|
||||
@Schema(description = "排序方式")
|
||||
private SortTypeEnum sortType;
|
||||
|
||||
/**
|
||||
* 代理列表
|
||||
*/
|
||||
@Schema(description = "代理列表")
|
||||
private String proxyList;
|
||||
|
||||
/**
|
||||
* 刮削开关
|
||||
*/
|
||||
@Schema(description = "刮削开关")
|
||||
private Boolean scrape;
|
||||
|
||||
/**
|
||||
* 重名的订阅将允许被替换
|
||||
*/
|
||||
@Schema(description = "重名订阅允许替换")
|
||||
private Boolean replace;
|
||||
|
||||
/**
|
||||
* 最大文件名长度 不包含后缀 如: .mkv .mp4
|
||||
*/
|
||||
@Schema(description = "最大文件名长度(不含后缀)")
|
||||
private Integer maxFileNameLength;
|
||||
|
||||
/**
|
||||
* 限制尝试次数
|
||||
*/
|
||||
@Schema(description = "限制尝试次数")
|
||||
private Boolean limitLoginAttempts;
|
||||
|
||||
/**
|
||||
* 构建信息
|
||||
*/
|
||||
@Schema(description = "构建信息")
|
||||
private String buildInfo;
|
||||
}
|
||||
@@ -2,8 +2,10 @@ package ani.rss.entity;
|
||||
|
||||
import ani.rss.util.other.ConfigUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import wushuo.tmdb.api.entity.TmdbConfig;
|
||||
|
||||
@Schema(description = "自定义 TMDB 配置")
|
||||
public class CustomTmdbConfig extends TmdbConfig {
|
||||
|
||||
public final static Config CONFIG = ConfigUtil.CONFIG;
|
||||
@@ -1,5 +1,6 @@
|
||||
package ani.rss.entity;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@@ -10,7 +11,10 @@ import java.io.Serializable;
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "Emby 媒体库")
|
||||
public class EmbyViews implements Serializable {
|
||||
@Schema(description = "id")
|
||||
private String id;
|
||||
@Schema(description = "名称")
|
||||
private String name;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package ani.rss.entity;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@@ -11,33 +12,43 @@ import java.io.Serializable;
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "EmbyWebHook")
|
||||
public class EmbyWebHook implements Serializable {
|
||||
|
||||
@SerializedName(value = "title", alternate = "Title")
|
||||
@Schema(description = "标题")
|
||||
private String title;
|
||||
|
||||
@SerializedName(value = "description", alternate = "Description")
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
@SerializedName(value = "date", alternate = "Date")
|
||||
@Schema(description = "日期")
|
||||
private String date;
|
||||
|
||||
@SerializedName(value = "event", alternate = "Event")
|
||||
@Schema(description = "事件")
|
||||
private String event;
|
||||
|
||||
@SerializedName(value = "severity", alternate = "Severity")
|
||||
@Schema(description = "严重级别")
|
||||
private String severity;
|
||||
|
||||
@SerializedName(value = "user", alternate = "User")
|
||||
@Schema(description = "用户信息")
|
||||
private User user;
|
||||
|
||||
@SerializedName(value = "server", alternate = "Server")
|
||||
@Schema(description = "服务器信息")
|
||||
private Server server;
|
||||
|
||||
@SerializedName(value = "item", alternate = "Item")
|
||||
@Schema(description = "项目信息")
|
||||
private Item item;
|
||||
|
||||
@SerializedName(value = "playbackInfo", alternate = "PlaybackInfo")
|
||||
@Schema(description = "播放信息")
|
||||
private PlaybackInfo playbackInfo;
|
||||
|
||||
/**
|
||||
@@ -45,23 +56,27 @@ public class EmbyWebHook implements Serializable {
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "项目信息")
|
||||
public static class Item implements Serializable {
|
||||
/**
|
||||
* 文件路径
|
||||
*/
|
||||
@SerializedName(value = "path", alternate = "Path")
|
||||
@Schema(description = "文件路径")
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* 剧集名
|
||||
*/
|
||||
@SerializedName(value = "seriesName", alternate = "SeriesName")
|
||||
@Schema(description = "剧集名")
|
||||
private String seriesName;
|
||||
|
||||
/**
|
||||
* 文件名
|
||||
*/
|
||||
@SerializedName(value = "fileName", alternate = "FileName")
|
||||
@Schema(description = "文件名")
|
||||
private String fileName;
|
||||
}
|
||||
|
||||
@@ -70,17 +85,20 @@ public class EmbyWebHook implements Serializable {
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "用户信息")
|
||||
public static class User implements Serializable {
|
||||
/**
|
||||
* 用户 Id
|
||||
*/
|
||||
@SerializedName(value = "id", alternate = "Id")
|
||||
@Schema(description = "用户 Id")
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
@SerializedName(value = "name", alternate = "Name")
|
||||
@Schema(description = "用户名称")
|
||||
private String name;
|
||||
}
|
||||
|
||||
@@ -89,23 +107,27 @@ public class EmbyWebHook implements Serializable {
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "服务器信息")
|
||||
public static class Server implements Serializable {
|
||||
/**
|
||||
* 服务器 Id
|
||||
*/
|
||||
@SerializedName(value = "id", alternate = "Id")
|
||||
@Schema(description = "服务器 Id")
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 服务器名称
|
||||
*/
|
||||
@SerializedName(value = "name", alternate = "Name")
|
||||
@Schema(description = "服务器名称")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 服务器版本号
|
||||
*/
|
||||
@SerializedName(value = "version", alternate = "Version")
|
||||
@Schema(description = "服务器版本号")
|
||||
private String version;
|
||||
}
|
||||
|
||||
@@ -114,11 +136,13 @@ public class EmbyWebHook implements Serializable {
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "播放信息")
|
||||
public static class PlaybackInfo implements Serializable {
|
||||
/**
|
||||
* 是否播放完成
|
||||
*/
|
||||
@SerializedName(value = "playedToCompletion", alternate = "PlayedToCompletion")
|
||||
@Schema(description = "是否播放完成")
|
||||
private Boolean playedToCompletion;
|
||||
}
|
||||
|
||||
21
ani-rss-application/src/main/java/ani/rss/entity/Global.java
Normal file
21
ani-rss-application/src/main/java/ani/rss/entity/Global.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package ani.rss.entity;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "全局变量")
|
||||
public class Global implements Serializable {
|
||||
public static List<String> ARGS = new ArrayList<>();
|
||||
|
||||
public static final ThreadLocal<HttpServletRequest> REQUEST = new ThreadLocal<>();
|
||||
public static final ThreadLocal<HttpServletResponse> RESPONSE = new ThreadLocal<>();
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package ani.rss.entity;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@@ -11,59 +12,71 @@ import java.util.Date;
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "下载项")
|
||||
public class Item implements Serializable {
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
@Schema(description = "标题")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 重命名
|
||||
*/
|
||||
@Schema(description = "重命名")
|
||||
private String reName;
|
||||
|
||||
/**
|
||||
* 种子
|
||||
*/
|
||||
@Schema(description = "种子")
|
||||
private String torrent;
|
||||
|
||||
/**
|
||||
* infoHash
|
||||
*/
|
||||
@Schema(description = "infoHash")
|
||||
private String infoHash;
|
||||
|
||||
/**
|
||||
* 集数
|
||||
*/
|
||||
@Schema(description = "集数")
|
||||
private Double episode;
|
||||
|
||||
/**
|
||||
* 大小
|
||||
*/
|
||||
@Schema(description = "大小")
|
||||
private String size;
|
||||
|
||||
/**
|
||||
* 大小
|
||||
*/
|
||||
@Schema(description = "大小")
|
||||
private Long length;
|
||||
|
||||
/**
|
||||
* 本地已存在
|
||||
*/
|
||||
@Schema(description = "本地已存在")
|
||||
private Boolean local;
|
||||
|
||||
/**
|
||||
* 主 rss
|
||||
*/
|
||||
@Schema(description = "主 rss")
|
||||
private Boolean master;
|
||||
|
||||
/**
|
||||
* 字幕组
|
||||
*/
|
||||
@Schema(description = "字幕组")
|
||||
private String subgroup;
|
||||
|
||||
/**
|
||||
* 发布时间
|
||||
*/
|
||||
@Schema(description = "发布时间")
|
||||
private Date pubDate;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package ani.rss.entity;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@@ -10,25 +11,30 @@ import java.io.Serializable;
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "日志")
|
||||
public class Log implements Serializable {
|
||||
|
||||
/**
|
||||
* 日志信息
|
||||
*/
|
||||
@Schema(description = "日志信息")
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 日志级别
|
||||
*/
|
||||
@Schema(description = "日志级别")
|
||||
private String level;
|
||||
|
||||
/**
|
||||
* 类路径
|
||||
*/
|
||||
@Schema(description = "类路径")
|
||||
private String loggerName;
|
||||
|
||||
/**
|
||||
* 线程名
|
||||
*/
|
||||
@Schema(description = "线程名")
|
||||
private String threadName;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package ani.rss.entity;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@@ -10,21 +11,26 @@ import java.io.Serializable;
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "登录")
|
||||
public class Login implements Serializable {
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@Schema(description = "用户名")
|
||||
private String username;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@Schema(description = "密码")
|
||||
private String password;
|
||||
/**
|
||||
* ip
|
||||
*/
|
||||
@Schema(description = "ip")
|
||||
private String ip;
|
||||
/**
|
||||
* key
|
||||
*/
|
||||
@Schema(description = "key")
|
||||
private String key;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user