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
|
- name: Build with Project
|
||||||
run: |
|
run: |
|
||||||
bash ./package.sh
|
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
|
echo "version=v$version" >> $GITHUB_ENV
|
||||||
- name: Upload to Artifacts
|
- name: Upload to Artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Artifacts
|
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
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
13
.github/workflows/deploy.yml
vendored
13
.github/workflows/deploy.yml
vendored
@@ -16,10 +16,9 @@ jobs:
|
|||||||
- name: Build with Project
|
- name: Build with Project
|
||||||
run: |
|
run: |
|
||||||
bash ./package.sh
|
bash ./package.sh
|
||||||
bash ./package-win.sh
|
|
||||||
|
|
||||||
time=$(date +%s%3N)
|
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
|
echo "{\"time\":$time,\"version\":\"$version\"}" > info.json
|
||||||
jq --arg content "$(cat UPDATE.md)" '. += {markdown: $content}' info.json > temp.json && mv temp.json 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
|
append_body: false
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
files: |
|
files: |
|
||||||
./ani-rss-application/target/ani-rss-jar-with-dependencies.jar
|
./ani-rss-application/target/ani-rss.jar
|
||||||
./ani-rss-application/target/ani-rss-jar-with-dependencies.jar.md5
|
./ani-rss-application/target/ani-rss.jar.md5
|
||||||
./ani-rss-application/target/ani-rss-launcher.exe
|
./ani-rss-application/target/ani-rss.exe
|
||||||
./ani-rss-application/target/ani-rss-launcher.exe.md5
|
./ani-rss-application/target/ani-rss.exe.md5
|
||||||
./ani-rss-application/target/ani-rss.win.x86_64.zip
|
|
||||||
./ani-rss-application/target/ani-rss.win.x86_64.zip.md5
|
|
||||||
./info.json
|
./info.json
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
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
|
||||||
.idea/modules.xml
|
target
|
||||||
.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
|
|
||||||
logs
|
logs
|
||||||
*.log
|
|
||||||
/config
|
/config
|
||||||
.git
|
*.log
|
||||||
|
|
||||||
java-17-openjdk-17.0.3.0.6-1.jre.win.x86_64.zip
|
|
||||||
ani-rss-update.exe
|
ani-rss-update.exe
|
||||||
/info.json
|
build_info
|
||||||
build_info
|
|
||||||
*.versionsBackup
|
|
||||||
jre.zip
|
|
||||||
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)
|
[请不要将本项目在国内宣传](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"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
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">
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ani.rss</groupId>
|
<groupId>ani.rss</groupId>
|
||||||
<artifactId>ani-rss</artifactId>
|
<artifactId>ani-rss</artifactId>
|
||||||
<version>2.5.10</version>
|
<version>3.0.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>ani-rss-application</artifactId>
|
<artifactId>ani-rss-application</artifactId>
|
||||||
@@ -17,14 +16,88 @@
|
|||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</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>
|
<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>
|
<dependency>
|
||||||
<groupId>ani.rss</groupId>
|
<groupId>ani.rss</groupId>
|
||||||
<artifactId>ani-rss-ui</artifactId>
|
<artifactId>ani-rss-ui</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ani.rss</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>ani-rss-web</artifactId>
|
<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>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
@@ -32,28 +105,8 @@
|
|||||||
<finalName>ani-rss</finalName>
|
<finalName>ani-rss</finalName>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>maven-assembly-plugin</artifactId>
|
<artifactId>spring-boot-maven-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>
|
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>com.akathist.maven.plugins.launch4j</groupId>
|
<groupId>com.akathist.maven.plugins.launch4j</groupId>
|
||||||
@@ -67,8 +120,8 @@
|
|||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<headerType>gui</headerType>
|
<headerType>gui</headerType>
|
||||||
<outfile>target/ani-rss-launcher.exe</outfile>
|
<outfile>target/ani-rss.exe</outfile>
|
||||||
<jar>target/ani-rss-jar-with-dependencies.jar</jar>
|
<jar>target/ani-rss.jar</jar>
|
||||||
<errTitle>Java environment is required!</errTitle>
|
<errTitle>Java environment is required!</errTitle>
|
||||||
<cmdLine>--gui</cmdLine>
|
<cmdLine>--gui</cmdLine>
|
||||||
<chdir>.</chdir>
|
<chdir>.</chdir>
|
||||||
@@ -79,11 +132,11 @@
|
|||||||
<restartOnCrash>false</restartOnCrash>
|
<restartOnCrash>false</restartOnCrash>
|
||||||
<icon>${project.parent.basedir}/ani-rss-ui/public/favicon.ico</icon>
|
<icon>${project.parent.basedir}/ani-rss-ui/public/favicon.ico</icon>
|
||||||
<singleInstance>
|
<singleInstance>
|
||||||
<mutexName>ani-rss-launcher (${project.version})</mutexName>
|
<mutexName>ani-rss (${project.version})</mutexName>
|
||||||
<windowTitle>ani-rss (${project.version})</windowTitle>
|
<windowTitle>ani-rss (${project.version})</windowTitle>
|
||||||
</singleInstance>
|
</singleInstance>
|
||||||
<jre>
|
<jre>
|
||||||
<path>jre/bin;%JAVA_HOME%/bin;%PATH%</path>
|
<path>%JAVA_HOME%/bin;%PATH%</path>
|
||||||
<minVersion>17</minVersion>
|
<minVersion>17</minVersion>
|
||||||
<opts>
|
<opts>
|
||||||
<opt>-Xms60m -Xmx1g -Xss256k</opt>
|
<opt>-Xms60m -Xmx1g -Xss256k</opt>
|
||||||
@@ -99,7 +152,7 @@
|
|||||||
<copyright>Copyright (C) 2024-2025</copyright>
|
<copyright>Copyright (C) 2024-2025</copyright>
|
||||||
<productName>${project.artifactId}</productName>
|
<productName>${project.artifactId}</productName>
|
||||||
<internalName>${project.artifactId}</internalName>
|
<internalName>${project.artifactId}</internalName>
|
||||||
<originalFilename>ani-rss-launcher.exe</originalFilename>
|
<originalFilename>ani-rss.exe</originalFilename>
|
||||||
<language>SIMPLIFIED_CHINESE</language>
|
<language>SIMPLIFIED_CHINESE</language>
|
||||||
</versionInfo>
|
</versionInfo>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -144,4 +197,5 @@
|
|||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
</project>
|
</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.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
@@ -11,7 +11,7 @@ import java.lang.annotation.Target;
|
|||||||
* 鉴权
|
* 鉴权
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target({ElementType.TYPE, ElementType.FIELD})
|
@Target(ElementType.METHOD)
|
||||||
public @interface Auth {
|
public @interface Auth {
|
||||||
boolean value() default true;
|
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.auth.fun.ApiKey;
|
||||||
import ani.rss.web.auth.fun.Form;
|
import ani.rss.auth.fun.Form;
|
||||||
import ani.rss.web.auth.fun.Header;
|
import ani.rss.auth.fun.Header;
|
||||||
import ani.rss.web.auth.fun.IpWhitelist;
|
import ani.rss.auth.fun.IpWhitelist;
|
||||||
import cn.hutool.http.server.HttpServerRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@@ -21,5 +21,5 @@ public enum AuthType {
|
|||||||
IP_WHITE_LIST(IpWhitelist.class);
|
IP_WHITE_LIST(IpWhitelist.class);
|
||||||
|
|
||||||
@Getter
|
@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.entity.Config;
|
||||||
import ani.rss.util.other.ConfigUtil;
|
import ani.rss.util.other.ConfigUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.http.server.HttpServerRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* api key 鉴权
|
* api key 鉴权
|
||||||
*/
|
*/
|
||||||
public class ApiKey implements Function<HttpServerRequest, Boolean> {
|
public class ApiKey implements Function<HttpServletRequest, Boolean> {
|
||||||
@Override
|
@Override
|
||||||
public Boolean apply(HttpServerRequest request) {
|
public Boolean apply(HttpServletRequest request) {
|
||||||
Config config = ConfigUtil.CONFIG;
|
Config config = ConfigUtil.CONFIG;
|
||||||
String apiKey = config.getApiKey();
|
String apiKey = config.getApiKey();
|
||||||
if (StrUtil.isBlank(apiKey)) {
|
if (StrUtil.isBlank(apiKey)) {
|
||||||
return false;
|
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);
|
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.entity.Login;
|
||||||
import ani.rss.web.util.AuthUtil;
|
import ani.rss.util.other.AuthUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.http.server.HttpServerRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表单鉴权
|
* 表单鉴权
|
||||||
*/
|
*/
|
||||||
public class Form implements Function<HttpServerRequest, Boolean> {
|
public class Form implements Function<HttpServletRequest, Boolean> {
|
||||||
@Override
|
@Override
|
||||||
public Boolean apply(HttpServerRequest request) {
|
public Boolean apply(HttpServletRequest request) {
|
||||||
String s = request.getParam("s");
|
String s = request.getParameter("s");
|
||||||
Login login = AuthUtil.getLogin();
|
Login login = AuthUtil.getLogin();
|
||||||
String auth = AuthUtil.getAuth(login);
|
String auth = AuthUtil.getAuth(login);
|
||||||
return StrUtil.equals(auth, s);
|
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.entity.Login;
|
||||||
import ani.rss.web.util.AuthUtil;
|
import ani.rss.util.other.AuthUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.http.server.HttpServerRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求头鉴权
|
* 请求头鉴权
|
||||||
*/
|
*/
|
||||||
public class Header implements Function<HttpServerRequest, Boolean> {
|
public class Header implements Function<HttpServletRequest, Boolean> {
|
||||||
@Override
|
@Override
|
||||||
public Boolean apply(HttpServerRequest request) {
|
public Boolean apply(HttpServletRequest request) {
|
||||||
String s = request.getHeader("Authorization");
|
String s = request.getHeader("Authorization");
|
||||||
if (StrUtil.isBlank(s)) {
|
if (StrUtil.isBlank(s)) {
|
||||||
return false;
|
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.commons.CacheUtils;
|
||||||
import ani.rss.entity.Config;
|
import ani.rss.entity.Config;
|
||||||
import ani.rss.util.basic.CidrRangeChecker;
|
import ani.rss.util.basic.CidrRangeChecker;
|
||||||
|
import ani.rss.util.other.AuthUtil;
|
||||||
import ani.rss.util.other.ConfigUtil;
|
import ani.rss.util.other.ConfigUtil;
|
||||||
import ani.rss.web.util.AuthUtil;
|
|
||||||
import cn.hutool.core.lang.PatternPool;
|
import cn.hutool.core.lang.PatternPool;
|
||||||
import cn.hutool.core.net.Ipv4Util;
|
import cn.hutool.core.net.Ipv4Util;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.crypto.SecureUtil;
|
import cn.hutool.crypto.SecureUtil;
|
||||||
import cn.hutool.http.server.HttpServerRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -18,9 +18,9 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class IpWhitelist implements Function<HttpServerRequest, Boolean> {
|
public class IpWhitelist implements Function<HttpServletRequest, Boolean> {
|
||||||
@Override
|
@Override
|
||||||
public Boolean apply(HttpServerRequest request) {
|
public Boolean apply(HttpServletRequest request) {
|
||||||
String ip = AuthUtil.getIp();
|
String ip = AuthUtil.getIp();
|
||||||
Config config = ConfigUtil.CONFIG;
|
Config config = ConfigUtil.CONFIG;
|
||||||
String ipWhitelistStr = config.getIpWhitelistStr();
|
String ipWhitelistStr = config.getIpWhitelistStr();
|
||||||
@@ -88,7 +88,7 @@ public class FileUtils {
|
|||||||
|
|
||||||
public static String normalize(String path) {
|
public static String normalize(String path) {
|
||||||
path = path.trim();
|
path = path.trim();
|
||||||
String s = cn.hutool.core.io.FileUtil.normalize(path);
|
String s = FileUtil.normalize(path);
|
||||||
while (s.endsWith("/")) {
|
while (s.endsWith("/")) {
|
||||||
s = s.substring(0, s.length() - 1);
|
s = s.substring(0, s.length() - 1);
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ import java.util.TimeZone;
|
|||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class GsonStatic {
|
public class GsonStatic {
|
||||||
private static final Gson GSON = new GsonBuilder()
|
public static final Gson GSON = new GsonBuilder()
|
||||||
.disableHtmlEscaping()
|
.disableHtmlEscaping()
|
||||||
.disableJdkUnsafe()
|
.disableJdkUnsafe()
|
||||||
.disableInnerClassSerialization()
|
.disableInnerClassSerialization()
|
||||||
@@ -4,10 +4,13 @@ import cn.hutool.core.io.FileUtil;
|
|||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
import cn.hutool.core.util.ReUtil;
|
import cn.hutool.core.util.ReUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.core.util.XmlUtil;
|
||||||
import cn.hutool.system.OsInfo;
|
import cn.hutool.system.OsInfo;
|
||||||
import cn.hutool.system.SystemUtil;
|
import cn.hutool.system.SystemUtil;
|
||||||
import lombok.Cleanup;
|
import lombok.Cleanup;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -75,8 +78,9 @@ public class MavenUtils {
|
|||||||
}
|
}
|
||||||
File file = new File("pom.xml");
|
File file = new File("pom.xml");
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
String s = FileUtil.readUtf8String(file);
|
Document document = XmlUtil.readXML(file);
|
||||||
version = ReUtil.get("<version>(.*?)</version>", s, 1);
|
Element element = XmlUtil.getElement(document.getDocumentElement(), "version");
|
||||||
|
version = element.getTextContent();
|
||||||
}
|
}
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package ani.rss.other;
|
package ani.rss.config;
|
||||||
|
|
||||||
import ani.rss.download.BaseDownload;
|
import ani.rss.download.BaseDownload;
|
||||||
import ani.rss.entity.About;
|
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.ClassUtil;
|
||||||
import cn.hutool.core.util.ReflectUtil;
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.cron.CronUtil;
|
|
||||||
import cn.hutool.http.ContentType;
|
import cn.hutool.http.ContentType;
|
||||||
import cn.hutool.http.Header;
|
import cn.hutool.http.Header;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
|
||||||
* 定时任务
|
|
||||||
*/
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class Cron {
|
@Component
|
||||||
public static void updateTrackers(Config config) {
|
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();
|
String trackersUpdateUrls = config.getTrackersUpdateUrls();
|
||||||
Assert.notBlank(trackersUpdateUrls, "Trackers更新地址 为空");
|
Assert.notBlank(trackersUpdateUrls, "Trackers更新地址 为空");
|
||||||
|
|
||||||
@@ -75,54 +121,4 @@ public class Cron {
|
|||||||
baseDownload.updateTrackers(trackers);
|
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.ExceptionUtils;
|
||||||
import ani.rss.commons.FileUtils;
|
import ani.rss.commons.FileUtils;
|
||||||
|
import ani.rss.dto.ImportAniDataDTO;
|
||||||
import ani.rss.entity.*;
|
import ani.rss.entity.*;
|
||||||
import ani.rss.enums.SortTypeEnum;
|
import ani.rss.enums.SortTypeEnum;
|
||||||
import ani.rss.service.AniService;
|
import ani.rss.service.AniService;
|
||||||
@@ -9,10 +11,6 @@ import ani.rss.service.ClearService;
|
|||||||
import ani.rss.service.DownloadService;
|
import ani.rss.service.DownloadService;
|
||||||
import ani.rss.task.RssTask;
|
import ani.rss.task.RssTask;
|
||||||
import ani.rss.util.other.*;
|
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.bean.BeanUtil;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.comparator.PinyinComparator;
|
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.date.DateUtil;
|
||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.lang.UUID;
|
||||||
import cn.hutool.core.text.StrFormatter;
|
import cn.hutool.core.text.StrFormatter;
|
||||||
import cn.hutool.core.thread.ThreadUtil;
|
import cn.hutool.core.thread.ThreadUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
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.extra.pinyin.PinyinUtil;
|
||||||
import cn.hutool.http.server.HttpServerRequest;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import cn.hutool.http.server.HttpServerResponse;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.io.File;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.function.ToLongFunction;
|
import java.util.function.ToLongFunction;
|
||||||
|
|
||||||
/**
|
|
||||||
* 订阅 增删改查
|
|
||||||
*/
|
|
||||||
@Auth
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Path("/ani")
|
@RestController
|
||||||
public class AniAction implements BaseAction {
|
public class AniController extends BaseController {
|
||||||
|
|
||||||
public static final AtomicBoolean DOWNLOAD = new AtomicBoolean(false);
|
public static final AtomicBoolean DOWNLOAD = new AtomicBoolean(false);
|
||||||
|
|
||||||
/**
|
@Auth
|
||||||
* 手动刷新订阅
|
@Operation(summary = "添加订阅")
|
||||||
*/
|
@PostMapping("/addAni")
|
||||||
private void refreshAni() {
|
public Result<Void> addAni(@RequestBody Ani ani) {
|
||||||
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);
|
|
||||||
ani.setTitle(ani.getTitle().trim())
|
ani.setTitle(ani.getTitle().trim())
|
||||||
.setUrl(ani.getUrl().trim());
|
.setUrl(ani.getUrl().trim());
|
||||||
AniUtil.verify(ani);
|
AniUtil.verify(ani);
|
||||||
@@ -104,8 +57,7 @@ public class AniAction implements BaseAction {
|
|||||||
.findFirst();
|
.findFirst();
|
||||||
|
|
||||||
if (first.isPresent()) {
|
if (first.isPresent()) {
|
||||||
resultErrorMsg("此订阅已存在");
|
return Result.error("此订阅已存在");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
first = AniUtil.ANI_LIST.stream()
|
first = AniUtil.ANI_LIST.stream()
|
||||||
@@ -122,8 +74,7 @@ public class AniAction implements BaseAction {
|
|||||||
AniUtil.ANI_LIST.remove(first.get());
|
AniUtil.ANI_LIST.remove(first.get());
|
||||||
log.info("自动替换 {} 第{}季", title, season);
|
log.info("自动替换 {} 第{}季", title, season);
|
||||||
} else {
|
} else {
|
||||||
resultErrorMsg("订阅标题重复");
|
return Result.error("订阅标题重复");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,15 +99,15 @@ public class AniAction implements BaseAction {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
resultSuccessMsg("添加订阅成功");
|
|
||||||
log.info("添加订阅 {} {} {}", title, ani.getUrl(), ani.getId());
|
log.info("添加订阅 {} {} {}", title, ani.getUrl(), ani.getId());
|
||||||
|
|
||||||
|
return Result.success("添加订阅成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Auth
|
||||||
* 修改订阅
|
@Operation(summary = "修改订阅")
|
||||||
*/
|
@PostMapping("/setAni")
|
||||||
private void put() {
|
public Result<Void> setAni(@RequestBody Ani ani) {
|
||||||
Ani ani = getBody(Ani.class);
|
|
||||||
ani.setTitle(ani.getTitle().trim())
|
ani.setTitle(ani.getTitle().trim())
|
||||||
.setUrl(ani.getUrl().trim());
|
.setUrl(ani.getUrl().trim());
|
||||||
AniUtil.verify(ani);
|
AniUtil.verify(ani);
|
||||||
@@ -165,19 +116,17 @@ public class AniAction implements BaseAction {
|
|||||||
.filter(it -> it.getTitle().equals(ani.getTitle()) && it.getSeason().equals(ani.getSeason()))
|
.filter(it -> it.getTitle().equals(ani.getTitle()) && it.getSeason().equals(ani.getSeason()))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
if (first.isPresent()) {
|
if (first.isPresent()) {
|
||||||
resultErrorMsg("订阅标题重复");
|
return Result.error("订阅标题重复");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
first = AniUtil.ANI_LIST.stream()
|
first = AniUtil.ANI_LIST.stream()
|
||||||
.filter(it -> it.getId().equals(ani.getId()))
|
.filter(it -> it.getId().equals(ani.getId()))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
if (first.isEmpty()) {
|
if (first.isEmpty()) {
|
||||||
resultErrorMsg("修改失败");
|
return Result.error("修改失败");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
HttpServerRequest request = ServerUtil.REQUEST.get();
|
HttpServletRequest request = Global.REQUEST.get();
|
||||||
String move = request.getParam("move");
|
String move = request.getParameter("move");
|
||||||
if (Boolean.parseBoolean(move)) {
|
if (Boolean.parseBoolean(move)) {
|
||||||
Ani get = ObjectUtil.clone(first.get());
|
Ani get = ObjectUtil.clone(first.get());
|
||||||
ThreadUtil.execute(() -> {
|
ThreadUtil.execute(() -> {
|
||||||
@@ -237,14 +186,74 @@ public class AniAction implements BaseAction {
|
|||||||
FileUtil.move(torrentDir, newTorrentDir.getParentFile(), true);
|
FileUtil.move(torrentDir, newTorrentDir.getParentFile(), true);
|
||||||
}
|
}
|
||||||
AniUtil.sync();
|
AniUtil.sync();
|
||||||
resultSuccessMsg("修改成功");
|
|
||||||
log.info("修改订阅 {} {} {}", ani.getTitle(), ani.getUrl(), ani.getId());
|
log.info("修改订阅 {} {} {}", ani.getTitle(), ani.getUrl(), ani.getId());
|
||||||
|
return Result.success("修改成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Auth
|
||||||
* 返回订阅列表
|
@Operation(summary = "删除订阅")
|
||||||
*/
|
@PostMapping("/deleteAni")
|
||||||
private void get() {
|
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;
|
Config config = ConfigUtil.CONFIG;
|
||||||
|
|
||||||
SortTypeEnum sortType = config.getSortType();
|
SortTypeEnum sortType = config.getSortType();
|
||||||
@@ -297,101 +306,13 @@ public class AniAction implements BaseAction {
|
|||||||
}).reversed());
|
}).reversed());
|
||||||
}
|
}
|
||||||
|
|
||||||
resultSuccess(list);
|
return Result.success(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Auth
|
||||||
* 删除订阅
|
@Operation(summary = "更新总集数")
|
||||||
*/
|
@PostMapping("/updateTotalEpisodeNumber")
|
||||||
public void delete() {
|
public Result<Void> updateTotalEpisodeNumber(@RequestParam("force") Boolean force, @RequestBody List<String> ids) {
|
||||||
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();
|
|
||||||
Assert.notEmpty(ids, "未选择订阅");
|
Assert.notEmpty(ids, "未选择订阅");
|
||||||
ThreadUtil.execute(() -> {
|
ThreadUtil.execute(() -> {
|
||||||
log.info("开始手动更新总集数");
|
log.info("开始手动更新总集数");
|
||||||
@@ -416,53 +337,196 @@ public class AniAction implements BaseAction {
|
|||||||
AniUtil.sync();
|
AniUtil.sync();
|
||||||
log.info("手动更新总集数完成 共更新{}条订阅", count);
|
log.info("手动更新总集数完成 共更新{}条订阅", count);
|
||||||
});
|
});
|
||||||
resultSuccessMsg("已开始更新总集数");
|
return Result.success("已开始更新总集数");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Auth
|
||||||
public void doAction(HttpServerRequest req, HttpServerResponse res) {
|
@Operation(summary = "批量 启用/禁用 订阅")
|
||||||
String method = req.getMethod();
|
@PostMapping("/batchEnable")
|
||||||
String type = StrUtil.blankToDefault(req.getParam("type"), "");
|
public Result<Void> batchEnable(@RequestParam("value") Boolean value, @RequestBody List<String> ids) {
|
||||||
switch (type) {
|
Assert.notEmpty(ids, "未选择订阅");
|
||||||
case "refreshAni" -> {
|
for (Ani ani : AniUtil.ANI_LIST) {
|
||||||
// 刷新订阅
|
String id = ani.getId();
|
||||||
refreshAni();
|
if (!ids.contains(id)) {
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
case "batchEnable" -> {
|
ani.setEnable(value);
|
||||||
// 批量 启用/禁用
|
}
|
||||||
boolean enable = Boolean.parseBoolean(req.getParam("value"));
|
AniUtil.sync();
|
||||||
batchEnable(enable);
|
return Result.success("修改完成");
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
@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" -> {
|
DOWNLOAD.set(true);
|
||||||
// 更新总集数
|
}
|
||||||
updateTotalEpisodeNumber();
|
Ani downloadAni = first.get();
|
||||||
return;
|
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) {
|
List<Integer> omitList = ItemsUtil.omitList(ani, items);
|
||||||
case "POST": {
|
|
||||||
// 添加订阅
|
Map<String, Object> map = Map.of(
|
||||||
post();
|
"downloadPath", downloadPath,
|
||||||
return;
|
"items", items,
|
||||||
}
|
"omitList", omitList
|
||||||
case "PUT": {
|
);
|
||||||
// 修改订阅
|
return Result.success(map);
|
||||||
put();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case "GET": {
|
|
||||||
// 获取订阅列表
|
|
||||||
get();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case "DELETE": {
|
|
||||||
// 删除订阅
|
|
||||||
delete();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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.commons.FileUtils;
|
||||||
import ani.rss.download.qBittorrent;
|
import ani.rss.download.qBittorrent;
|
||||||
import ani.rss.entity.*;
|
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.ConfigUtil;
|
||||||
import ani.rss.util.other.RenameUtil;
|
import ani.rss.util.other.RenameUtil;
|
||||||
import ani.rss.util.other.TorrentUtil;
|
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.codec.Base64;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.io.FileUtil;
|
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.thread.ThreadUtil;
|
||||||
import cn.hutool.core.util.*;
|
import cn.hutool.core.util.*;
|
||||||
import cn.hutool.http.HttpResponse;
|
import cn.hutool.http.HttpResponse;
|
||||||
import cn.hutool.http.server.HttpServerRequest;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import cn.hutool.http.server.HttpServerResponse;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.bittorrent.TorrentFile;
|
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.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
|
||||||
* 合集
|
|
||||||
*/
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Auth
|
@RestController
|
||||||
@Path("/collection")
|
public class CollectionController extends BaseController {
|
||||||
public class CollectionAction implements BaseAction {
|
|
||||||
|
@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) {
|
public static synchronized List<Item> preview(CollectionInfo collectionInfo) {
|
||||||
String torrent = collectionInfo.getTorrent();
|
String torrent = collectionInfo.getTorrent();
|
||||||
File tempFile = FileUtil.createTempFile();
|
File tempFile = FileUtil.createTempFile();
|
||||||
@@ -185,139 +321,4 @@ public class CollectionAction implements BaseAction {
|
|||||||
.then(HttpReq::assertStatus);
|
.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.annotation.Auth;
|
||||||
import ani.rss.entity.Ani;
|
import ani.rss.entity.*;
|
||||||
import ani.rss.entity.Config;
|
|
||||||
import ani.rss.entity.EmbyWebHook;
|
|
||||||
import ani.rss.enums.StringEnum;
|
import ani.rss.enums.StringEnum;
|
||||||
import ani.rss.service.DownloadService;
|
import ani.rss.service.DownloadService;
|
||||||
import ani.rss.util.other.*;
|
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.thread.ExecutorBuilder;
|
||||||
import cn.hutool.core.util.ReUtil;
|
import cn.hutool.core.util.ReUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.http.server.HttpServerRequest;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import cn.hutool.http.server.HttpServerResponse;
|
|
||||||
import lombok.Synchronized;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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 wushuo.tmdb.api.entity.Tmdb;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
/**
|
|
||||||
* WebHook
|
|
||||||
*/
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Auth(type = AuthType.API_KEY)
|
@RestController
|
||||||
@Path("/web_hook")
|
@RequestMapping
|
||||||
public class WebHookAction implements BaseAction {
|
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()
|
private static final ExecutorService EXECUTOR = ExecutorBuilder.create()
|
||||||
.setCorePoolSize(1)
|
.setCorePoolSize(1)
|
||||||
@@ -43,25 +40,19 @@ public class WebHookAction implements BaseAction {
|
|||||||
.setWorkQueue(new LinkedBlockingQueue<>(256))
|
.setWorkQueue(new LinkedBlockingQueue<>(256))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@Override
|
@Auth
|
||||||
@Synchronized("EXECUTOR")
|
@Operation(summary = "BGM自动点格子")
|
||||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
@PostMapping("/embyWebHook")
|
||||||
String body = getBody();
|
public Result<Void> embyWebHook(@RequestBody EmbyWebHook embyWebHook) {
|
||||||
|
log.debug("webhook: {}", embyWebHook);
|
||||||
Assert.notBlank(body, "WebHook body is empty");
|
|
||||||
|
|
||||||
log.debug("webhook: {}", body);
|
|
||||||
|
|
||||||
Config config = ConfigUtil.CONFIG;
|
Config config = ConfigUtil.CONFIG;
|
||||||
String bgmToken = config.getBgmToken();
|
String bgmToken = config.getBgmToken();
|
||||||
if (StrUtil.isBlank(bgmToken)) {
|
if (StrUtil.isBlank(bgmToken)) {
|
||||||
log.info("bgmToken 为空");
|
log.info("bgmToken 为空");
|
||||||
response.sendOk();
|
return Result.success();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EmbyWebHook embyWebHook = GsonStatic.fromJson(body, EmbyWebHook.class);
|
|
||||||
|
|
||||||
String event = embyWebHook.getEvent();
|
String event = embyWebHook.getEvent();
|
||||||
|
|
||||||
if (List.of("system.webhooktest", "system.notificationtest").contains(event)) {
|
if (List.of("system.webhooktest", "system.notificationtest").contains(event)) {
|
||||||
@@ -83,8 +74,7 @@ public class WebHookAction implements BaseAction {
|
|||||||
String ip = AuthUtil.getIp();
|
String ip = AuthUtil.getIp();
|
||||||
log.info(s, ip, id, name, version);
|
log.info(s, ip, id, name, version);
|
||||||
// 测试
|
// 测试
|
||||||
response.sendOk();
|
return Result.success();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EmbyWebHook.Item item = embyWebHook.getItem();
|
EmbyWebHook.Item item = embyWebHook.getItem();
|
||||||
@@ -92,32 +82,27 @@ public class WebHookAction implements BaseAction {
|
|||||||
String seriesName = item.getSeriesName();
|
String seriesName = item.getSeriesName();
|
||||||
String fileName = item.getFileName();
|
String fileName = item.getFileName();
|
||||||
if (!ReUtil.contains(StringEnum.SEASON_REG, fileName)) {
|
if (!ReUtil.contains(StringEnum.SEASON_REG, fileName)) {
|
||||||
response.sendOk();
|
return Result.success();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// 季
|
// 季
|
||||||
int season = Integer.parseInt(ReUtil.get(StringEnum.SEASON_REG, fileName, 1));
|
int season = Integer.parseInt(ReUtil.get(StringEnum.SEASON_REG, fileName, 1));
|
||||||
|
|
||||||
// 番外
|
// 番外
|
||||||
if (season < 1) {
|
if (season < 1) {
|
||||||
response.sendOk();
|
return Result.success();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 集 x.5
|
// 集 x.5
|
||||||
double episode = Double.parseDouble(ReUtil.get(StringEnum.SEASON_REG, fileName, 2));
|
double episode = Double.parseDouble(ReUtil.get(StringEnum.SEASON_REG, fileName, 2));
|
||||||
if (ItemsUtil.is5(episode)) {
|
if (ItemsUtil.is5(episode)) {
|
||||||
response.sendOk();
|
return Result.success();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response.sendOk();
|
|
||||||
|
|
||||||
int type = getType(embyWebHook);
|
int type = getType(embyWebHook);
|
||||||
|
|
||||||
if (type < 0) {
|
if (type < 0) {
|
||||||
// 播放状态未正确获取
|
// 播放状态未正确获取
|
||||||
return;
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
EXECUTOR.execute(() -> {
|
EXECUTOR.execute(() -> {
|
||||||
@@ -142,6 +127,8 @@ public class WebHookAction implements BaseAction {
|
|||||||
BgmUtil.collections(subjectId);
|
BgmUtil.collections(subjectId);
|
||||||
BgmUtil.collectionsEpisodes(episodeId, type);
|
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.commons.CacheUtils;
|
||||||
import ani.rss.entity.Config;
|
import ani.rss.entity.Config;
|
||||||
import ani.rss.entity.Login;
|
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.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.lang.Assert;
|
||||||
import cn.hutool.core.thread.ThreadUtil;
|
import cn.hutool.core.thread.ThreadUtil;
|
||||||
import cn.hutool.core.util.RandomUtil;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
import cn.hutool.http.server.HttpServerRequest;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import cn.hutool.http.server.HttpServerResponse;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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
|
@Slf4j
|
||||||
@Auth(value = false)
|
@RestController
|
||||||
@Path("/login")
|
public class LoginController extends BaseController {
|
||||||
public class LoginAction implements BaseAction {
|
|
||||||
|
|
||||||
@Override
|
@Operation(summary = "登录")
|
||||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
@PostMapping("/login")
|
||||||
limitLoginAttempts(false);
|
public Result<String> login(@RequestBody Login myLogin) {
|
||||||
|
AuthUtil.limitLoginAttempts(false);
|
||||||
|
|
||||||
Login myLogin = getBody(Login.class);
|
|
||||||
Config config = ConfigUtil.CONFIG;
|
Config config = ConfigUtil.CONFIG;
|
||||||
Login login = config.getLogin();
|
Login login = config.getLogin();
|
||||||
|
|
||||||
@@ -57,25 +49,25 @@ public class LoginAction implements BaseAction {
|
|||||||
clearLimitLoginAttempts();
|
clearLimitLoginAttempts();
|
||||||
log.info("登录成功 {} ip: {}", username, ip);
|
log.info("登录成功 {} ip: {}", username, ip);
|
||||||
String s = AuthUtil.getAuth(myLogin);
|
String s = AuthUtil.getAuth(myLogin);
|
||||||
resultSuccess(s);
|
return new Result<String>()
|
||||||
return;
|
.setCode(200)
|
||||||
|
.setMessage("登录成功")
|
||||||
|
.setData(s);
|
||||||
}
|
}
|
||||||
limitLoginAttempts(true);
|
AuthUtil.limitLoginAttempts(true);
|
||||||
log.warn("登陆失败 {} ip: {}", myUsername, ip);
|
log.warn("登陆失败 {} ip: {}", myUsername, ip);
|
||||||
ThreadUtil.sleep(RandomUtil.randomInt(500, 5000));
|
ThreadUtil.sleep(RandomUtil.randomInt(500, 5000));
|
||||||
resultErrorMsg("用户名或密码错误");
|
return Result.error("用户名或密码错误");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清除限制尝试次数
|
* 清除限制尝试次数
|
||||||
*/
|
*/
|
||||||
public static void clearLimitLoginAttempts() {
|
private void clearLimitLoginAttempts() {
|
||||||
String ip = AuthUtil.getIp();
|
String ip = AuthUtil.getIp();
|
||||||
String key = "LimitLoginAttempts#" + ip;
|
String key = "LimitLoginAttempts#" + ip;
|
||||||
if (CacheUtils.containsKey(key)) {
|
if (CacheUtils.containsKey(key)) {
|
||||||
CacheUtils.remove(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.Ani;
|
||||||
import ani.rss.entity.BgmInfo;
|
import ani.rss.entity.BgmInfo;
|
||||||
import ani.rss.entity.NotificationConfig;
|
import ani.rss.entity.NotificationConfig;
|
||||||
|
import ani.rss.entity.Result;
|
||||||
import ani.rss.enums.NotificationStatusEnum;
|
import ani.rss.enums.NotificationStatusEnum;
|
||||||
import ani.rss.enums.NotificationTypeEnum;
|
import ani.rss.enums.NotificationTypeEnum;
|
||||||
import ani.rss.notification.BaseNotification;
|
import ani.rss.notification.BaseNotification;
|
||||||
|
import ani.rss.notification.TelegramNotification;
|
||||||
import ani.rss.util.other.AniUtil;
|
import ani.rss.util.other.AniUtil;
|
||||||
import ani.rss.util.other.BgmUtil;
|
import ani.rss.util.other.BgmUtil;
|
||||||
import ani.rss.util.other.NotificationUtil;
|
import ani.rss.util.other.NotificationUtil;
|
||||||
import ani.rss.util.other.TmdbUtils;
|
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.core.util.ReflectUtil;
|
||||||
import cn.hutool.http.server.HttpServerRequest;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import cn.hutool.http.server.HttpServerResponse;
|
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.Tmdb;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
@RestController
|
||||||
* 通知
|
public class NotificationController extends BaseController {
|
||||||
*/
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("add".equals(type)) {
|
@Auth
|
||||||
add();
|
@Operation(summary = "测试通知")
|
||||||
}
|
@PostMapping("/testNotification")
|
||||||
|
public Result<Void> testNotification(@RequestBody NotificationConfig notificationConfig) {
|
||||||
}
|
|
||||||
|
|
||||||
private void add() {
|
|
||||||
NotificationConfig notificationConfig = NotificationConfig.createNotificationConfig();
|
|
||||||
resultSuccess(notificationConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void test() {
|
|
||||||
NotificationConfig notificationConfig = getBody(NotificationConfig.class);
|
|
||||||
NotificationTypeEnum notificationType = notificationConfig.getNotificationType();
|
NotificationTypeEnum notificationType = notificationConfig.getNotificationType();
|
||||||
Class<? extends BaseNotification> aClass = NotificationUtil.NOTIFICATION_MAP.get(notificationType);
|
Class<? extends BaseNotification> aClass = NotificationUtil.NOTIFICATION_MAP.get(notificationType);
|
||||||
BaseNotification baseNotification = ReflectUtil.newInstance(aClass);
|
BaseNotification baseNotification = ReflectUtil.newInstance(aClass);
|
||||||
@@ -69,10 +51,26 @@ public class NotificationAction implements BaseAction {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
baseNotification.test(notificationConfig, ani, "test", NotificationStatusEnum.DOWNLOAD_START);
|
baseNotification.test(notificationConfig, ani, "test", NotificationStatusEnum.DOWNLOAD_START);
|
||||||
resultSuccess();
|
return Result.success();
|
||||||
} catch (Exception e) {
|
} 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.commons.FileUtils;
|
||||||
import ani.rss.entity.Ani;
|
import ani.rss.entity.Ani;
|
||||||
import ani.rss.entity.PlayItem;
|
import ani.rss.entity.PlayItem;
|
||||||
|
import ani.rss.entity.Result;
|
||||||
import ani.rss.enums.StringEnum;
|
import ani.rss.enums.StringEnum;
|
||||||
import ani.rss.service.DownloadService;
|
import ani.rss.service.DownloadService;
|
||||||
import ani.rss.util.other.AniUtil;
|
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.codec.Base64;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
import cn.hutool.core.util.ReUtil;
|
import cn.hutool.core.util.ReUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.http.server.HttpServerRequest;
|
import com.matthewn4444.ebml.EBMLReader;
|
||||||
import cn.hutool.http.server.HttpServerResponse;
|
import com.matthewn4444.ebml.subtitles.Subtitles;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
@RestController
|
||||||
* 视频列表
|
public class PlayController extends BaseController {
|
||||||
*/
|
|
||||||
@Auth
|
@Auth
|
||||||
@Slf4j
|
@Operation(summary = "获取内封字幕")
|
||||||
@Path("/playlist")
|
@PostMapping("/getSubtitles")
|
||||||
public class PlaylistAction implements BaseAction {
|
public Result<List<PlayItem.Subtitles>> getSubtitles(@RequestBody Map<String, String> map) throws IOException {
|
||||||
@Override
|
String file = map.get("file");
|
||||||
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
|
Assert.notBlank(file);
|
||||||
Ani ani = getBody(Ani.class);
|
|
||||||
|
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();
|
String url = ani.getUrl();
|
||||||
Optional<Ani> first = AniUtil.ANI_LIST
|
Optional<Ani> first = AniUtil.ANI_LIST
|
||||||
.stream()
|
.stream()
|
||||||
.filter(it -> url.equals(it.getUrl()))
|
.filter(it -> url.equals(it.getUrl()))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
if (first.isEmpty()) {
|
if (first.isEmpty()) {
|
||||||
resultError();
|
return Result.error();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
ani = first.get();
|
ani = first.get();
|
||||||
|
|
||||||
@@ -49,7 +102,7 @@ public class PlaylistAction implements BaseAction {
|
|||||||
// 按照集数排序
|
// 按照集数排序
|
||||||
CollUtil.sort(collect, Comparator.comparingDouble(PlayItem::getEpisode));
|
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 cn.hutool.http.HttpResponse;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -28,6 +30,8 @@ import java.util.*;
|
|||||||
* Aria2
|
* Aria2
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class Aria2 implements BaseDownload {
|
public class Aria2 implements BaseDownload {
|
||||||
private Config config;
|
private Config config;
|
||||||
|
|
||||||
@@ -25,13 +25,17 @@ import cn.hutool.http.HttpResponse;
|
|||||||
import cn.hutool.http.Method;
|
import cn.hutool.http.Method;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class OpenList implements BaseDownload {
|
public class OpenList implements BaseDownload {
|
||||||
private Config config;
|
private Config config;
|
||||||
|
|
||||||
@@ -22,7 +22,9 @@ import cn.hutool.http.HttpResponse;
|
|||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -34,15 +36,15 @@ import java.util.Set;
|
|||||||
* Transmission
|
* Transmission
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class Transmission implements BaseDownload {
|
public class Transmission implements BaseDownload {
|
||||||
private String host = "";
|
private String host = "";
|
||||||
private String authorization = "";
|
private String authorization = "";
|
||||||
private String sessionId = "";
|
private String sessionId = "";
|
||||||
private Config config;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Boolean login(Boolean test, Config config) {
|
public Boolean login(Boolean test, Config config) {
|
||||||
this.config = config;
|
|
||||||
String username = config.getDownloadToolUsername();
|
String username = config.getDownloadToolUsername();
|
||||||
String password = config.getDownloadToolPassword();
|
String password = config.getDownloadToolPassword();
|
||||||
host = config.getDownloadToolHost();
|
host = config.getDownloadToolHost();
|
||||||
@@ -24,8 +24,10 @@ import com.google.gson.JsonArray;
|
|||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -34,7 +36,10 @@ import java.util.*;
|
|||||||
* qBittorrent
|
* qBittorrent
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class qBittorrent implements BaseDownload {
|
public class qBittorrent implements BaseDownload {
|
||||||
|
|
||||||
private Config config;
|
private Config config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package ani.rss.entity;
|
package ani.rss.entity;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
@@ -11,39 +12,47 @@ import java.util.Date;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "关于")
|
||||||
public class About implements Serializable {
|
public class About implements Serializable {
|
||||||
/**
|
/**
|
||||||
* 版本
|
* 版本
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "版本")
|
||||||
private String version;
|
private String version;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 最新版本
|
* 最新版本
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "最新版本")
|
||||||
private String latest;
|
private String latest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否需要更新
|
* 是否需要更新
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "是否需要更新")
|
||||||
private Boolean update;
|
private Boolean update;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否允许自动更新
|
* 是否允许自动更新
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "是否允许自动更新")
|
||||||
private Boolean autoUpdate;
|
private Boolean autoUpdate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载地址
|
* 下载地址
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "下载地址")
|
||||||
private String downloadUrl;
|
private String downloadUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新内容
|
* 更新内容
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "更新内容")
|
||||||
private String markdownBody;
|
private String markdownBody;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发布时间
|
* 发布时间
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "发布时间")
|
||||||
private Date date;
|
private Date date;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package ani.rss.entity;
|
package ani.rss.entity;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import wushuo.tmdb.api.entity.Tmdb;
|
import wushuo.tmdb.api.entity.Tmdb;
|
||||||
@@ -13,20 +14,24 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "订阅")
|
||||||
public class Ani implements Serializable {
|
public class Ani implements Serializable {
|
||||||
/**
|
/**
|
||||||
* id
|
* id
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "id")
|
||||||
private String id;
|
private String id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 不在页面显示
|
* 不在页面显示
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "不在页面显示")
|
||||||
private String mikanTitle;
|
private String mikanTitle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RSS URL
|
* RSS URL
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "RSS URL")
|
||||||
private String url;
|
private String url;
|
||||||
|
|
||||||
private Boolean exists;
|
private Boolean exists;
|
||||||
@@ -34,239 +39,288 @@ public class Ani implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 备用rss
|
* 备用rss
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "备用rss")
|
||||||
private List<StandbyRss> standbyRssList;
|
private List<StandbyRss> standbyRssList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标题
|
* 标题
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "标题")
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日语标题 来源于BGM
|
* 日语标题 来源于BGM
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "日语标题 来源于BGM")
|
||||||
private String jpTitle;
|
private String jpTitle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 剧集偏移
|
* 剧集偏移
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "剧集偏移")
|
||||||
private Integer offset;
|
private Integer offset;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 年度
|
* 年度
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "年度")
|
||||||
private Integer year;
|
private Integer year;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 月
|
* 月
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "月")
|
||||||
private Integer month;
|
private Integer month;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日
|
* 日
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "日")
|
||||||
private Integer date;
|
private Integer date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 星期 1表示周日,2表示周一
|
* 星期 1表示周日,2表示周一
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "星期 1表示周日,2表示周一")
|
||||||
private Integer week;
|
private Integer week;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 季度
|
* 季度
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "季度")
|
||||||
private Integer season;
|
private Integer season;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 封面本地保存位置
|
* 封面本地保存位置
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "封面本地保存位置")
|
||||||
private String cover;
|
private String cover;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 图片 https://
|
* 图片 https://
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "图片 https://")
|
||||||
private String image;
|
private String image;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字幕组
|
* 字幕组
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "字幕组")
|
||||||
private String subgroup;
|
private String subgroup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 匹配
|
* 匹配
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "匹配")
|
||||||
private List<String> match;
|
private List<String> match;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 排除
|
* 排除
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "排除")
|
||||||
private List<String> exclude;
|
private List<String> exclude;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否启用全局排除
|
* 是否启用全局排除
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "是否启用全局排除")
|
||||||
private Boolean globalExclude;
|
private Boolean globalExclude;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 剧场版 or OVA
|
* 剧场版 or OVA
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "剧场版 or OVA")
|
||||||
private Boolean ova;
|
private Boolean ova;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拼音
|
* 拼音
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "拼音")
|
||||||
private String pinyin;
|
private String pinyin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拼音
|
* 拼音
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "拼音首字母")
|
||||||
private String pinyinInitials;
|
private String pinyinInitials;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启用
|
* 启用
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "启用")
|
||||||
private Boolean enable;
|
private Boolean enable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 当前集数
|
* 当前集数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "当前集数")
|
||||||
private Integer currentEpisodeNumber;
|
private Integer currentEpisodeNumber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 总集数
|
* 总集数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "总集数")
|
||||||
private Integer totalEpisodeNumber;
|
private Integer totalEpisodeNumber;
|
||||||
|
|
||||||
|
@Schema(description = "TheMovieDB 名称")
|
||||||
private String themoviedbName;
|
private String themoviedbName;
|
||||||
|
|
||||||
|
@Schema(description = "类型")
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
@Schema(description = "BGM 地址")
|
||||||
private String bgmUrl;
|
private String bgmUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义下载位置
|
* 自定义下载位置
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义下载位置")
|
||||||
private Boolean customDownloadPath;
|
private Boolean customDownloadPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义下载位置
|
* 自定义下载位置
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义下载位置路径")
|
||||||
private String downloadPath;
|
private String downloadPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 评分
|
* 评分
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "评分")
|
||||||
private Double score;
|
private Double score;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义集数获取规则
|
* 自定义集数获取规则
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义集数获取规则")
|
||||||
private Boolean customEpisode;
|
private Boolean customEpisode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义集数获取规则
|
* 自定义集数获取规则
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义集数获取规则表达式")
|
||||||
private String customEpisodeStr;
|
private String customEpisodeStr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义集数获取规则 groupIndex
|
* 自定义集数获取规则 groupIndex
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义集数获取规则 groupIndex")
|
||||||
private Integer customEpisodeGroupIndex;
|
private Integer customEpisodeGroupIndex;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 遗漏检测
|
* 遗漏检测
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "遗漏检测")
|
||||||
private Boolean omit;
|
private Boolean omit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 只下载最新集
|
* 只下载最新集
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "只下载最新集")
|
||||||
private Boolean downloadNew;
|
private Boolean downloadNew;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 不进行下载的集
|
* 不进行下载的集
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "不进行下载的集")
|
||||||
private List<Double> notDownload;
|
private List<Double> notDownload;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tmdb 相关信息
|
* tmdb 相关信息
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "TMDB 相关信息")
|
||||||
private Tmdb tmdb;
|
private Tmdb tmdb;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动上传
|
* 自动上传
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自动上传")
|
||||||
private Boolean upload;
|
private Boolean upload;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 摸鱼
|
* 摸鱼
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "摸鱼")
|
||||||
private Boolean procrastinating;
|
private Boolean procrastinating;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义重命名模版
|
* 自定义重命名模版
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义重命名模版开关")
|
||||||
private Boolean customRenameTemplateEnable;
|
private Boolean customRenameTemplateEnable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义重命名模版
|
* 自定义重命名模版
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义重命名模版")
|
||||||
private String customRenameTemplate;
|
private String customRenameTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义优先保留开关
|
* 自定义优先保留开关
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义优先保留开关")
|
||||||
private Boolean customPriorityKeywordsEnable;
|
private Boolean customPriorityKeywordsEnable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义优先保留关键词列表
|
* 自定义优先保留关键词列表
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义优先保留关键词列表")
|
||||||
private List<String> customPriorityKeywords;
|
private List<String> customPriorityKeywords;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上次下载完成时间
|
* 上次下载完成时间
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "上次下载完成时间")
|
||||||
private Long lastDownloadTime;
|
private Long lastDownloadTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义上传
|
* 自定义上传
|
||||||
*/
|
*/
|
||||||
@SerializedName(value = "customUploadEnable", alternate = "customAlistPath")
|
@SerializedName(value = "customUploadEnable", alternate = "customAlistPath")
|
||||||
|
@Schema(description = "自定义上传开关")
|
||||||
private Boolean customUploadEnable;
|
private Boolean customUploadEnable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义上传
|
* 自定义上传
|
||||||
*/
|
*/
|
||||||
@SerializedName(value = "customUploadPathTarget", alternate = "alistPath")
|
@SerializedName(value = "customUploadPathTarget", alternate = "alistPath")
|
||||||
|
@Schema(description = "自定义上传目标路径")
|
||||||
private String customUploadPathTarget;
|
private String customUploadPathTarget;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息通知
|
* 消息通知
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "消息通知")
|
||||||
private Boolean message;
|
private Boolean message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 完结迁移
|
* 完结迁移
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "完结迁移")
|
||||||
private Boolean completed;
|
private Boolean completed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义完结迁移
|
* 自定义完结迁移
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义完结迁移开关")
|
||||||
private Boolean customCompleted;
|
private Boolean customCompleted;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义完结迁移
|
* 自定义完结迁移
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义完结迁移路径模版")
|
||||||
private String customCompletedPathTemplate;
|
private String customCompletedPathTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义标签开关
|
* 自定义标签开关
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义标签开关")
|
||||||
private Boolean customTagsEnable;
|
private Boolean customTagsEnable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单个订阅自定义标签
|
* 单个订阅自定义标签
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "单个订阅自定义标签")
|
||||||
private List<String> customTags;
|
private List<String> customTags;
|
||||||
|
|
||||||
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package ani.rss.entity;
|
package ani.rss.entity;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "Bgm番剧信息")
|
||||||
public class BgmInfo implements Serializable {
|
public class BgmInfo implements Serializable {
|
||||||
private String id;
|
private String id;
|
||||||
|
|
||||||
@@ -22,37 +24,44 @@ public class BgmInfo implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 名称
|
* 名称
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "名称")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 中文名称
|
* 中文名称
|
||||||
*/
|
*/
|
||||||
@SerializedName(value = "nameCn", alternate = "name_cn")
|
@SerializedName(value = "nameCn", alternate = "name_cn")
|
||||||
|
@Schema(description = "中文名称")
|
||||||
private String nameCn;
|
private String nameCn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 集数
|
* 集数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "集数")
|
||||||
private Integer eps;
|
private Integer eps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 时间
|
* 时间
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "时间")
|
||||||
private Date date;
|
private Date date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 图片
|
* 图片
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "图片")
|
||||||
private Images images;
|
private Images images;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 季度
|
* 季度
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "季度")
|
||||||
private Integer season;
|
private Integer season;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 平台 OVA/剧场版
|
* 平台 OVA/剧场版
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "平台 OVA/剧场版")
|
||||||
private String platform;
|
private String platform;
|
||||||
|
|
||||||
private List<Tag> tags;
|
private List<Tag> tags;
|
||||||
@@ -64,6 +73,7 @@ public class BgmInfo implements Serializable {
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "封面图片")
|
||||||
public static class Images implements Serializable {
|
public static class Images implements Serializable {
|
||||||
private String small;
|
private String small;
|
||||||
private String grid;
|
private String grid;
|
||||||
@@ -77,12 +87,16 @@ public class BgmInfo implements Serializable {
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "标签")
|
||||||
public static class Tag implements Serializable {
|
public static class Tag implements Serializable {
|
||||||
|
@Schema(description = "标签名")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "计数")
|
||||||
private String count;
|
private String count;
|
||||||
|
|
||||||
@SerializedName(value = "totalCont", alternate = "total_cont")
|
@SerializedName(value = "totalCont", alternate = "total_cont")
|
||||||
|
@Schema(description = "总计数")
|
||||||
private String totalCont;
|
private String totalCont;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,25 +105,30 @@ public class BgmInfo implements Serializable {
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "评分")
|
||||||
public static class Rating implements Serializable {
|
public static class Rating implements Serializable {
|
||||||
/**
|
/**
|
||||||
* 级别
|
* 级别
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "级别")
|
||||||
private Integer rank;
|
private Integer rank;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 评分
|
* 评分
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "评分")
|
||||||
private Double score;
|
private Double score;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 评分数
|
* 评分数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "评分数")
|
||||||
private Integer total;
|
private Integer total;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 各阶段评分数
|
* 各阶段评分数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "各阶段评分数")
|
||||||
private Map<String, Integer> count;
|
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;
|
package ani.rss.entity;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
@@ -10,19 +11,23 @@ import java.io.Serializable;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "合集")
|
||||||
public class CollectionInfo implements Serializable {
|
public class CollectionInfo implements Serializable {
|
||||||
/**
|
/**
|
||||||
* 种子文件 base64
|
* 种子文件 base64
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "种子文件 base64")
|
||||||
private String torrent;
|
private String torrent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订阅
|
* 订阅
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "订阅")
|
||||||
private Ani ani;
|
private Ani ani;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bgm
|
* bgm
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "bgm")
|
||||||
private BgmInfo bgmInfo;
|
private BgmInfo bgmInfo;
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package ani.rss.entity;
|
|||||||
|
|
||||||
import ani.rss.enums.BgmTokenTypeEnum;
|
import ani.rss.enums.BgmTokenTypeEnum;
|
||||||
import ani.rss.enums.SortTypeEnum;
|
import ani.rss.enums.SortTypeEnum;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
@@ -13,305 +14,366 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "设置")
|
||||||
public class Config implements Serializable {
|
public class Config implements Serializable {
|
||||||
/**
|
/**
|
||||||
* Mikan Host
|
* Mikan Host
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "Mikan Host")
|
||||||
private String mikanHost;
|
private String mikanHost;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tmdbApi
|
* tmdbApi
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "TMDB API")
|
||||||
private String tmdbApi;
|
private String tmdbApi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tmdbApiKey
|
* tmdbApiKey
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "TMDB API Key")
|
||||||
private String tmdbApiKey;
|
private String tmdbApiKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 仅获取动漫
|
* 仅获取动漫
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "仅获取动漫")
|
||||||
private Boolean tmdbAnime;
|
private Boolean tmdbAnime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载工具
|
* 下载工具
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "下载工具类型")
|
||||||
private String downloadToolType;
|
private String downloadToolType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载重试次数
|
* 下载重试次数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "下载重试次数")
|
||||||
private Integer downloadRetry;
|
private Integer downloadRetry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载工具 地址
|
* 下载工具 地址
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "下载工具地址")
|
||||||
private String downloadToolHost;
|
private String downloadToolHost;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载工具 用户名
|
* 下载工具 用户名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "下载工具用户名")
|
||||||
private String downloadToolUsername;
|
private String downloadToolUsername;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载工具 密码
|
* 下载工具 密码
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "下载工具密码")
|
||||||
private String downloadToolPassword;
|
private String downloadToolPassword;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* qb下载时,使用qb自身的保存路径配置(未下载完成的使用临时目录,复制种子文件)
|
* qb下载时,使用qb自身的保存路径配置(未下载完成的使用临时目录,复制种子文件)
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "使用 qb 自身保存路径")
|
||||||
private Boolean qbUseDownloadPath;
|
private Boolean qbUseDownloadPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分享率
|
* 分享率
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "分享率")
|
||||||
private Integer ratioLimit;
|
private Integer ratioLimit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 总做种时长
|
* 总做种时长
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "总做种时长")
|
||||||
private Integer seedingTimeLimit;
|
private Integer seedingTimeLimit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 非活跃时长
|
* 非活跃时长
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "非活跃时长")
|
||||||
private Integer inactiveSeedingTimeLimit;
|
private Integer inactiveSeedingTimeLimit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载路径
|
* 下载路径
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "下载路径模版")
|
||||||
private String downloadPathTemplate;
|
private String downloadPathTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 剧场版下载路径
|
* 剧场版下载路径
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "剧场版下载路径模版")
|
||||||
private String ovaDownloadPathTemplate;
|
private String ovaDownloadPathTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义标签
|
* 自定义标签
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义标签")
|
||||||
private List<String> customTags;
|
private List<String> customTags;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 优先保留开关
|
* 优先保留开关
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "优先保留开关")
|
||||||
private Boolean priorityKeywordsEnable;
|
private Boolean priorityKeywordsEnable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 优先保留关键词列表
|
* 优先保留关键词列表
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "优先保留关键词列表")
|
||||||
private List<String> priorityKeywords;
|
private List<String> priorityKeywords;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 延迟下载
|
* 延迟下载
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "延迟下载(分钟)")
|
||||||
private Integer delayedDownload;
|
private Integer delayedDownload;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 显示评分
|
* 显示评分
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "显示评分")
|
||||||
private Boolean scoreShow;
|
private Boolean scoreShow;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RSS 间隔(分钟)
|
* RSS 间隔(分钟)
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "RSS 间隔(分钟)")
|
||||||
private Integer rssSleepMinutes;
|
private Integer rssSleepMinutes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重命名间隔(秒)
|
* 重命名间隔(秒)
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "重命名间隔(秒)")
|
||||||
private Integer renameSleepSeconds;
|
private Integer renameSleepSeconds;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动重命名
|
* 自动重命名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自动重命名")
|
||||||
private Boolean rename;
|
private Boolean rename;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* rss开关
|
* rss开关
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "RSS 开关")
|
||||||
private Boolean rss;
|
private Boolean rss;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* rss 超时时间 秒
|
* rss 超时时间 秒
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "RSS 超时时间(秒)")
|
||||||
private Integer rssTimeout;
|
private Integer rssTimeout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件已下载自动跳过
|
* 文件已下载自动跳过
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "文件已下载自动跳过")
|
||||||
private Boolean fileExist;
|
private Boolean fileExist;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 等待做种完毕
|
* 等待做种完毕
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "等待做种完毕")
|
||||||
private Boolean awaitStalledUP;
|
private Boolean awaitStalledUP;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动删除已完成任务
|
* 自动删除已完成任务
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自动删除已完成任务")
|
||||||
private Boolean delete;
|
private Boolean delete;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 仅在主RSS更新后删除备用RSS
|
* 仅在主RSS更新后删除备用RSS
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "主RSS更新后删除备用RSS")
|
||||||
private Boolean deleteStandbyRSSOnly;
|
private Boolean deleteStandbyRSSOnly;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动推断剧集偏移
|
* 自动推断剧集偏移
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自动推断剧集偏移")
|
||||||
private Boolean offset;
|
private Boolean offset;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取标题时带上年份
|
* 获取标题时带上年份
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "获取标题时带上年份")
|
||||||
private Boolean titleYear;
|
private Boolean titleYear;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动禁用已完结番剧的订阅
|
* 自动禁用已完结番剧的订阅
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自动禁用已完结番剧订阅")
|
||||||
private Boolean autoDisabled;
|
private Boolean autoDisabled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动跳过 x.5 集数
|
* 自动跳过 x.5 集数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自动跳过 x.5 集数")
|
||||||
private Boolean skip5;
|
private Boolean skip5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 备用RSS
|
* 备用RSS
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "备用RSS")
|
||||||
private Boolean standbyRss;
|
private Boolean standbyRss;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 多字幕组共存模式
|
* 多字幕组共存模式
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "多字幕组共存模式")
|
||||||
private Boolean coexist;
|
private Boolean coexist;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 最大日志条数
|
* 最大日志条数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "最大日志条数")
|
||||||
private Integer logsMax;
|
private Integer logsMax;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DEBUG
|
* DEBUG
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "DEBUG")
|
||||||
private Boolean debug;
|
private Boolean debug;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 仅启用主rss摸鱼检测
|
* 仅启用主rss摸鱼检测
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "仅启用主RSS摸鱼检测")
|
||||||
private Boolean procrastinatingMasterOnly;
|
private Boolean procrastinatingMasterOnly;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 代理是否开启
|
* 代理是否开启
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "代理是否开启")
|
||||||
private Boolean proxy;
|
private Boolean proxy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 代理host
|
* 代理host
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "代理 host")
|
||||||
private String proxyHost;
|
private String proxyHost;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 代理端口
|
* 代理端口
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "代理端口")
|
||||||
private Integer proxyPort;
|
private Integer proxyPort;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 代理用户名
|
* 代理用户名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "代理用户名")
|
||||||
private String proxyUsername;
|
private String proxyUsername;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 代理密码
|
* 代理密码
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "代理密码")
|
||||||
private String proxyPassword;
|
private String proxyPassword;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同时下载数量限制
|
* 同时下载数量限制
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "同时下载数量限制")
|
||||||
private Integer downloadCount;
|
private Integer downloadCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录信息
|
* 登录信息
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "登录信息")
|
||||||
private Login login;
|
private Login login;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 禁止多端登录
|
* 禁止多端登录
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "禁止多端登录")
|
||||||
private Boolean multiLoginForbidden;
|
private Boolean multiLoginForbidden;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录有效时间/小时
|
* 登录有效时间/小时
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "登录有效时间(小时)")
|
||||||
private Integer loginEffectiveHours;
|
private Integer loginEffectiveHours;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局排除
|
* 全局排除
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "全局排除")
|
||||||
private List<String> exclude;
|
private List<String> exclude;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认导入全局排除
|
* 默认导入全局排除
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "默认导入全局排除")
|
||||||
private Boolean importExclude;
|
private Boolean importExclude;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认启用全局排除
|
* 默认启用全局排除
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "默认启用全局排除")
|
||||||
private Boolean enabledExclude;
|
private Boolean enabledExclude;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BGM日语标题
|
* BGM日语标题
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "BGM日语标题")
|
||||||
private Boolean bgmJpName;
|
private Boolean bgmJpName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tmdb
|
* tmdb
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "启用 TMDB")
|
||||||
private Boolean tmdb;
|
private Boolean tmdb;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取标题时带有tmdbId
|
* 获取标题时带有tmdbId
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "标题带 TMDB ID")
|
||||||
private Boolean tmdbId;
|
private Boolean tmdbId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 剧集标题是否支持plex命名方式
|
* 剧集标题是否支持plex命名方式
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "Plex 命名方式")
|
||||||
private Boolean tmdbIdPlexMode;
|
private Boolean tmdbIdPlexMode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tmdb 语言
|
* tmdb 语言
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "TMDB 语言")
|
||||||
private String tmdbLanguage;
|
private String tmdbLanguage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取罗马音
|
* 获取罗马音
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "获取罗马音")
|
||||||
private Boolean tmdbRomaji;
|
private Boolean tmdbRomaji;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开启ip白名单
|
* 开启ip白名单
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "开启 IP 白名单")
|
||||||
private Boolean ipWhitelist;
|
private Boolean ipWhitelist;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ip白名单
|
* ip白名单
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "IP 白名单")
|
||||||
private String ipWhitelistStr;
|
private String ipWhitelistStr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 显示已下载视频列表
|
* 显示已下载视频列表
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "显示已下载视频列表")
|
||||||
private Boolean showPlaylist;
|
private Boolean showPlaylist;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检测遗漏集数
|
* 检测遗漏集数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "检测遗漏集数")
|
||||||
private Boolean omit;
|
private Boolean omit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -319,295 +381,354 @@ public class Config implements Serializable {
|
|||||||
* <p>
|
* <p>
|
||||||
* INPUT or AUTO
|
* INPUT or AUTO
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "BGM Token 类型")
|
||||||
private BgmTokenTypeEnum bgmTokenType;
|
private BgmTokenTypeEnum bgmTokenType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bgmToken
|
* bgmToken
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "BGM Token")
|
||||||
private String bgmToken;
|
private String bgmToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bgmAppID
|
* bgmAppID
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "BGM App ID")
|
||||||
private String bgmAppID;
|
private String bgmAppID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bgmAppID
|
* bgmAppID
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "BGM App Secret")
|
||||||
private String bgmAppSecret;
|
private String bgmAppSecret;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bgmRefreshToken
|
* bgmRefreshToken
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "BGM Refresh Token")
|
||||||
private String bgmRefreshToken;
|
private String bgmRefreshToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bgmRedirectUri
|
* bgmRedirectUri
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "BGM Redirect URI")
|
||||||
private String bgmRedirectUri;
|
private String bgmRedirectUri;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* api key
|
* api key
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "API Key")
|
||||||
private String apiKey;
|
private String apiKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按星期展示
|
* 按星期展示
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "按星期展示")
|
||||||
private Boolean weekShow;
|
private Boolean weekShow;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 只下载最新集
|
* 只下载最新集
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "只下载最新集")
|
||||||
private Boolean downloadNew;
|
private Boolean downloadNew;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 仅允许内网ip访问
|
* 仅允许内网ip访问
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "仅允许内网 IP 访问")
|
||||||
private Boolean innerIP;
|
private Boolean innerIP;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重命名模版
|
* 重命名模版
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "重命名模版")
|
||||||
private String renameTemplate;
|
private String renameTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重命名时剔除 年份 如 (2024)
|
* 重命名时剔除 年份 如 (2024)
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "重命名剔除年份")
|
||||||
private Boolean renameDelYear;
|
private Boolean renameDelYear;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重命名时剔除 tmdbId [tmdbid=242143]
|
* 重命名时剔除 tmdbId [tmdbid=242143]
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "重命名剔除 TMDB ID")
|
||||||
private Boolean renameDelTmdbId;
|
private Boolean renameDelTmdbId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验登录IP
|
* 校验登录IP
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "校验登录 IP")
|
||||||
private Boolean verifyLoginIp;
|
private Boolean verifyLoginIp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动更新 trackers
|
* 自动更新 trackers
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自动更新 trackers")
|
||||||
private Boolean autoTrackersUpdate;
|
private Boolean autoTrackersUpdate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trackers更新地址
|
* Trackers更新地址
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "Trackers 更新地址")
|
||||||
private String trackersUpdateUrls;
|
private String trackersUpdateUrls;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息模版
|
* 消息模版
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "消息模版")
|
||||||
private String notificationTemplate;
|
private String notificationTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动更新
|
* 自动更新
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自动更新")
|
||||||
private Boolean autoUpdate;
|
private Boolean autoUpdate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 版本
|
* 版本
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "版本")
|
||||||
private String version;
|
private String version;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取BGM封面图片质量
|
* 获取BGM封面图片质量
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "BGM 封面图片质量")
|
||||||
private String bgmImage;
|
private String bgmImage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义CSS
|
* 自定义CSS
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义 CSS")
|
||||||
private String customCss;
|
private String customCss;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义JS
|
* 自定义JS
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义 JS")
|
||||||
private String customJs;
|
private String customJs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义集数获取规则
|
* 自定义集数获取规则
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义集数获取规则")
|
||||||
private Boolean customEpisode;
|
private Boolean customEpisode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义集数获取规则
|
* 自定义集数获取规则
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义集数获取规则表达式")
|
||||||
private String customEpisodeStr;
|
private String customEpisodeStr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义集数获取规则 groupIndex
|
* 自定义集数获取规则 groupIndex
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义集数获取规则 groupIndex")
|
||||||
private Integer customEpisodeGroupIndex;
|
private Integer customEpisodeGroupIndex;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpenList driver
|
* OpenList driver
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "OpenList Driver")
|
||||||
private String provider;
|
private String provider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加行订阅是是否开启自动上传
|
* 添加行订阅是是否开启自动上传
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "新增订阅自动上传")
|
||||||
private Boolean upload;
|
private Boolean upload;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传速度限制
|
* 上传速度限制
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "上传速度限制")
|
||||||
private Long upLimit;
|
private Long upLimit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载速度限制
|
* 下载速度限制
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "下载速度限制")
|
||||||
private Long dlLimit;
|
private Long dlLimit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 捐赠过期时间
|
* 捐赠过期时间
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "捐赠过期时间")
|
||||||
private Long expirationTime;
|
private Long expirationTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 爱发电订单号
|
* 爱发电订单号
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "爱发电订单号")
|
||||||
private String outTradeNo;
|
private String outTradeNo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 捐赠或试用是否过期
|
* 捐赠或试用是否过期
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "捐赠或试用是否过期")
|
||||||
private Boolean verifyExpirationTime;
|
private Boolean verifyExpirationTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 试用
|
* 试用
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "试用")
|
||||||
private Boolean tryOut;
|
private Boolean tryOut;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 摸鱼
|
* 摸鱼
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "摸鱼")
|
||||||
private Boolean procrastinating;
|
private Boolean procrastinating;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 摸鱼天数
|
* 摸鱼天数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "摸鱼天数")
|
||||||
private Integer procrastinatingDay;
|
private Integer procrastinatingDay;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* github 加速
|
* github 加速
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "GitHub 加速")
|
||||||
private String github;
|
private String github;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义github加速
|
* 自定义github加速
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义 GitHub 加速")
|
||||||
private Boolean customGithub;
|
private Boolean customGithub;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义github加速网址
|
* 自定义github加速网址
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自定义 GitHub 加速地址")
|
||||||
private String customGithubUrl;
|
private String customGithubUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* github Token
|
* github Token
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "GitHub Token")
|
||||||
private String githubToken;
|
private String githubToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开启 OpenList 列表刷新
|
* 开启 OpenList 列表刷新
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "开启 OpenList 列表刷新")
|
||||||
private Boolean alistRefresh;
|
private Boolean alistRefresh;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpenList 刷新延迟
|
* OpenList 刷新延迟
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "OpenList 刷新延迟")
|
||||||
private Long alistRefreshDelayed;
|
private Long alistRefreshDelayed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动更新总集数信息
|
* 自动更新总集数信息
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "自动更新总集数信息")
|
||||||
private Boolean updateTotalEpisodeNumber;
|
private Boolean updateTotalEpisodeNumber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 强制更新总集数信息
|
* 强制更新总集数信息
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "强制更新总集数信息")
|
||||||
private Boolean forceUpdateTotalEpisodeNumber;
|
private Boolean forceUpdateTotalEpisodeNumber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpenList 离线超时 分钟
|
* OpenList 离线超时 分钟
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "OpenList 离线超时(分钟)")
|
||||||
private Integer alistDownloadTimeout;
|
private Integer alistDownloadTimeout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpenList 下载重试次数
|
* OpenList 下载重试次数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "OpenList 下载重试次数")
|
||||||
private Long alistDownloadRetryNumber;
|
private Long alistDownloadRetryNumber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置备份
|
* 设置备份
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "设置备份")
|
||||||
private Boolean configBackup;
|
private Boolean configBackup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 备份天数
|
* 备份天数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "备份天数")
|
||||||
private Integer configBackupDay;
|
private Integer configBackupDay;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 展示最后更新时间
|
* 展示最后更新时间
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "展示最后更新时间")
|
||||||
private Boolean showLastDownloadTime;
|
private Boolean showLastDownloadTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 番剧完结迁移
|
* 番剧完结迁移
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "番剧完结迁移")
|
||||||
private Boolean completed;
|
private Boolean completed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 番剧完结迁移位置
|
* 番剧完结迁移位置
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "番剧完结迁移位置")
|
||||||
private String completedPathTemplate;
|
private String completedPathTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通知
|
* 通知
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "通知配置列表")
|
||||||
private List<NotificationConfig> notificationConfigList;
|
private List<NotificationConfig> notificationConfigList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加订阅时自动复制主rss至备用rss
|
* 添加订阅时自动复制主rss至备用rss
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "添加订阅时复制主RSS至备用")
|
||||||
private Boolean copyMasterToStandby;
|
private Boolean copyMasterToStandby;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 排序方式
|
* 排序方式
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "排序方式")
|
||||||
private SortTypeEnum sortType;
|
private SortTypeEnum sortType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 代理列表
|
* 代理列表
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "代理列表")
|
||||||
private String proxyList;
|
private String proxyList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刮削开关
|
* 刮削开关
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "刮削开关")
|
||||||
private Boolean scrape;
|
private Boolean scrape;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重名的订阅将允许被替换
|
* 重名的订阅将允许被替换
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "重名订阅允许替换")
|
||||||
private Boolean replace;
|
private Boolean replace;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 最大文件名长度 不包含后缀 如: .mkv .mp4
|
* 最大文件名长度 不包含后缀 如: .mkv .mp4
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "最大文件名长度(不含后缀)")
|
||||||
private Integer maxFileNameLength;
|
private Integer maxFileNameLength;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 限制尝试次数
|
* 限制尝试次数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "限制尝试次数")
|
||||||
private Boolean limitLoginAttempts;
|
private Boolean limitLoginAttempts;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建信息
|
* 构建信息
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "构建信息")
|
||||||
private String buildInfo;
|
private String buildInfo;
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,10 @@ package ani.rss.entity;
|
|||||||
|
|
||||||
import ani.rss.util.other.ConfigUtil;
|
import ani.rss.util.other.ConfigUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import wushuo.tmdb.api.entity.TmdbConfig;
|
import wushuo.tmdb.api.entity.TmdbConfig;
|
||||||
|
|
||||||
|
@Schema(description = "自定义 TMDB 配置")
|
||||||
public class CustomTmdbConfig extends TmdbConfig {
|
public class CustomTmdbConfig extends TmdbConfig {
|
||||||
|
|
||||||
public final static Config CONFIG = ConfigUtil.CONFIG;
|
public final static Config CONFIG = ConfigUtil.CONFIG;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package ani.rss.entity;
|
package ani.rss.entity;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
@@ -10,7 +11,10 @@ import java.io.Serializable;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "Emby 媒体库")
|
||||||
public class EmbyViews implements Serializable {
|
public class EmbyViews implements Serializable {
|
||||||
|
@Schema(description = "id")
|
||||||
private String id;
|
private String id;
|
||||||
|
@Schema(description = "名称")
|
||||||
private String name;
|
private String name;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package ani.rss.entity;
|
package ani.rss.entity;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
@@ -11,33 +12,43 @@ import java.io.Serializable;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "EmbyWebHook")
|
||||||
public class EmbyWebHook implements Serializable {
|
public class EmbyWebHook implements Serializable {
|
||||||
|
|
||||||
@SerializedName(value = "title", alternate = "Title")
|
@SerializedName(value = "title", alternate = "Title")
|
||||||
|
@Schema(description = "标题")
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
@SerializedName(value = "description", alternate = "Description")
|
@SerializedName(value = "description", alternate = "Description")
|
||||||
|
@Schema(description = "描述")
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
@SerializedName(value = "date", alternate = "Date")
|
@SerializedName(value = "date", alternate = "Date")
|
||||||
|
@Schema(description = "日期")
|
||||||
private String date;
|
private String date;
|
||||||
|
|
||||||
@SerializedName(value = "event", alternate = "Event")
|
@SerializedName(value = "event", alternate = "Event")
|
||||||
|
@Schema(description = "事件")
|
||||||
private String event;
|
private String event;
|
||||||
|
|
||||||
@SerializedName(value = "severity", alternate = "Severity")
|
@SerializedName(value = "severity", alternate = "Severity")
|
||||||
|
@Schema(description = "严重级别")
|
||||||
private String severity;
|
private String severity;
|
||||||
|
|
||||||
@SerializedName(value = "user", alternate = "User")
|
@SerializedName(value = "user", alternate = "User")
|
||||||
|
@Schema(description = "用户信息")
|
||||||
private User user;
|
private User user;
|
||||||
|
|
||||||
@SerializedName(value = "server", alternate = "Server")
|
@SerializedName(value = "server", alternate = "Server")
|
||||||
|
@Schema(description = "服务器信息")
|
||||||
private Server server;
|
private Server server;
|
||||||
|
|
||||||
@SerializedName(value = "item", alternate = "Item")
|
@SerializedName(value = "item", alternate = "Item")
|
||||||
|
@Schema(description = "项目信息")
|
||||||
private Item item;
|
private Item item;
|
||||||
|
|
||||||
@SerializedName(value = "playbackInfo", alternate = "PlaybackInfo")
|
@SerializedName(value = "playbackInfo", alternate = "PlaybackInfo")
|
||||||
|
@Schema(description = "播放信息")
|
||||||
private PlaybackInfo playbackInfo;
|
private PlaybackInfo playbackInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,23 +56,27 @@ public class EmbyWebHook implements Serializable {
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "项目信息")
|
||||||
public static class Item implements Serializable {
|
public static class Item implements Serializable {
|
||||||
/**
|
/**
|
||||||
* 文件路径
|
* 文件路径
|
||||||
*/
|
*/
|
||||||
@SerializedName(value = "path", alternate = "Path")
|
@SerializedName(value = "path", alternate = "Path")
|
||||||
|
@Schema(description = "文件路径")
|
||||||
private String path;
|
private String path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 剧集名
|
* 剧集名
|
||||||
*/
|
*/
|
||||||
@SerializedName(value = "seriesName", alternate = "SeriesName")
|
@SerializedName(value = "seriesName", alternate = "SeriesName")
|
||||||
|
@Schema(description = "剧集名")
|
||||||
private String seriesName;
|
private String seriesName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件名
|
* 文件名
|
||||||
*/
|
*/
|
||||||
@SerializedName(value = "fileName", alternate = "FileName")
|
@SerializedName(value = "fileName", alternate = "FileName")
|
||||||
|
@Schema(description = "文件名")
|
||||||
private String fileName;
|
private String fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,17 +85,20 @@ public class EmbyWebHook implements Serializable {
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "用户信息")
|
||||||
public static class User implements Serializable {
|
public static class User implements Serializable {
|
||||||
/**
|
/**
|
||||||
* 用户 Id
|
* 用户 Id
|
||||||
*/
|
*/
|
||||||
@SerializedName(value = "id", alternate = "Id")
|
@SerializedName(value = "id", alternate = "Id")
|
||||||
|
@Schema(description = "用户 Id")
|
||||||
private String id;
|
private String id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户名称
|
* 用户名称
|
||||||
*/
|
*/
|
||||||
@SerializedName(value = "name", alternate = "Name")
|
@SerializedName(value = "name", alternate = "Name")
|
||||||
|
@Schema(description = "用户名称")
|
||||||
private String name;
|
private String name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,23 +107,27 @@ public class EmbyWebHook implements Serializable {
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "服务器信息")
|
||||||
public static class Server implements Serializable {
|
public static class Server implements Serializable {
|
||||||
/**
|
/**
|
||||||
* 服务器 Id
|
* 服务器 Id
|
||||||
*/
|
*/
|
||||||
@SerializedName(value = "id", alternate = "Id")
|
@SerializedName(value = "id", alternate = "Id")
|
||||||
|
@Schema(description = "服务器 Id")
|
||||||
private String id;
|
private String id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务器名称
|
* 服务器名称
|
||||||
*/
|
*/
|
||||||
@SerializedName(value = "name", alternate = "Name")
|
@SerializedName(value = "name", alternate = "Name")
|
||||||
|
@Schema(description = "服务器名称")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务器版本号
|
* 服务器版本号
|
||||||
*/
|
*/
|
||||||
@SerializedName(value = "version", alternate = "Version")
|
@SerializedName(value = "version", alternate = "Version")
|
||||||
|
@Schema(description = "服务器版本号")
|
||||||
private String version;
|
private String version;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,11 +136,13 @@ public class EmbyWebHook implements Serializable {
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "播放信息")
|
||||||
public static class PlaybackInfo implements Serializable {
|
public static class PlaybackInfo implements Serializable {
|
||||||
/**
|
/**
|
||||||
* 是否播放完成
|
* 是否播放完成
|
||||||
*/
|
*/
|
||||||
@SerializedName(value = "playedToCompletion", alternate = "PlayedToCompletion")
|
@SerializedName(value = "playedToCompletion", alternate = "PlayedToCompletion")
|
||||||
|
@Schema(description = "是否播放完成")
|
||||||
private Boolean playedToCompletion;
|
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;
|
package ani.rss.entity;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
@@ -11,59 +12,71 @@ import java.util.Date;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "下载项")
|
||||||
public class Item implements Serializable {
|
public class Item implements Serializable {
|
||||||
/**
|
/**
|
||||||
* 标题
|
* 标题
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "标题")
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重命名
|
* 重命名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "重命名")
|
||||||
private String reName;
|
private String reName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 种子
|
* 种子
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "种子")
|
||||||
private String torrent;
|
private String torrent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* infoHash
|
* infoHash
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "infoHash")
|
||||||
private String infoHash;
|
private String infoHash;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 集数
|
* 集数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "集数")
|
||||||
private Double episode;
|
private Double episode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 大小
|
* 大小
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "大小")
|
||||||
private String size;
|
private String size;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 大小
|
* 大小
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "大小")
|
||||||
private Long length;
|
private Long length;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 本地已存在
|
* 本地已存在
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "本地已存在")
|
||||||
private Boolean local;
|
private Boolean local;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主 rss
|
* 主 rss
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "主 rss")
|
||||||
private Boolean master;
|
private Boolean master;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字幕组
|
* 字幕组
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "字幕组")
|
||||||
private String subgroup;
|
private String subgroup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发布时间
|
* 发布时间
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "发布时间")
|
||||||
private Date pubDate;
|
private Date pubDate;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package ani.rss.entity;
|
package ani.rss.entity;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
@@ -10,25 +11,30 @@ import java.io.Serializable;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "日志")
|
||||||
public class Log implements Serializable {
|
public class Log implements Serializable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日志信息
|
* 日志信息
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "日志信息")
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日志级别
|
* 日志级别
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "日志级别")
|
||||||
private String level;
|
private String level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类路径
|
* 类路径
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "类路径")
|
||||||
private String loggerName;
|
private String loggerName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 线程名
|
* 线程名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "线程名")
|
||||||
private String threadName;
|
private String threadName;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package ani.rss.entity;
|
package ani.rss.entity;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
@@ -10,21 +11,26 @@ import java.io.Serializable;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "登录")
|
||||||
public class Login implements Serializable {
|
public class Login implements Serializable {
|
||||||
/**
|
/**
|
||||||
* 用户名
|
* 用户名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "用户名")
|
||||||
private String username;
|
private String username;
|
||||||
/**
|
/**
|
||||||
* 密码
|
* 密码
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "密码")
|
||||||
private String password;
|
private String password;
|
||||||
/**
|
/**
|
||||||
* ip
|
* ip
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "ip")
|
||||||
private String ip;
|
private String ip;
|
||||||
/**
|
/**
|
||||||
* key
|
* key
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "key")
|
||||||
private String 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