├── .gitattributes ├── .github └── workflows │ └── docker-publish.yml ├── .gitignore ├── Dockerfile ├── README.md ├── docker-compose.yml ├── entrypoint.sh ├── log └── strm.log ├── pom.xml └── src └── main ├── java └── cn │ └── jackding │ └── aliststrm │ ├── AlistStrmApplication.java │ ├── ScheduledTask.java │ ├── alist │ └── AlistService.java │ ├── config │ └── Config.java │ ├── controller │ └── NotifyController.java │ ├── service │ ├── AsynService.java │ ├── CopyAlistFileService.java │ └── StrmService.java │ ├── tg │ ├── ResponseHandler.java │ ├── StrmBot.java │ └── TgSendMsg.java │ └── util │ ├── SpringContextUtil.java │ └── Utils.java └── resources ├── application.properties └── log4j2.xml /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Java CI with Maven docker 2 | 3 | on: 4 | push: 5 | # 分支 6 | branches: [ main ] 7 | pull_request: 8 | branches: [ main ] 9 | 10 | jobs: 11 | compile: 12 | runs-on: ubuntu-latest 13 | name: Running Java ${{ matrix.java }} compile 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Set up JDK 1.8 17 | uses: actions/setup-java@v3 18 | with: 19 | distribution: 'temurin' 20 | java-version: '8' 21 | cache: 'maven' 22 | - name: 编译代码 23 | run: mvn compile 24 | - name: Deploy the JAR file to the remote server 25 | uses: actions/checkout@v3 26 | - name: Set up JDK 1.8 27 | uses: actions/setup-java@v3 28 | with: 29 | distribution: 'temurin' 30 | java-version: '8' 31 | cache: 'maven' 32 | - name: Generate the package 33 | run: mvn -B package --file pom.xml -Dmaven.test.skip=true 34 | - name: Set up QEMU 35 | uses: docker/setup-qemu-action@v2 36 | 37 | - name: Set up Docker Buildx 38 | uses: docker/setup-buildx-action@v2 39 | 40 | - name: Login to DockerHub 41 | uses: docker/login-action@v2 42 | with: 43 | username: ${{ secrets.DOCKER_USERNAME }} 44 | password: ${{ secrets.DOCKER_PASSWORD }} 45 | - name: Build Latest Image 46 | uses: docker/build-push-action@v3 47 | with: 48 | context: . 49 | file: Dockerfile 50 | platforms: linux/amd64,linux/arm64,linux/arm/v7 51 | push: true 52 | tags: | 53 | ${{ secrets.DOCKER_USERNAME }}/alist-strm:latest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | /config 35 | /log 36 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:8u412-b08-jre-jammy 2 | LABEL title="alist-strm" 3 | LABEL description="将alist的视频文件生成媒体播放设备可播放的strm文件" 4 | LABEL authors="JackDing" 5 | RUN apt-get update && apt-get install -y gosu && rm -rf /var/lib/apt/lists/* 6 | WORKDIR /app 7 | COPY ./target/application.jar /app/aliststrm.jar 8 | COPY --chmod=755 entrypoint.sh /entrypoint.sh 9 | ENV TZ=Asia/Shanghai 10 | ENV alistServerUrl="" 11 | ENV alistServerToken="" 12 | ENV alistScanPath="" 13 | ENV isDownSub="0" 14 | ENV slowMode="" 15 | ENV encode="1" 16 | ENV tgToken="" 17 | ENV tgUserId="" 18 | ENV JAVA_OPTS="-Xms32m -Xmx512m" 19 | ENV srcDir="" 20 | ENV dstDir="" 21 | ENV replaceDir="" 22 | ENV strmAfterSync="1" 23 | ENV runAfterStartup="1" 24 | ENV minFileSize="100" 25 | ENV logLevel="" 26 | ENV maxIdleConnections="5" 27 | ENV refresh="1" 28 | ENV scheduledCron="0 0 6,18 * * ?" 29 | ENV PUID=0 30 | ENV PGID=0 31 | ENV UMASK=022 32 | ENTRYPOINT [ "/entrypoint.sh" ] 33 | VOLUME /data 34 | VOLUME /log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # alist-strm 2 | alist生成可播放strm视频文件 3 | 4 | ## 主要功能 5 | 6 | ``` 7 | 1.生成strm文件,启动即执行,定时任务执行,tg机器人执行,接口调用执行 8 | 2.复制同步alist的两个文件夹,tg机器人执行,接口调用执行 9 | 3.可支持第三方app回调,自动化处理,如qb下载完成通知,自动复制alist挂载的本地硬盘复制到云盘然后生成strm文件 10 | ``` 11 | 12 | ## docker部署 13 | 14 | ``` 15 | 部署前参数需要修改 16 | 必要参数 17 | alistServerUrl alist地址 如http://192.168.1.2:5244 18 | alistServerToken 可在alist后台获取 19 | alistScanPath 需要生成strm文件的目录如http://192.168.1.2:5244/阿里云分享/电影 那就填入/阿里云分享/电影 20 | 可选参数 21 | slowMode 单线程模式,防止请求网盘太快,默认0,启用填1 22 | encode 是否编码strm文件里面的链接 默认1启用 不启用填0 23 | isDownSub 是否下载目录里面的字幕文件 默认0不下载 下载填1 24 | runAfterStartup 启动是否立即执行同步任务 默认启用1,启用填0 25 | logLevel 日志级别 DEBUG INFO ERROR OFF 26 | tgToken tg机器人token,通过t.me/BotFather机器人创建bot获取token 27 | tgUserId tg用户id,通过t.me/userinfobot机器人获取userId 28 | maxIdleConnections HTTP调用线程池参数配置 默认5 29 | refresh参数 是否去读取网盘最新数据,1是实时读取网盘 0是读取alist缓存 默认1 30 | PUID 用户参数 生成strm文件的所属用户 31 | PGID 用户组参数 生成strm文件的所属用户组 32 | 33 | 复制alist不同目录的视频 源目录删除不会删除目标目录文件 只会新增 34 | srcDir 源目录 35 | dstDir 目标目录 36 | minFileSize 复制的最小文件 37 | replaceDir qb的下载根目录 使用/api/v1/notifyByDir接口时需要填 38 | strmAfterSync参数,支持上传完文件不生成strm 默认1生成strm 0不生成strm 39 | scheduledCron 定时任务cron参数,默认0 0 6,18 * * ? 每天6点和18点执行同步任务 40 | 41 | ``` 42 | 43 | # 开发计划 44 | 45 | - [x] tg机器人命令生成strm文件 46 | - [ ] ... 47 | 48 | # 更新记录 49 | 50 | ``` 51 | 20240610 重构代码,增加tg机器人命令strm、strmdir 52 | 20240617 增加下载目录中字幕文件的功能 53 | 20240617 增加alist目录复制的功能 使用tg机器人/sync命令执行任务 54 | 20240622 执行sync任务之后自动执行strm任务 增加定时任务每天6、18点执行sync任务 55 | 20240623 增加调用接口api/v1/notify直接执行复制sync任务 配合qb使用 监听端口是6894 56 | 20240624 增加/syncdir命令执行指定目录的同步复制,如:/sync /阿里云盘/电影#/115网盘/电影,就会将/阿里云盘/电影下的视频同步复制到/115网盘/电影 57 | 20240630 增加参数minFileSize 默认100 判断视频大小是否大于100MB,如果大于100MB才复制同步文件 58 | 20240724 增加参数配置日志级别 增加复制任务多线程执行 59 | 20240807 增加HTTP调用线程池参数配置maxIdleConnections 默认5 60 | 20240821 增加refresh参数 是否去读取网盘最新数据,1是实时读取网盘 0是读取alist缓存 默认1 61 | 20241107 增加/api/v1/notifyByDir接口和replaceDir参数,按需同步目录,防止同步文件太多,耗时过长 62 | 20241202 增加定时任务cron参数scheduledCron,默认0 0 6,18 * * ? 每天6点和18点执行同步任务 63 | 20250108 优化日志打印 64 | 20250111 复制任务完成之后立即生成strm文件,不用等待所有复制任务完成,自动重试失败的复制任务 65 | 20250115 增加strmAfterSync参数,支持上传完文件不生成strm 默认1生成strm 0不生成strm 66 | 20250424 优化性能 67 | ``` 68 | 69 | # docker CLI安装 70 | 71 | ``` 72 | docker run -d \ 73 | --name=alist-strm \ 74 | -e TZ=Asia/Shanghai \ 75 | -e alistServerUrl=http://192.168.1.2:5244 \ 76 | -e alistServerToken=xxx \ 77 | -e alistScanPath='/阿里云分享/电影' \ 78 | -e slowMode=0 \ 79 | -v /volume1/docker/alist-strm/data:/data \ 80 | jacksaoding/alist-strm:latest 81 | ``` 82 | 83 | # docker compose安装 84 | 85 | ``` 86 | version: "3" 87 | services: 88 | app: 89 | container_name: alist-strm 90 | image: 'jacksaoding/alist-strm:latest' 91 | network_mode: "host" 92 | environment: 93 | TZ: Asia/Shanghai 94 | alistServerUrl: http://192.168.1.2:5244 95 | alistServerToken: xxx 96 | alistScanPath: /阿里云分享/电影 97 | slowMode: 0 98 | volumes: 99 | - /volume1/docker/alist-strm/data:/data 100 | ``` 101 | 102 | # qb脚本参考 103 | 104 | `sh /config/notify.sh "%G" "%F"` 105 | 106 | ``` 107 | #!/bin/bash 108 | 109 | # 获取传递的标签 110 | TAG=$1 111 | dir=$2 112 | MOVIEPILOT="MOVIEPILOT" 113 | 114 | if [[ "$TAG" =~ "$MOVIEPILOT" ]]; then 115 | # 调用 notify 接口 116 | #curl -X POST http://192.168.31.66:6894/api/v1/notify 117 | curl -X POST -H "Content-Type: application/json" -d "{\"dir\": \"$dir\"}" http://192.168.31.66:6894/api/v1/notifyByDir &>/dev/null & 118 | fi 119 | ``` -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | app: 4 | container_name: alist-strm 5 | image: 'jacksaoding/alist-strm:latest' 6 | network_mode: "host" 7 | environment: 8 | TZ: Asia/Shanghai 9 | alistServerUrl: http://192.168.1.2:5244 10 | alistServerToken: xxx 11 | alistScanPath: /阿里云分享/电影 12 | volumes: 13 | - /volume1/docker/alist-strm/data:/data -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | chown -R "${PUID}":"${PGID}" /app /log 4 | chown "${PUID}":"${PGID}" /data 5 | 6 | cd /app 7 | 8 | umask "${UMASK}" 9 | 10 | exec gosu "${PUID}":"${PGID}" java $JAVA_OPTS -XX:+UseG1GC -XX:+OptimizeStringConcat -XX:+PrintGCDetails -Xloggc:/log/gc.log -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/log -jar /app/aliststrm.jar 11 | -------------------------------------------------------------------------------- /log/strm.log: -------------------------------------------------------------------------------- 1 | [2024-06-10 20:30:15:190] Starting AlistStrmApplication using Java 1.8.0_311 on Jacskao-Win11 with PID 100524 (D:\idea project\alist-strm\target\classes started by 90773 in D:\idea project\alist-strm) 2 | [2024-06-10 20:30:15:192] No active profile set, falling back to 1 default profile: "default" 3 | [2024-06-10 20:30:15:584] Started AlistStrmApplication in 0.658 seconds (JVM running for 1.908) 4 | [2024-06-10 20:30:16:386] No post action was detected for method with name [commands] 5 | [2024-06-10 20:30:16:388] No post action was detected for method with name [stats] 6 | [2024-06-10 20:30:16:388] No post action was detected for method with name [backup] 7 | [2024-06-10 20:30:16:394] No post action was detected for method with name [recover] 8 | [2024-06-10 20:30:16:395] No post action was detected for method with name [report] 9 | [2024-06-10 20:30:16:395] No post action was detected for method with name [ban] 10 | [2024-06-10 20:30:16:395] No post action was detected for method with name [unban] 11 | [2024-06-10 20:30:16:396] No post action was detected for method with name [promote] 12 | [2024-06-10 20:30:16:396] No post action was detected for method with name [claim] 13 | [2024-06-10 20:30:16:396] No post action was detected for method with name [demote] 14 | [2024-06-10 20:30:16:396] No post action was detected for method with name [strmdir] 15 | [2024-06-10 20:30:16:396] No post action was detected for method with name [strm] 16 | [2024-06-10 20:30:18:147] 开始执行strm任务2024-06-10T20:30:18.146 17 | [2024-06-10 20:30:18:247] 开始获取/每日更新 18 | [2024-06-10 20:30:18:272] 获取完成/每日更新 19 | [2024-06-10 20:30:19:274] 开始获取/每日更新/电影 20 | [2024-06-10 20:30:19:277] 获取完成/每日更新/电影 21 | [2024-06-10 20:30:20:292] 开始获取/每日更新/电影/老狐狸 22 | [2024-06-10 20:30:20:380] 获取完成/每日更新/电影/老狐狸 23 | [2024-06-10 20:30:21:390] 开始获取/每日更新/电视剧 24 | [2024-06-10 20:30:21:471] 获取完成/每日更新/电视剧 25 | [2024-06-10 20:30:21:471] 任务执行完成2024-06-10T20:30:21.471 26 | [2024-06-10 20:30:23:501] Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running 27 | org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException: Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running 28 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseInternal(PartialBotApiMethod.java:53) ~[telegrambots-meta-6.1.0.jar:?] 29 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseArray(PartialBotApiMethod.java:38) ~[telegrambots-meta-6.1.0.jar:?] 30 | at org.telegram.telegrambots.meta.api.methods.updates.GetUpdates.deserializeResponse(GetUpdates.java:83) ~[telegrambots-meta-6.1.0.jar:?] 31 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.getUpdatesFromServer(DefaultBotSession.java:260) ~[telegrambots-6.1.0.jar:?] 32 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.run(DefaultBotSession.java:189) ~[telegrambots-6.1.0.jar:?] 33 | [2024-06-10 20:30:28:100] Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running 34 | org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException: Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running 35 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseInternal(PartialBotApiMethod.java:53) ~[telegrambots-meta-6.1.0.jar:?] 36 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseArray(PartialBotApiMethod.java:38) ~[telegrambots-meta-6.1.0.jar:?] 37 | at org.telegram.telegrambots.meta.api.methods.updates.GetUpdates.deserializeResponse(GetUpdates.java:83) ~[telegrambots-meta-6.1.0.jar:?] 38 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.getUpdatesFromServer(DefaultBotSession.java:260) ~[telegrambots-6.1.0.jar:?] 39 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.run(DefaultBotSession.java:189) ~[telegrambots-6.1.0.jar:?] 40 | [2024-06-10 20:30:32:694] Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running 41 | org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException: Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running 42 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseInternal(PartialBotApiMethod.java:53) ~[telegrambots-meta-6.1.0.jar:?] 43 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseArray(PartialBotApiMethod.java:38) ~[telegrambots-meta-6.1.0.jar:?] 44 | at org.telegram.telegrambots.meta.api.methods.updates.GetUpdates.deserializeResponse(GetUpdates.java:83) ~[telegrambots-meta-6.1.0.jar:?] 45 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.getUpdatesFromServer(DefaultBotSession.java:260) ~[telegrambots-6.1.0.jar:?] 46 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.run(DefaultBotSession.java:189) ~[telegrambots-6.1.0.jar:?] 47 | [2024-06-10 20:30:37:289] Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running 48 | org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException: Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running 49 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseInternal(PartialBotApiMethod.java:53) ~[telegrambots-meta-6.1.0.jar:?] 50 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseArray(PartialBotApiMethod.java:38) ~[telegrambots-meta-6.1.0.jar:?] 51 | at org.telegram.telegrambots.meta.api.methods.updates.GetUpdates.deserializeResponse(GetUpdates.java:83) ~[telegrambots-meta-6.1.0.jar:?] 52 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.getUpdatesFromServer(DefaultBotSession.java:260) ~[telegrambots-6.1.0.jar:?] 53 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.run(DefaultBotSession.java:189) ~[telegrambots-6.1.0.jar:?] 54 | [2024-06-10 20:30:41:888] Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running 55 | org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException: Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running 56 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseInternal(PartialBotApiMethod.java:53) ~[telegrambots-meta-6.1.0.jar:?] 57 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseArray(PartialBotApiMethod.java:38) ~[telegrambots-meta-6.1.0.jar:?] 58 | at org.telegram.telegrambots.meta.api.methods.updates.GetUpdates.deserializeResponse(GetUpdates.java:83) ~[telegrambots-meta-6.1.0.jar:?] 59 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.getUpdatesFromServer(DefaultBotSession.java:260) ~[telegrambots-6.1.0.jar:?] 60 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.run(DefaultBotSession.java:189) ~[telegrambots-6.1.0.jar:?] 61 | [2024-06-10 20:30:46:494] Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running 62 | org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException: Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running 63 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseInternal(PartialBotApiMethod.java:53) ~[telegrambots-meta-6.1.0.jar:?] 64 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseArray(PartialBotApiMethod.java:38) ~[telegrambots-meta-6.1.0.jar:?] 65 | at org.telegram.telegrambots.meta.api.methods.updates.GetUpdates.deserializeResponse(GetUpdates.java:83) ~[telegrambots-meta-6.1.0.jar:?] 66 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.getUpdatesFromServer(DefaultBotSession.java:260) ~[telegrambots-6.1.0.jar:?] 67 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.run(DefaultBotSession.java:189) ~[telegrambots-6.1.0.jar:?] 68 | [2024-06-10 20:30:51:097] Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running 69 | org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException: Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running 70 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseInternal(PartialBotApiMethod.java:53) ~[telegrambots-meta-6.1.0.jar:?] 71 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseArray(PartialBotApiMethod.java:38) ~[telegrambots-meta-6.1.0.jar:?] 72 | at org.telegram.telegrambots.meta.api.methods.updates.GetUpdates.deserializeResponse(GetUpdates.java:83) ~[telegrambots-meta-6.1.0.jar:?] 73 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.getUpdatesFromServer(DefaultBotSession.java:260) ~[telegrambots-6.1.0.jar:?] 74 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.run(DefaultBotSession.java:189) ~[telegrambots-6.1.0.jar:?] 75 | [2024-06-10 20:32:50:053] Starting AlistStrmApplication using Java 1.8.0_311 on Jacskao-Win11 with PID 116800 (D:\idea project\alist-strm\target\classes started by 90773 in D:\idea project\alist-strm) 76 | [2024-06-10 20:32:50:055] No active profile set, falling back to 1 default profile: "default" 77 | [2024-06-10 20:32:50:401] Started AlistStrmApplication in 0.582 seconds (JVM running for 1.706) 78 | [2024-06-10 20:32:51:128] No post action was detected for method with name [recover] 79 | [2024-06-10 20:32:51:129] No post action was detected for method with name [stats] 80 | [2024-06-10 20:32:51:129] No post action was detected for method with name [ban] 81 | [2024-06-10 20:32:51:130] No post action was detected for method with name [unban] 82 | [2024-06-10 20:32:51:130] No post action was detected for method with name [promote] 83 | [2024-06-10 20:32:51:130] No post action was detected for method with name [claim] 84 | [2024-06-10 20:32:51:130] No post action was detected for method with name [report] 85 | [2024-06-10 20:32:51:130] No post action was detected for method with name [backup] 86 | [2024-06-10 20:32:51:131] No post action was detected for method with name [demote] 87 | [2024-06-10 20:32:51:131] No post action was detected for method with name [commands] 88 | [2024-06-10 20:32:51:131] No post action was detected for method with name [strm] 89 | [2024-06-10 20:32:51:131] No post action was detected for method with name [strmdir] 90 | [2024-06-10 20:32:52:802] 开始执行strm任务2024-06-10T20:32:52.800 91 | [2024-06-10 20:32:52:884] 开始获取/每日更新 92 | [2024-06-10 20:32:52:907] 获取完成/每日更新 93 | [2024-06-10 20:32:53:917] 开始获取/每日更新/电影 94 | [2024-06-10 20:32:53:920] 获取完成/每日更新/电影 95 | [2024-06-10 20:32:54:935] 开始获取/每日更新/电影/老狐狸 96 | [2024-06-10 20:32:54:938] 获取完成/每日更新/电影/老狐狸 97 | [2024-06-10 20:32:55:949] 开始获取/每日更新/电视剧 98 | [2024-06-10 20:32:56:161] 获取完成/每日更新/电视剧 99 | [2024-06-10 20:32:56:162] 任务执行完成2024-06-10T20:32:56.162 100 | [2024-06-10 20:33:16:490] [bot] New update [212303506] received at 2024-06-10T20:33:16.490+08:00[Asia/Shanghai] 101 | [2024-06-10 20:33:16:491] Update(updateId=212303506, message=Message(messageId=2410, from=User(id=5173469146, firstName=Jacksao, isBot=false, lastName=Ding, userName=JacksaoDing, languageCode=zh-hans, canJoinGroups=null, canReadAllGroupMessages=null, supportInlineQueries=null, isPremium=null, addedToAttachmentMenu=null), date=1718022795, chat=Chat(id=5173469146, type=private, title=null, firstName=Jacksao, lastName=Ding, userName=JacksaoDing, photo=null, description=null, inviteLink=null, pinnedMessage=null, stickerSetName=null, canSetStickerSet=null, permissions=null, slowModeDelay=null, bio=null, linkedChatId=null, location=null, messageAutoDeleteTime=null, hasPrivateForwards=null, HasProtectedContent=null, joinToSendMessages=null, joinByRequest=null), forwardFrom=null, forwardFromChat=null, forwardDate=null, text=/strm, entities=[MessageEntity(type=bot_command, offset=0, length=5, url=null, user=null, language=null, text=/strm)], captionEntities=null, audio=null, document=null, photo=null, sticker=null, video=null, contact=null, location=null, venue=null, animation=null, pinnedMessage=null, newChatMembers=[], leftChatMember=null, newChatTitle=null, newChatPhoto=null, deleteChatPhoto=null, groupchatCreated=null, replyToMessage=null, voice=null, caption=null, superGroupCreated=null, channelChatCreated=null, migrateToChatId=null, migrateFromChatId=null, editDate=null, game=null, forwardFromMessageId=null, invoice=null, successfulPayment=null, videoNote=null, authorSignature=null, forwardSignature=null, mediaGroupId=null, connectedWebsite=null, passportData=null, forwardSenderName=null, poll=null, replyMarkup=null, dice=null, viaBot=null, senderChat=null, proximityAlertTriggered=null, messageAutoDeleteTimerChanged=null, isAutomaticForward=null, hasProtectedContent=null, webAppData=null, videoChatStarted=null, videoChatEnded=null, videoChatParticipantsInvited=null, videoChatScheduled=null), inlineQuery=null, chosenInlineQuery=null, callbackQuery=null, editedMessage=null, channelPost=null, editedChannelPost=null, shippingQuery=null, preCheckoutQuery=null, poll=null, pollAnswer=null, myChatMember=null, chatMember=null, chatJoinRequest=null) 102 | [2024-06-10 20:33:17:135] 开始执行strm任务2024-06-10T20:33:17.135 103 | [2024-06-10 20:33:17:135] 开始获取/每日更新 104 | [2024-06-10 20:33:17:140] 获取完成/每日更新 105 | [2024-06-10 20:33:18:150] 开始获取/每日更新/电影 106 | [2024-06-10 20:33:18:154] 获取完成/每日更新/电影 107 | [2024-06-10 20:33:19:167] 开始获取/每日更新/电影/老狐狸 108 | [2024-06-10 20:33:19:172] 获取完成/每日更新/电影/老狐狸 109 | [2024-06-10 20:33:20:183] 开始获取/每日更新/电视剧 110 | [2024-06-10 20:33:20:251] 获取完成/每日更新/电视剧 111 | [2024-06-10 20:33:20:251] 任务执行完成2024-06-10T20:33:20.251 112 | [2024-06-10 20:33:20:258] [bot] Processing of update [212303506] ended at 2024-06-10T20:33:20.258+08:00[Asia/Shanghai] 113 | ---> Processing time: [3767 ms] <--- 114 | 115 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.7.2 9 | 10 | 11 | cn.jackding 12 | alist-strm 13 | 0.0.1-SNAPSHOT 14 | alist-strm 15 | alist-strm 16 | 17 | 1.8 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-logging 27 | 28 | 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-web 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-logging 38 | 39 | 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-log4j2 45 | 46 | 47 | 48 | 49 | com.lmax 50 | disruptor 51 | 3.4.2 52 | 53 | 54 | 55 | com.squareup.okhttp3 56 | okhttp 57 | 4.9.3 58 | 59 | 60 | 61 | com.alibaba.fastjson2 62 | fastjson2 63 | 2.0.49 64 | 65 | 66 | 67 | org.projectlombok 68 | lombok 69 | provided 70 | 71 | 72 | 73 | org.telegram 74 | telegrambots 75 | 6.1.0 76 | 77 | 78 | 79 | org.telegram 80 | telegrambots-abilities 81 | 6.1.0 82 | 83 | 84 | eclipse-collections 85 | org.eclipse.collections 86 | 87 | 88 | 89 | 90 | org.eclipse.collections 91 | eclipse-collections 92 | 11.0.0.M1 93 | 94 | 95 | 96 | 97 | 98 | 99 | org.springframework.boot 100 | spring-boot-maven-plugin 101 | 102 | 103 | application 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /src/main/java/cn/jackding/aliststrm/AlistStrmApplication.java: -------------------------------------------------------------------------------- 1 | package cn.jackding.aliststrm; 2 | 3 | import cn.jackding.aliststrm.config.Config; 4 | import cn.jackding.aliststrm.service.StrmService; 5 | import cn.jackding.aliststrm.tg.StrmBot; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.apache.logging.log4j.Level; 9 | import org.apache.logging.log4j.LogManager; 10 | import org.apache.logging.log4j.core.config.Configurator; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.boot.CommandLineRunner; 14 | import org.springframework.boot.SpringApplication; 15 | import org.springframework.boot.autoconfigure.SpringBootApplication; 16 | import org.springframework.scheduling.annotation.EnableAsync; 17 | import org.springframework.scheduling.annotation.EnableScheduling; 18 | import org.telegram.telegrambots.bots.DefaultBotOptions; 19 | import org.telegram.telegrambots.meta.TelegramBotsApi; 20 | import org.telegram.telegrambots.meta.exceptions.TelegramApiException; 21 | import org.telegram.telegrambots.updatesreceivers.DefaultBotSession; 22 | 23 | @SpringBootApplication 24 | @Slf4j 25 | @EnableAsync 26 | @EnableScheduling 27 | public class AlistStrmApplication implements CommandLineRunner { 28 | 29 | @Autowired 30 | private StrmService strmService; 31 | 32 | @Value("${runAfterStartup:1}") 33 | private String runAfterStartup; 34 | 35 | @Value("${logLevel:}") 36 | private String logLevel; 37 | 38 | @Value("${slowMode:0}") 39 | private String slowMode; 40 | 41 | public static void main(String[] args) { 42 | SpringApplication.run(AlistStrmApplication.class, args); 43 | } 44 | 45 | @Override 46 | public void run(String... args) throws Exception { 47 | if (StringUtils.isNotBlank(logLevel)) { 48 | Configurator.setAllLevels(LogManager.getRootLogger().getName(), Level.valueOf(logLevel)); 49 | } 50 | if (!"1".equals(slowMode)) { 51 | System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "49"); 52 | } 53 | if ("1".equals(runAfterStartup)) { 54 | strmService.strm(); 55 | } else { 56 | log.info("启动立即执行任务未启用,等待定时任务处理"); 57 | } 58 | if (StringUtils.isBlank(Config.tgUserId) || StringUtils.isBlank(Config.tgToken)) { 59 | return; 60 | } 61 | TelegramBotsApi telegramBotsApi; 62 | try { 63 | telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class); 64 | } catch (TelegramApiException e) { 65 | log.error("", e); 66 | return; 67 | } 68 | DefaultBotOptions botOptions = new DefaultBotOptions(); 69 | // botOptions.setProxyHost(Config.telegramBotProxyHost); 70 | // botOptions.setProxyPort(Config.telegramBotProxyPort); 71 | // botOptions.setProxyType(DefaultBotOptions.ProxyType.HTTP); 72 | //使用AbilityBot创建的事件响应机器人 73 | try { 74 | telegramBotsApi.registerBot(new StrmBot(botOptions)); 75 | } catch (TelegramApiException e) { 76 | log.error("", e); 77 | } 78 | } 79 | 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/cn/jackding/aliststrm/ScheduledTask.java: -------------------------------------------------------------------------------- 1 | package cn.jackding.aliststrm; 2 | 3 | import cn.jackding.aliststrm.alist.AlistService; 4 | import cn.jackding.aliststrm.service.CopyAlistFileService; 5 | import com.alibaba.fastjson2.JSONObject; 6 | import lombok.extern.log4j.Log4j2; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.scheduling.annotation.Scheduled; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.util.CollectionUtils; 11 | 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | /** 15 | * @Author Jack 16 | * @Date 2024/5/24 13:32 17 | * @Version 1.0.0 18 | */ 19 | @Log4j2 20 | @Service 21 | public class ScheduledTask { 22 | 23 | @Autowired 24 | private CopyAlistFileService copyAlistFileService; 25 | 26 | @Autowired 27 | private AlistService alistService; 28 | 29 | /** 30 | * 每天执行两次 31 | */ 32 | @Scheduled(cron = "${scheduledCron:0 0 6,18 * * ?}") 33 | public void syncDaily() { 34 | JSONObject jsonObject = alistService.copyUndone(); 35 | if (jsonObject == null || !(200 == jsonObject.getInteger("code"))) { 36 | log.warn("定时任务未执行,因为alist的task/copy/undone服务不可用"); 37 | return; 38 | } 39 | if (CollectionUtils.isEmpty(jsonObject.getJSONArray("data"))) { 40 | copyAlistFileService.syncFiles("", ConcurrentHashMap.newKeySet()); 41 | } else { 42 | log.warn("定时任务未执行,因为还有正在上传的文件"); 43 | } 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/cn/jackding/aliststrm/alist/AlistService.java: -------------------------------------------------------------------------------- 1 | package cn.jackding.aliststrm.alist; 2 | 3 | import cn.jackding.aliststrm.util.Utils; 4 | import com.alibaba.fastjson2.JSONObject; 5 | import lombok.extern.slf4j.Slf4j; 6 | import okhttp3.*; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.stereotype.Service; 9 | 10 | import javax.annotation.PostConstruct; 11 | import java.io.File; 12 | import java.util.List; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | /** 16 | * @Author Jack 17 | * @Date 2024/6/22 18:36 18 | * @Version 1.0.0 19 | */ 20 | @Service 21 | @Slf4j 22 | public class AlistService { 23 | 24 | @Value("${alistServerToken}") 25 | private String token; 26 | 27 | @Value("${alistServerUrl}") 28 | private String url; 29 | 30 | @Value("${maxIdleConnections:5}") 31 | private int maxIdleConnections; 32 | 33 | @Value("${refresh:1}") 34 | private String refresh; 35 | 36 | private OkHttpClient client; 37 | 38 | // 在 Bean 初始化时预创建 OkHttpClient 39 | @PostConstruct 40 | public void init() { 41 | client = new OkHttpClient.Builder() 42 | .connectTimeout(90, TimeUnit.SECONDS) 43 | .readTimeout(90, TimeUnit.SECONDS) 44 | .writeTimeout(90, TimeUnit.SECONDS) 45 | .connectionPool(new ConnectionPool(maxIdleConnections, 5, TimeUnit.SECONDS)) 46 | .build(); 47 | log.info("OkHttpClient initialized successfully."); 48 | } 49 | 50 | public JSONObject getAlist(String path) { 51 | JSONObject jsonResponse = null; 52 | 53 | // 设置请求头 54 | Headers headers = new Headers.Builder() 55 | .add("Content-Type", "application/json") 56 | .add("Accept", "application/json") 57 | .add("Authorization", token) 58 | .build(); 59 | 60 | // 构建请求体数据 61 | JSONObject requestBodyJson = new JSONObject(); 62 | requestBodyJson.put("path", path); 63 | requestBodyJson.put("password", ""); 64 | requestBodyJson.put("page", 1); 65 | requestBodyJson.put("per_page", 0); 66 | if ("1".equals(refresh)) { 67 | requestBodyJson.put("refresh", true); 68 | } else { 69 | requestBodyJson.put("refresh", false); 70 | } 71 | String requestBodyString = requestBodyJson.toJSONString(); 72 | 73 | // 构建请求 74 | Request request = new Request.Builder() 75 | .url(url + "/api/fs/list") 76 | .headers(headers) 77 | .post(RequestBody.create(MediaType.parse("application/json"), requestBodyString)) 78 | .build(); 79 | 80 | log.debug("开始获取alist目录{}", path); 81 | for (int i = 0; i < 3; i++) { 82 | // 发送请求并处理响应 83 | try (Response response = client.newCall(request).execute()) { 84 | if (response.isSuccessful()) { 85 | // 获取响应体 86 | String responseBody = response.body().string(); 87 | 88 | // 解析 JSON 响应 89 | jsonResponse = JSONObject.parseObject(responseBody); 90 | 91 | // 处理响应数据 92 | if (200 == jsonResponse.getInteger("code")) { 93 | log.debug("获取alist目录成功{}", path); 94 | return jsonResponse; 95 | } else { 96 | log.info("Response Body: " + jsonResponse.toJSONString()); 97 | log.warn("获取alist目录{}第{}次失败", path, i + 1); 98 | Utils.sleep(1); 99 | } 100 | 101 | } else { 102 | log.warn("Request failed with code: {}", response.code()); 103 | log.error("Request failed with response :{}", response); 104 | return jsonResponse; 105 | } 106 | } catch (Exception e) { 107 | log.error("获取alist目录失败{}", path); 108 | log.error("", e); 109 | } 110 | } 111 | return jsonResponse; 112 | } 113 | 114 | public JSONObject getFile(String path) { 115 | JSONObject jsonResponse; 116 | 117 | // 设置请求头 118 | Headers headers = new Headers.Builder() 119 | .add("Content-Type", "application/json") 120 | .add("Accept", "application/json") 121 | .add("Authorization", token) 122 | .build(); 123 | 124 | // 构建请求体数据 125 | JSONObject requestBodyJson = new JSONObject(); 126 | requestBodyJson.put("path", path); 127 | requestBodyJson.put("password", ""); 128 | requestBodyJson.put("page", 1); 129 | requestBodyJson.put("per_page", 0); 130 | if ("1".equals(refresh)) { 131 | requestBodyJson.put("refresh", true); 132 | } else { 133 | requestBodyJson.put("refresh", false); 134 | } 135 | String requestBodyString = requestBodyJson.toJSONString(); 136 | 137 | // 构建请求 138 | Request request = new Request.Builder() 139 | .url(url + "/api/fs/get") 140 | .headers(headers) 141 | .post(RequestBody.create(MediaType.parse("application/json"), requestBodyString)) 142 | .build(); 143 | 144 | log.debug("开始获取alist文件{}", path); 145 | 146 | // 发送请求并处理响应 147 | try (Response response = client.newCall(request).execute()) { 148 | if (response.isSuccessful()) { 149 | // 获取响应体 150 | String responseBody = response.body().string(); 151 | 152 | // 解析 JSON 响应 153 | jsonResponse = JSONObject.parseObject(responseBody); 154 | 155 | 156 | log.debug("获取alist文件成功{}", path); 157 | return jsonResponse; 158 | 159 | 160 | } else { 161 | log.warn("Request failed with code: {}", response.code()); 162 | log.error("Request failed with response :{}", response); 163 | return null; 164 | } 165 | } catch (Exception e) { 166 | log.error("获取alist文件失败{}", path); 167 | log.error("", e); 168 | } 169 | 170 | return null; 171 | } 172 | 173 | 174 | public JSONObject copyAlist(String srcDir, String dstDir, List names) { 175 | JSONObject jsonResponse = null; 176 | 177 | // 设置请求头 178 | Headers headers = new Headers.Builder() 179 | .add("Content-Type", "application/json") 180 | .add("Accept", "application/json") 181 | .add("Authorization", token) 182 | .build(); 183 | 184 | // 构建请求体数据 185 | JSONObject requestBodyJson = new JSONObject(); 186 | requestBodyJson.put("src_dir", srcDir); 187 | requestBodyJson.put("dst_dir", dstDir); 188 | requestBodyJson.put("names", names); 189 | String requestBodyString = requestBodyJson.toJSONString(); 190 | 191 | // 构建请求 192 | Request request = new Request.Builder() 193 | .url(url + "/api/fs/copy") 194 | .headers(headers) 195 | .post(RequestBody.create(MediaType.parse("application/json"), requestBodyString)) 196 | .build(); 197 | 198 | log.debug("开始复制[{}]=>[{}]", srcDir + File.separator + names.get(0), dstDir); 199 | for (int i = 0; i < 3; i++) { 200 | // 发送请求并处理响应 201 | try (Response response = client.newCall(request).execute()) { 202 | if (response.isSuccessful()) { 203 | // 获取响应体 204 | String responseBody = response.body().string(); 205 | 206 | // 解析 JSON 响应 207 | jsonResponse = JSONObject.parseObject(responseBody); 208 | 209 | // 处理响应数据 210 | if (200 == jsonResponse.getInteger("code")) { 211 | log.debug("复制[{}]=>[{}]成功", srcDir + File.separator + names.get(0), dstDir); 212 | return jsonResponse; 213 | } else { 214 | log.warn("Response Body: " + jsonResponse.toJSONString()); 215 | log.error("复制[{}]=>[{}]第{}次失败", srcDir + File.separator + names.get(0), dstDir, i + 1); 216 | Utils.sleep(1); 217 | } 218 | 219 | } else { 220 | log.warn("Request failed with code: {}", response.code()); 221 | log.error("Request failed with response :{}", response); 222 | return jsonResponse; 223 | } 224 | } catch (Exception e) { 225 | log.error("复制[{}]=>[{}]失败", srcDir + File.separator + names.get(0), dstDir); 226 | log.error("", e); 227 | } 228 | } 229 | return jsonResponse; 230 | } 231 | 232 | 233 | public JSONObject mkdir(String path) { 234 | JSONObject jsonResponse; 235 | 236 | // 设置请求头 237 | Headers headers = new Headers.Builder() 238 | .add("Content-Type", "application/json") 239 | .add("Accept", "application/json") 240 | .add("Authorization", token) 241 | .build(); 242 | 243 | // 构建请求体数据 244 | JSONObject requestBodyJson = new JSONObject(); 245 | requestBodyJson.put("path", path); 246 | String requestBodyString = requestBodyJson.toJSONString(); 247 | 248 | // 构建请求 249 | Request request = new Request.Builder() 250 | .url(url + "/api/fs/mkdir") 251 | .headers(headers) 252 | .post(RequestBody.create(MediaType.parse("application/json"), requestBodyString)) 253 | .build(); 254 | 255 | log.debug("开始创建alist目录{}", path); 256 | 257 | // 发送请求并处理响应 258 | try (Response response = client.newCall(request).execute()) { 259 | if (response.isSuccessful()) { 260 | // 获取响应体 261 | String responseBody = response.body().string(); 262 | 263 | // 解析 JSON 响应 264 | jsonResponse = JSONObject.parseObject(responseBody); 265 | 266 | 267 | log.debug("创建alist目录完成{}", path); 268 | return jsonResponse; 269 | 270 | 271 | } else { 272 | log.warn("Request failed with code: {}", response.code()); 273 | log.error("Request failed with response :{}", response); 274 | return null; 275 | } 276 | } catch (Exception e) { 277 | log.warn("创建alist目录失败{}", path); 278 | log.error("", e); 279 | } 280 | 281 | return null; 282 | } 283 | 284 | /** 285 | * 获取未完成的复制任务 286 | * 287 | * @return 288 | */ 289 | public JSONObject copyUndone() { 290 | JSONObject jsonResponse; 291 | 292 | // 设置请求头 293 | Headers headers = new Headers.Builder() 294 | .add("Content-Type", "application/json") 295 | .add("Accept", "application/json") 296 | .add("Authorization", token) 297 | .build(); 298 | 299 | // 构建请求 300 | Request request = new Request.Builder() 301 | .url(url + "/api/task/copy/undone") 302 | .headers(headers) 303 | .get() 304 | .build(); 305 | 306 | // 发送请求并处理响应 307 | try (Response response = client.newCall(request).execute()) { 308 | if (response.isSuccessful()) { 309 | // 获取响应体 310 | String responseBody = response.body().string(); 311 | 312 | // 解析 JSON 响应 313 | jsonResponse = JSONObject.parseObject(responseBody); 314 | return jsonResponse; 315 | } else { 316 | log.warn("Request failed with code: {}", response.code()); 317 | log.error("Request failed with response :{}", response); 318 | return null; 319 | } 320 | } catch (Exception e) { 321 | log.error("", e); 322 | } 323 | return null; 324 | } 325 | 326 | /** 327 | * 获取复制任务的信息 328 | * 329 | * @return 330 | */ 331 | public JSONObject copyInfo(String tid) { 332 | JSONObject jsonResponse; 333 | 334 | // 设置请求头 335 | Headers headers = new Headers.Builder() 336 | .add("Accept", "application/json") 337 | .add("Authorization", token) 338 | .build(); 339 | 340 | // 创建请求体,传递参数 341 | RequestBody formBody = new FormBody.Builder() 342 | .add("tid", tid) 343 | .build(); 344 | 345 | // 构建请求 346 | Request request = new Request.Builder() 347 | .url(url + "/api/task/copy/info" + "?tid=" + tid) 348 | .headers(headers) 349 | .post(formBody) 350 | .build(); 351 | 352 | // 发送请求并处理响应 353 | try (Response response = client.newCall(request).execute()) { 354 | if (response.isSuccessful()) { 355 | // 获取响应体 356 | String responseBody = response.body().string(); 357 | 358 | // 解析 JSON 响应 359 | jsonResponse = JSONObject.parseObject(responseBody); 360 | return jsonResponse; 361 | } else { 362 | log.warn("Request failed with code: {}", response.code()); 363 | log.error("Request failed with response :{}", response); 364 | return null; 365 | } 366 | } catch (Exception e) { 367 | log.error("", e); 368 | } 369 | return null; 370 | } 371 | 372 | /** 373 | * 获取复制任务的信息 374 | * 375 | * @return 376 | */ 377 | public JSONObject copyRetry(String tid) { 378 | JSONObject jsonResponse; 379 | 380 | // 设置请求头 381 | Headers headers = new Headers.Builder() 382 | .add("Accept", "application/json") 383 | .add("Authorization", token) 384 | .build(); 385 | 386 | // 创建请求体,传递参数 387 | RequestBody formBody = new FormBody.Builder() 388 | .add("tid", tid) 389 | .build(); 390 | 391 | // 构建请求 392 | Request request = new Request.Builder() 393 | .url(url + "/api/task/copy/retry" + "?tid=" + tid) 394 | .headers(headers) 395 | .post(formBody) 396 | .build(); 397 | 398 | // 发送请求并处理响应 399 | try (Response response = client.newCall(request).execute()) { 400 | if (response.isSuccessful()) { 401 | // 获取响应体 402 | String responseBody = response.body().string(); 403 | 404 | // 解析 JSON 响应 405 | jsonResponse = JSONObject.parseObject(responseBody); 406 | return jsonResponse; 407 | } else { 408 | log.warn("Request failed with code: {}", response.code()); 409 | log.error("Request failed with response :{}", response); 410 | return null; 411 | } 412 | } catch (Exception e) { 413 | log.error("", e); 414 | } 415 | return null; 416 | } 417 | 418 | } 419 | -------------------------------------------------------------------------------- /src/main/java/cn/jackding/aliststrm/config/Config.java: -------------------------------------------------------------------------------- 1 | package cn.jackding.aliststrm.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * @Author Jack 8 | * @Date 2022/8/2 21:04 9 | * @Version 1.0.0 10 | */ 11 | @Component 12 | public class Config { 13 | 14 | public static String tgToken; 15 | 16 | public static String tgUserId; 17 | 18 | @Value("${tgToken:}") 19 | public void setTgToken(String tgToken) { 20 | Config.tgToken = tgToken; 21 | } 22 | 23 | @Value("${tgUserId:}") 24 | public void setTgUserId(String tgUserId) { 25 | Config.tgUserId = tgUserId; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/cn/jackding/aliststrm/controller/NotifyController.java: -------------------------------------------------------------------------------- 1 | package cn.jackding.aliststrm.controller; 2 | 3 | import cn.jackding.aliststrm.service.CopyAlistFileService; 4 | import cn.jackding.aliststrm.util.Utils; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.util.StringUtils; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestBody; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import java.util.Map; 15 | import java.util.concurrent.ConcurrentHashMap; 16 | 17 | /** 18 | * @Author Jack 19 | * @Date 2024/6/23 20:34 20 | * @Version 1.0.0 21 | */ 22 | @RestController 23 | @RequestMapping("api/v1") 24 | @Slf4j 25 | public class NotifyController { 26 | 27 | @Value("${replaceDir:}") 28 | private String replaceDir; 29 | 30 | @Autowired 31 | private CopyAlistFileService copyAlistFileService; 32 | 33 | @PostMapping("/notify") 34 | public void notifySync() { 35 | copyAlistFileService.syncFiles("", ConcurrentHashMap.newKeySet()); 36 | } 37 | 38 | @PostMapping("/notifyByDir") 39 | public void notifyByDir(@RequestBody Map map) { 40 | log.info("map: " + map); 41 | String relativePath = ""; 42 | if (StringUtils.hasText(replaceDir) && StringUtils.hasText((CharSequence) map.get("dir"))) { 43 | relativePath = map.get("dir").toString().replaceFirst(replaceDir, ""); 44 | if (Utils.isVideo(relativePath)) { 45 | copyAlistFileService.syncOneFile(relativePath); 46 | } else { 47 | copyAlistFileService.syncFiles(relativePath, ConcurrentHashMap.newKeySet()); 48 | } 49 | } else { 50 | copyAlistFileService.syncFiles(relativePath, ConcurrentHashMap.newKeySet()); 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/cn/jackding/aliststrm/service/AsynService.java: -------------------------------------------------------------------------------- 1 | package cn.jackding.aliststrm.service; 2 | 3 | import cn.jackding.aliststrm.alist.AlistService; 4 | import cn.jackding.aliststrm.util.Utils; 5 | import com.alibaba.fastjson2.JSONObject; 6 | import org.apache.commons.lang3.StringUtils; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.scheduling.annotation.Async; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.Set; 12 | import java.util.concurrent.atomic.AtomicBoolean; 13 | 14 | /** 15 | * 异步线程服务 16 | * 17 | * @Author Jack 18 | * @Date 2024/6/23 12:42 19 | * @Version 1.0.0 20 | */ 21 | @Service 22 | public class AsynService { 23 | 24 | @Autowired 25 | private AlistService alistService; 26 | 27 | @Autowired 28 | private StrmService strmService; 29 | 30 | private final AtomicBoolean isRun = new AtomicBoolean(false); 31 | 32 | /** 33 | * 判断alist的复制任务是否完成 完成就执行strm任务 34 | * 35 | * @return 36 | * @Async 37 | */ 38 | @Async 39 | public void isCopyDone(String dstDir, String strmDir, Set taskIdList) { 40 | if (isRun.get() && StringUtils.isBlank(strmDir)) { 41 | return; 42 | } 43 | isRun.set(true); 44 | Utils.sleep(30); 45 | while (true) { 46 | boolean allTasksCompleted = true; 47 | for (String taskId : taskIdList) { 48 | JSONObject jsonResponse = alistService.copyInfo(taskId); 49 | if (jsonResponse == null) { 50 | continue; 51 | } 52 | 53 | // 检查任务状态 54 | Integer code = jsonResponse.getInteger("code"); 55 | Integer state = -1; 56 | if (jsonResponse.getJSONObject("data") != null) { 57 | state = jsonResponse.getJSONObject("data").getInteger("state"); 58 | } 59 | 60 | //不是上传成功状态 61 | if (200 == code && state != 2) { 62 | //失败状态了 就重试 状态1是运行中 状态8是等待重试 63 | if (state == 7) { 64 | alistService.copyRetry(taskId); 65 | } 66 | allTasksCompleted = false; 67 | } else if (404 == code || state == 2) { 68 | taskIdList.remove(taskId); 69 | } 70 | } 71 | if (allTasksCompleted) { 72 | isRun.set(false); 73 | strmService.strmDir(dstDir + strmDir);// 生成 STRM 文件 74 | break;// 任务完成,退出循环 75 | } else { 76 | Utils.sleep(30);//继续检查 77 | } 78 | } 79 | } 80 | 81 | @Async 82 | public void isCopyDoneOneFile(String path, String taskId) { 83 | Utils.sleep(30); 84 | while (true) { 85 | JSONObject jsonResponse = alistService.copyInfo(taskId); 86 | if (jsonResponse == null) { 87 | break; 88 | } 89 | // 检查任务状态 90 | Integer code = jsonResponse.getInteger("code"); 91 | Integer state = -1; 92 | if (jsonResponse.getJSONObject("data") != null) { 93 | state = jsonResponse.getJSONObject("data").getInteger("state"); 94 | } 95 | //判定任务是否完成了 完成了就生成strm文件 96 | if (404 == code || state == 2) { 97 | strmService.strmOneFile(path);// 生成 STRM 文件 98 | break;// 任务完成,退出循环 99 | } else if (state == 7) { 100 | //失败就重试 101 | alistService.copyRetry(taskId); 102 | } 103 | Utils.sleep(30);//继续检查 104 | } 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/cn/jackding/aliststrm/service/CopyAlistFileService.java: -------------------------------------------------------------------------------- 1 | package cn.jackding.aliststrm.service; 2 | 3 | import cn.jackding.aliststrm.alist.AlistService; 4 | import cn.jackding.aliststrm.util.Utils; 5 | import com.alibaba.fastjson2.JSONArray; 6 | import com.alibaba.fastjson2.JSONObject; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.Collections; 14 | import java.util.Set; 15 | import java.util.concurrent.ConcurrentHashMap; 16 | import java.util.concurrent.atomic.AtomicBoolean; 17 | import java.util.stream.Stream; 18 | 19 | /** 20 | * 复制alist文件 21 | * 22 | * @Author Jack 23 | * @Date 2024/6/22 17:53 24 | * @Version 1.0.0 25 | */ 26 | @Service 27 | @Slf4j 28 | public class CopyAlistFileService { 29 | 30 | @Value("${srcDir:}") 31 | private String srcDir; 32 | 33 | @Value("${dstDir:}") 34 | private String dstDir; 35 | 36 | @Autowired 37 | private AlistService alistService; 38 | 39 | @Autowired 40 | private AsynService asynService; 41 | 42 | @Value("${minFileSize:100}") 43 | private String minFileSize; 44 | 45 | @Value("${slowMode:0}") 46 | private String slowMode; 47 | 48 | @Value("${strmAfterSync:1}") 49 | private String strmAfterSync; 50 | 51 | private final Set cache = ConcurrentHashMap.newKeySet(); 52 | 53 | public void syncFiles(String srcDir, String dstDir, String relativePath, String strmDir, Set taskIdList) { 54 | if (StringUtils.isAnyBlank(srcDir, dstDir)) { 55 | return; 56 | } 57 | AtomicBoolean flag = new AtomicBoolean(false); 58 | //查出所有源目录 59 | JSONObject object = alistService.getAlist(srcDir + relativePath); 60 | if (object.getJSONObject("data") == null) { 61 | return; 62 | } 63 | JSONArray jsonArray = object.getJSONObject("data").getJSONArray("content"); 64 | if (jsonArray == null) { 65 | return; 66 | } 67 | //判断是不是用多线程流 68 | Stream stream; 69 | if ("1".equals(slowMode)) { 70 | stream = jsonArray.stream().sequential(); 71 | } else { 72 | stream = jsonArray.stream().parallel(); 73 | } 74 | stream.forEach(content -> { 75 | JSONObject contentJson = (JSONObject) content; 76 | String name = contentJson.getString("name"); 77 | 78 | //不是视频文件就不用继续往下走上传了 79 | if (!contentJson.getBoolean("is_dir") && !Utils.isVideo(name)) { 80 | return; 81 | } 82 | 83 | JSONObject jsonObject = alistService.getFile(dstDir + "/" + relativePath + "/" + name); 84 | //是目录 85 | if (contentJson.getBoolean("is_dir")) { 86 | //判断目标目录是否存在这个文件夹 87 | //200就是存在 存在就继续往下级目录找 88 | if (200 == jsonObject.getInteger("code")) { 89 | syncFiles(srcDir, dstDir, relativePath + "/" + name, taskIdList); 90 | } else { 91 | alistService.mkdir(dstDir + "/" + relativePath + "/" + name); 92 | syncFiles(srcDir, dstDir, relativePath + "/" + name, taskIdList); 93 | } 94 | } else { 95 | if (cache.contains(dstDir + "/" + relativePath + "/" + name)) { 96 | log.info("文件已处理过,跳过处理" + dstDir + "/" + relativePath + "/" + name); 97 | return; 98 | } 99 | //是视频文件才复制 并且不存在 100 | if (!(200 == jsonObject.getInteger("code")) && Utils.isVideo(name)) { 101 | if (contentJson.getLong("size") > Long.parseLong(minFileSize) * 1024 * 1024) { 102 | JSONObject jsonResponse = alistService.copyAlist(srcDir + "/" + relativePath, dstDir + "/" + relativePath, Collections.singletonList(name)); 103 | if (jsonResponse != null && 200 == jsonResponse.getInteger("code")) { 104 | cache.add(dstDir + "/" + relativePath + "/" + name); 105 | flag.set(true); 106 | //获取上传文件的任务id 107 | JSONArray tasks = jsonResponse.getJSONObject("data").getJSONArray("tasks"); 108 | taskIdList.add(tasks.getJSONObject(0).getString("id")); 109 | } 110 | } 111 | } 112 | } 113 | }); 114 | 115 | if (flag.get() && "1".equals(strmAfterSync)) { 116 | asynService.isCopyDone(dstDir, strmDir, taskIdList); 117 | } 118 | 119 | 120 | } 121 | 122 | public void syncOneFile(String srcDir, String dstDir, String relativePath) { 123 | if (cache.contains(dstDir + "/" + relativePath)) { 124 | log.info("文件已处理过,跳过处理" + dstDir + "/" + relativePath); 125 | return; 126 | } 127 | AtomicBoolean flag = new AtomicBoolean(false); 128 | String taskId = null; 129 | JSONObject jsonObject = alistService.getFile(dstDir + "/" + relativePath); 130 | if (!(200 == jsonObject.getInteger("code")) && Utils.isVideo(relativePath)) { 131 | JSONObject srcJson = alistService.getFile(srcDir + "/" + relativePath); 132 | if (srcJson.getJSONObject("data").getLong("size") > Long.parseLong(minFileSize) * 1024 * 1024) { 133 | alistService.mkdir(dstDir + "/" + relativePath.substring(0, relativePath.lastIndexOf("/"))); 134 | JSONObject jsonResponse = alistService.copyAlist(srcDir + "/" + relativePath.substring(0, relativePath.lastIndexOf("/")), dstDir + "/" + relativePath.substring(0, relativePath.lastIndexOf("/")), Collections.singletonList(relativePath.substring(relativePath.lastIndexOf("/")))); 135 | if (jsonResponse != null && 200 == jsonResponse.getInteger("code")) { 136 | cache.add(dstDir + "/" + relativePath); 137 | flag.set(true); 138 | //获取上传文件的任务id 139 | JSONArray tasks = jsonResponse.getJSONObject("data").getJSONArray("tasks"); 140 | taskId = tasks.getJSONObject(0).getString("id"); 141 | } 142 | } 143 | } 144 | 145 | if (flag.get() && "1".equals(strmAfterSync)) { 146 | asynService.isCopyDoneOneFile(dstDir + relativePath, taskId); 147 | } 148 | 149 | } 150 | 151 | public void syncOneFile(String relativePath) { 152 | syncOneFile(srcDir, dstDir, relativePath); 153 | } 154 | 155 | public void syncFiles(String srcDir, String dstDir, String relativePath, Set taskIdList) { 156 | syncFiles(srcDir, dstDir, relativePath, "", taskIdList); 157 | } 158 | 159 | public void syncFiles(String relativePath, Set taskIdList) { 160 | syncFiles(srcDir, dstDir, relativePath, relativePath, taskIdList); 161 | } 162 | 163 | 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/cn/jackding/aliststrm/service/StrmService.java: -------------------------------------------------------------------------------- 1 | package cn.jackding.aliststrm.service; 2 | 3 | import cn.jackding.aliststrm.alist.AlistService; 4 | import cn.jackding.aliststrm.util.Utils; 5 | import com.alibaba.fastjson2.JSONArray; 6 | import com.alibaba.fastjson2.JSONObject; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.io.*; 14 | import java.net.URL; 15 | import java.net.URLConnection; 16 | import java.net.URLEncoder; 17 | import java.time.LocalDateTime; 18 | import java.util.Set; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | import java.util.stream.Stream; 21 | 22 | /** 23 | * @Author Jack 24 | * @Date 2024/6/10 20:13 25 | * @Version 1.0.0 26 | */ 27 | @Service 28 | @Slf4j 29 | public class StrmService { 30 | 31 | 32 | @Value("${alistScanPath}") 33 | private String path; 34 | 35 | @Value("${alistServerUrl}") 36 | private String url; 37 | 38 | @Value("${slowMode:0}") 39 | private String slowMode; 40 | 41 | @Value("${output.dir}") 42 | private String outputDir; 43 | 44 | @Value("${encode:1}") 45 | private String encode; 46 | 47 | @Value("${isDownSub:0}") 48 | private String isDownSub; 49 | 50 | @Autowired 51 | private AlistService alistService; 52 | 53 | private final Set cache = ConcurrentHashMap.newKeySet(); 54 | 55 | public void strm() { 56 | strmDir(path); 57 | } 58 | 59 | public void strmDir(String path) { 60 | log.info("开始执行指定路径strm任务{}", LocalDateTime.now()); 61 | Utils.sendTgMsg("开始执行strm任务"); 62 | try { 63 | getData(path, outputDir + File.separator + path.replace("/", File.separator)); 64 | } catch (Exception e) { 65 | Utils.sendTgMsg("strm任务执行出错"); 66 | log.error("", e); 67 | } finally { 68 | log.info("strm任务执行完成{}", LocalDateTime.now()); 69 | Utils.sendTgMsg("strm任务执行完成"); 70 | } 71 | } 72 | 73 | public void strmOneFile(String path) { 74 | //判断是否处理过 75 | if (cache.contains(path)) { 76 | log.info("文件已处理过,跳过处理{}", path); 77 | return; 78 | } 79 | String fileName = path.substring(path.lastIndexOf("/"), path.lastIndexOf(".")).replaceAll("[\\\\/:*?\"<>|]", ""); 80 | File file = new File(outputDir + File.separator + path.substring(0, path.lastIndexOf("/")).replace("/", File.separator)); 81 | if (!file.exists()) { 82 | file.mkdirs(); 83 | } 84 | try (FileWriter writer = new FileWriter(outputDir + File.separator + path.substring(0, path.lastIndexOf("/")).replace("/", File.separator) + File.separator + (fileName.length() > 255 ? fileName.substring(0, 250) : fileName) + ".strm")) { 85 | String encodePath = path; 86 | if ("1".equals(encode)) { 87 | encodePath = URLEncoder.encode(path, "UTF-8").replace("+", "%20").replace("%2F", "/"); 88 | } 89 | writer.write(url + "/d" + encodePath); 90 | cache.add(path); 91 | } catch (Exception e) { 92 | log.error("", e); 93 | } 94 | } 95 | 96 | public void getData(String path, String localPath) { 97 | 98 | File outputDirFile = new File(localPath); 99 | outputDirFile.mkdirs(); 100 | 101 | JSONObject jsonObject = alistService.getAlist(path); 102 | if (jsonObject != null && 200 == jsonObject.getInteger("code")) { 103 | JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("content"); 104 | if (jsonArray == null) { 105 | return; 106 | } 107 | //判断是不是用多线程流 108 | Stream stream; 109 | if ("1".equals(slowMode)) { 110 | stream = jsonArray.stream().sequential(); 111 | } else { 112 | stream = jsonArray.stream().parallel(); 113 | } 114 | 115 | stream.forEach(obj -> { 116 | JSONObject object = (JSONObject) obj; 117 | String name = object.getString("name"); 118 | if (object.getBoolean("is_dir")) { 119 | String newLocalPath = localPath + File.separator + (name.length() > 255 ? name.substring(0, 250) : name); 120 | File file = new File(newLocalPath); 121 | if (!file.exists()) { 122 | file.mkdirs(); 123 | } 124 | getData(path + "/" + name, newLocalPath); 125 | } else { 126 | //判断是否处理过 127 | if (cache.contains(path + "/" + name)) { 128 | log.info("文件已处理过,跳过处理" + path + "/" + name); 129 | return; 130 | } 131 | //视频文件 132 | if (Utils.isVideo(name)) { 133 | String fileName = name.substring(0, name.lastIndexOf(".")).replaceAll("[\\\\/:*?\"<>|]", ""); 134 | try (FileWriter writer = new FileWriter(localPath + File.separator + (fileName.length() > 255 ? fileName.substring(0, 250) : fileName) + ".strm")) { 135 | String encodePath = path + "/" + name; 136 | if ("1".equals(encode)) { 137 | encodePath = URLEncoder.encode(path + "/" + name, "UTF-8").replace("+", "%20").replace("%2F", "/"); 138 | } 139 | writer.write(url + "/d" + encodePath); 140 | cache.add(path + "/" + name); 141 | } catch (Exception e) { 142 | log.error("", e); 143 | } 144 | } 145 | 146 | //字幕文件 147 | if ("1".equals(isDownSub) && Utils.isSrt(name)) { 148 | String url = alistService.getFile(path + "/" + name).getJSONObject("data").getString("raw_url"); 149 | String fileName = name.replaceAll("[\\\\/:*?\"<>|]", ""); 150 | downloadFile(url, localPath + File.separator + (fileName.length() > 255 ? fileName.substring(0, 250) : fileName) + name.substring(name.lastIndexOf("."))); 151 | cache.add(path + "/" + name); 152 | } 153 | } 154 | }); 155 | 156 | } 157 | 158 | } 159 | 160 | public static void downloadFile(String fileURL, String saveDir) { 161 | if (StringUtils.isBlank(fileURL)) { 162 | return; 163 | } 164 | // 创建URL对象 165 | URL url = null; 166 | try { 167 | url = new URL(fileURL); 168 | } catch (Exception e) { 169 | log.error("文件{}下载失败1", fileURL); 170 | throw new RuntimeException(e); 171 | } 172 | // 打开连接 173 | URLConnection connection = null; 174 | try { 175 | connection = url.openConnection(); 176 | } catch (IOException e) { 177 | log.error("文件{}下载失败2", fileURL); 178 | throw new RuntimeException(e); 179 | } 180 | try (InputStream inputStream = new BufferedInputStream(connection.getInputStream()); FileOutputStream outputStream = new FileOutputStream(saveDir)) { 181 | 182 | byte[] buffer = new byte[1024]; 183 | int bytesRead = -1; 184 | 185 | // 读取文件并写入到本地 186 | while ((bytesRead = inputStream.read(buffer)) != -1) { 187 | outputStream.write(buffer, 0, bytesRead); 188 | } 189 | 190 | } catch (IOException ex) { 191 | log.error("文件{}下载失败3", fileURL); 192 | log.error("", ex); 193 | } 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /src/main/java/cn/jackding/aliststrm/tg/ResponseHandler.java: -------------------------------------------------------------------------------- 1 | package cn.jackding.aliststrm.tg; 2 | 3 | import cn.jackding.aliststrm.service.CopyAlistFileService; 4 | import cn.jackding.aliststrm.service.StrmService; 5 | import cn.jackding.aliststrm.util.SpringContextUtil; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.telegram.abilitybots.api.db.DBContext; 8 | import org.telegram.abilitybots.api.sender.MessageSender; 9 | import org.telegram.telegrambots.meta.api.methods.send.SendMessage; 10 | 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | @Slf4j 14 | public class ResponseHandler { 15 | 16 | private final MessageSender sender; 17 | 18 | public ResponseHandler(MessageSender sender, DBContext db) { 19 | this.sender = sender; 20 | } 21 | 22 | /** 23 | * 根据用户的输入同步 24 | * 25 | * @param chatId 26 | * @param parameter 27 | * @param messageId 28 | */ 29 | public void replyToStrmDir(long chatId, String parameter, Integer messageId) { 30 | try { 31 | sender.execute(SendMessage.builder().chatId(chatId).replyToMessageId(messageId).text("==开始执行指定路径strm任务==").build()); 32 | StrmService strmService = (StrmService) SpringContextUtil.getBean("strmService"); 33 | strmService.strmDir(parameter); 34 | sender.execute(SendMessage.builder().chatId(chatId).replyToMessageId(messageId).text("==执行指定路径strm任务完成==").build()); 35 | } catch (Exception e) { 36 | log.error("", e); 37 | } 38 | } 39 | 40 | public void replyToSyncDir(long chatId, String parameter, Integer messageId) { 41 | try { 42 | if (!parameter.contains("#")) { 43 | sender.execute(SendMessage.builder().chatId(chatId).replyToMessageId(messageId).text("请输入正确的参数,例如:/阿里云盘/电影#/115网盘/电影").build()); 44 | } 45 | String[] strings = parameter.split("#"); 46 | if (strings.length != 2) { 47 | sender.execute(SendMessage.builder().chatId(chatId).replyToMessageId(messageId).text("请输入正确的参数,例如:/阿里云盘/电影#/115网盘/电影").build()); 48 | } 49 | sender.execute(SendMessage.builder().chatId(chatId).replyToMessageId(messageId).text("==开始执行指定路径sync任务==").build()); 50 | CopyAlistFileService copyAlistFileService = (CopyAlistFileService) SpringContextUtil.getBean("copyAlistFileService"); 51 | copyAlistFileService.syncFiles(strings[0], strings[1], "", ConcurrentHashMap.newKeySet()); 52 | sender.execute(SendMessage.builder().chatId(chatId).replyToMessageId(messageId).text("==执行指定路径sync任务完成==").build()); 53 | } catch (Exception e) { 54 | log.error("", e); 55 | } 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/cn/jackding/aliststrm/tg/StrmBot.java: -------------------------------------------------------------------------------- 1 | package cn.jackding.aliststrm.tg; 2 | 3 | import cn.jackding.aliststrm.config.Config; 4 | import cn.jackding.aliststrm.service.CopyAlistFileService; 5 | import cn.jackding.aliststrm.service.StrmService; 6 | import cn.jackding.aliststrm.util.SpringContextUtil; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.telegram.abilitybots.api.bot.AbilityBot; 10 | import org.telegram.abilitybots.api.objects.Ability; 11 | import org.telegram.abilitybots.api.objects.Flag; 12 | import org.telegram.telegrambots.bots.DefaultBotOptions; 13 | 14 | import java.util.concurrent.ConcurrentHashMap; 15 | 16 | import static org.telegram.abilitybots.api.objects.Locality.USER; 17 | import static org.telegram.abilitybots.api.objects.Privacy.CREATOR; 18 | import static org.telegram.abilitybots.api.util.AbilityUtils.getChatId; 19 | 20 | /** 21 | * @Author Jack 22 | * @Date 2024/6/4 21:33 23 | * @Version 1.0.0 24 | */ 25 | @Slf4j 26 | public class StrmBot extends AbilityBot { 27 | 28 | private final ResponseHandler responseHandler = new ResponseHandler(sender, db); 29 | 30 | public StrmBot() { 31 | super(Config.tgToken, ""); 32 | } 33 | 34 | public StrmBot(DefaultBotOptions options) { 35 | super(Config.tgToken, "bot", options); 36 | } 37 | 38 | @Override 39 | public long creatorId() { 40 | return Long.parseLong(Config.tgUserId); 41 | } 42 | 43 | public Ability strm() { 44 | return Ability.builder() 45 | .name("strm") 46 | .info("生成strm") 47 | .privacy(CREATOR) 48 | .locality(USER) 49 | .input(0) 50 | .action(ctx -> { 51 | silent.send("==开始执行strm任务==", ctx.chatId()); 52 | StrmService strmService = (StrmService) SpringContextUtil.getBean("strmService"); 53 | strmService.strm(); 54 | silent.send("==执行strm任务完成==", ctx.chatId()); 55 | }) 56 | .build(); 57 | } 58 | 59 | public Ability strmDir() { 60 | return Ability.builder() 61 | .name("strmdir") 62 | .info("生成指定路径strm") 63 | .privacy(CREATOR) 64 | .locality(USER) 65 | .input(0) 66 | .action(ctx -> { 67 | String parameter; 68 | try { 69 | parameter = ctx.firstArg(); 70 | } catch (Exception e) { 71 | silent.forceReply("请输入路径", ctx.chatId()); 72 | return; 73 | } 74 | if (StringUtils.isBlank(parameter)) { 75 | silent.forceReply("请输入路径", ctx.chatId()); 76 | return; 77 | } 78 | silent.send("==开始执行指定路径strm任务==", ctx.chatId()); 79 | StrmService strmService = (StrmService) SpringContextUtil.getBean("strmService"); 80 | strmService.strmDir(parameter); 81 | silent.send("==执行指定路径strm任务完成==", ctx.chatId()); 82 | }) 83 | .reply((bot, upd) -> responseHandler.replyToStrmDir(getChatId(upd), upd.getMessage().getText(), upd.getMessage().getMessageId()), Flag.REPLY,//回复 84 | upd -> upd.getMessage().getReplyToMessage().hasText(), upd -> upd.getMessage().getReplyToMessage().getText().equals("请输入路径")//回复的是上面的问题 85 | ) 86 | .build(); 87 | } 88 | 89 | public Ability sync() { 90 | return Ability.builder() 91 | .name("sync") 92 | .info("同步alist") 93 | .privacy(CREATOR) 94 | .locality(USER) 95 | .input(0) 96 | .action(ctx -> { 97 | silent.send("==开始执行同步alist任务==", ctx.chatId()); 98 | CopyAlistFileService copyAlistFileService = (CopyAlistFileService) SpringContextUtil.getBean("copyAlistFileService"); 99 | copyAlistFileService.syncFiles("", ConcurrentHashMap.newKeySet()); 100 | silent.send("==执行同步alist任务完成==", ctx.chatId()); 101 | }) 102 | .build(); 103 | } 104 | 105 | public Ability syncDir() { 106 | return Ability.builder() 107 | .name("syncdir") 108 | .info("同步alist指定目录") 109 | .privacy(CREATOR) 110 | .locality(USER) 111 | .input(0) 112 | .action(ctx -> { 113 | String parameter; 114 | try { 115 | parameter = ctx.firstArg(); 116 | } catch (Exception e) { 117 | silent.forceReply("请输入路径(格式:源路径#目标路径)", ctx.chatId()); 118 | return; 119 | } 120 | if (StringUtils.isBlank(parameter)) { 121 | silent.forceReply("请输入路径(格式:源路径#目标路径)", ctx.chatId()); 122 | return; 123 | } 124 | if (!parameter.contains("#")) { 125 | silent.send("请输入正确的参数,例如:/阿里云盘/电影#/115网盘/电影", ctx.chatId()); 126 | } 127 | String[] strings = parameter.split("#"); 128 | if (strings.length != 2) { 129 | silent.send("请输入正确的参数,例如:/阿里云盘/电影#/115网盘/电影", ctx.chatId()); 130 | } 131 | silent.send("==开始执行同步alist指定目录任务==", ctx.chatId()); 132 | CopyAlistFileService copyAlistFileService = (CopyAlistFileService) SpringContextUtil.getBean("copyAlistFileService"); 133 | copyAlistFileService.syncFiles(strings[0], strings[1], "", ConcurrentHashMap.newKeySet()); 134 | silent.send("==执行同步alist指定目录任务完成==", ctx.chatId()); 135 | }) 136 | .reply((bot, upd) -> responseHandler.replyToSyncDir(getChatId(upd), upd.getMessage().getText(), upd.getMessage().getMessageId()), Flag.REPLY,//回复 137 | upd -> upd.getMessage().getReplyToMessage().hasText(), upd -> upd.getMessage().getReplyToMessage().getText().equals("请输入路径(格式:源路径#目标路径)")//回复的是上面的问题 138 | ) 139 | .build(); 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/cn/jackding/aliststrm/tg/TgSendMsg.java: -------------------------------------------------------------------------------- 1 | package cn.jackding.aliststrm.tg; 2 | 3 | import cn.jackding.aliststrm.config.Config; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.telegram.telegrambots.bots.TelegramLongPollingBot; 7 | import org.telegram.telegrambots.meta.api.methods.send.SendMessage; 8 | import org.telegram.telegrambots.meta.api.objects.Update; 9 | import org.telegram.telegrambots.meta.exceptions.TelegramApiException; 10 | 11 | /** 12 | * @Author Jack 13 | * @Date 2024/6/4 17:17 14 | * @Version 1.0.0 15 | */ 16 | @Slf4j 17 | public class TgSendMsg extends TelegramLongPollingBot { 18 | 19 | public void sendMsg(String msg) { 20 | if (StringUtils.isBlank(Config.tgUserId) || StringUtils.isBlank(Config.tgToken)) { 21 | return; 22 | } 23 | SendMessage message = new SendMessage(); 24 | message.setChatId(Config.tgUserId); 25 | message.setText(msg); 26 | 27 | try { 28 | execute(message); 29 | } catch (TelegramApiException e) { 30 | log.error("", e); 31 | } 32 | } 33 | 34 | @Override 35 | public String getBotUsername() { 36 | return ""; 37 | } 38 | 39 | @Override 40 | public String getBotToken() { 41 | return Config.tgToken; 42 | } 43 | 44 | @Override 45 | public void onUpdateReceived(Update update) { 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/cn/jackding/aliststrm/util/SpringContextUtil.java: -------------------------------------------------------------------------------- 1 | package cn.jackding.aliststrm.util; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * @Author Jack 10 | * @Date 2024/6/4 23:24 11 | * @Version 1.0.0 12 | */ 13 | @Component 14 | public class SpringContextUtil implements ApplicationContextAware { 15 | 16 | private static ApplicationContext context; 17 | 18 | @Override 19 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 20 | setApplicationContextStatic(applicationContext); 21 | } 22 | 23 | public static void setApplicationContextStatic(ApplicationContext applicationContext) throws BeansException { 24 | SpringContextUtil.context = applicationContext; 25 | } 26 | 27 | public synchronized static Object getBean(String beanName) { 28 | return context.getBean(beanName); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cn/jackding/aliststrm/util/Utils.java: -------------------------------------------------------------------------------- 1 | package cn.jackding.aliststrm.util; 2 | 3 | import cn.jackding.aliststrm.tg.TgSendMsg; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | /** 9 | * @Author Jack 10 | * @Date 2024/6/22 19:23 11 | * @Version 1.0.0 12 | */ 13 | @Slf4j 14 | public class Utils { 15 | 16 | public static boolean isVideo(String name) { 17 | return name.toLowerCase().endsWith(".mp4") || name.toLowerCase().endsWith(".mkv") 18 | || name.toLowerCase().endsWith(".avi") || name.toLowerCase().endsWith(".mov") 19 | || name.toLowerCase().endsWith(".rmvb") || name.toLowerCase().endsWith(".flv") 20 | || name.toLowerCase().endsWith(".webm") || name.toLowerCase().endsWith(".m3u8") 21 | || name.toLowerCase().endsWith(".wmv") || name.toLowerCase().endsWith(".iso") || name.toLowerCase().endsWith(".ts"); 22 | 23 | } 24 | 25 | public static boolean isSrt(String name) { 26 | return name.toLowerCase().endsWith(".ass") || name.toLowerCase().endsWith(".srt"); 27 | } 28 | 29 | public static void sleep(long l) { 30 | try { 31 | TimeUnit.SECONDS.sleep(l); 32 | } catch (InterruptedException e) { 33 | log.error("", e); 34 | } 35 | } 36 | 37 | public static void sendTgMsg(String msg) { 38 | new TgSendMsg().sendMsg(msg); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #alist\u5730\u5740 \u5982http://192.168.1.2:5244 alistServerUrl= #alist\u5730\u5740 \u53EF\u5728alist\u540E\u53F0\u83B7\u53D6 alistServerToken= #\u9700\u8981\u751F\u6210strm\u6587\u4EF6\u7684\u76EE\u5F55\u5982http://192.168.1.2:5244/\u963F\u91CC\u4E91\u5206\u4EAB/\u7535\u5F71 \u90A3\u5C31\u586B\u5165/\u963F\u91CC\u4E91\u5206\u4EAB/\u7535\u5F71 alistScanPath= #\u751F\u6210strm\u6587\u4EF6\u7684\u5B58\u653E\u5730\u5740 \u9ED8\u8BA4data output.dir=/data server.port=6894 spring.main.banner-mode=off spring.main.log-startup-info=false -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | /log 11 | 12 | [%d{yyyy-MM-dd HH:mm:ss:SSS}] %msg%n 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | --------------------------------------------------------------------------------