├── .gitignore
├── LICENSE
├── README.md
├── db
├── media.mv.db
└── media.trace.db
├── img
├── cloud189-auto-save.png
├── cloud189-auto-save1.png
├── emby-shenyi.png
├── emby-shenyi2.png
├── quark-auto-save.png
├── tg通知
│ ├── 最爱更新.png
│ ├── 深度删除.png
│ └── 片头片尾更新.png
├── 任务配置1.png
└── 任务配置2.png
├── lib
├── sgys-common-2.0.jar
├── sgys-common-2.0.pom
├── sgys-core-biz-rpc-2.0.jar
├── sgys-core-biz-rpc-2.0.pom
├── sgys-core-mybatisplus-2.0.jar
├── sgys-core-mybatisplus-2.0.pom
├── sgys-core-web-2.0.jar
├── sgys-core-web-2.0.pom
└── sgys-dependencies-2.0.pom
├── pom.xml
└── src
└── main
├── java
└── com
│ └── zq
│ └── media
│ └── tools
│ ├── MediaToolsApplication.java
│ ├── config
│ ├── StrmWebConfig.java
│ ├── TelegramBotConfig.java
│ └── feign
│ │ ├── Feign115Config.java
│ │ ├── FeignAlistConfig.java
│ │ ├── FeignEmbyConfig.java
│ │ └── FeignTtmConfig.java
│ ├── controller
│ ├── AlistController.java
│ ├── Driver115Controller.java
│ └── ReceiveNotificationController.java
│ ├── driver
│ └── Driver115.java
│ ├── dto
│ ├── HandleFileDTO.java
│ ├── PendingProcessFileDTO.java
│ ├── req
│ │ ├── alist
│ │ │ ├── CopyFileReqDTO.java
│ │ │ ├── DeleteFileReqDTO.java
│ │ │ ├── GetFileReqDTO.java
│ │ │ ├── ListFileReqDTO.java
│ │ │ ├── MoveFileReqDTO.java
│ │ │ └── RenameFileReqDTO.java
│ │ └── ttm
│ │ │ └── TtmReqDTO.java
│ └── resp
│ │ ├── alist
│ │ ├── GetFileRespDTO.java
│ │ ├── TaskRespDTO.java
│ │ └── listFileRespDTO.java
│ │ ├── driver115
│ │ ├── BehaviorDetailRespDTO.java
│ │ ├── BehaviorDetailsRespDTO.java
│ │ ├── FileListRespDTO.java
│ │ ├── GetDownloadUrlRespDTO.java
│ │ ├── GetPathRespDTO.java
│ │ └── LifeListRespDTO.java
│ │ └── emby
│ │ ├── FavoriteItemsDTO.java
│ │ ├── ItemRespDTO.java
│ │ └── MediaPlaybackInfoRespDTO.java
│ ├── entity
│ └── Media115.java
│ ├── enums
│ ├── BehaviorType.java
│ ├── EmbyEvent.java
│ ├── EmbyMediaType.java
│ ├── EmbyNotifyType.java
│ ├── FileCategory.java
│ ├── TelegramBotCommand.java
│ ├── TtmAction.java
│ └── TtmScopeName.java
│ ├── feign
│ ├── AlistClient.java
│ ├── Driver115Client.java
│ ├── EmbyClient.java
│ ├── Life115Client.java
│ └── TtmClient.java
│ ├── init
│ └── AlistStrmInit.java
│ ├── mapper
│ └── Media115Mapper.java
│ ├── params
│ └── EmbyNotifyParam.java
│ ├── properties
│ ├── ConfigProperties.java
│ └── TelegramBotProperties.java
│ ├── service
│ ├── IAlistService.java
│ ├── IMedia115Service.java
│ ├── IReceiveNotificationService.java
│ ├── ITelegramBotService.java
│ └── impl
│ │ ├── AlistServiceImpl.java
│ │ ├── Media115ServiceImpl.java
│ │ ├── ReceiveNotificationServiceImpl.java
│ │ └── TelegramBotServiceImpl.java
│ ├── task
│ └── Driver115LifeMonitor.java
│ ├── telegram
│ └── handler
│ │ ├── CommandHandler.java
│ │ ├── SgysTelegramBot.java
│ │ └── command
│ │ └── GetImageCommand.java
│ └── util
│ ├── FileUtil.java
│ ├── MediaUtil.java
│ └── StrmUtil.java
└── resources
├── Dockerfile
├── application-media.yml
├── application.yml
├── config
├── init.sql
└── logback.xml
└── static
└── pic.jpg
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | !.mvn/wrapper/maven-wrapper.jar
3 | !**/src/main/**/target/
4 | !**/src/test/**/target/
5 |
6 | ### IntelliJ IDEA ###
7 | .idea/
8 | .idea/modules.xml
9 | .idea/jarRepositories.xml
10 | .idea/compiler.xml
11 | .idea/libraries/
12 | *.iws
13 | *.iml
14 | *.ipr
15 |
16 | ### Eclipse ###
17 | .apt_generated
18 | .classpath
19 | .factorypath
20 | .project
21 | .settings
22 | .springBeans
23 | .sts4-cache
24 |
25 | ### NetBeans ###
26 | /nbproject/private/
27 | /nbbuild/
28 | /dist/
29 | /nbdist/
30 | /.nb-gradle/
31 | build/
32 | !**/src/main/**/build/
33 | !**/src/test/**/build/
34 |
35 | ### VS Code ###
36 | .vscode/
37 |
38 | ### Mac OS ###
39 | .DS_Store
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 ShangGuanYunShun
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # sgys-media-tools
2 |
3 | #### 介绍
4 | 媒体库工具:strm工具、网盘、alist等等
5 | 1. 利用alist,可以全量生成strm文件,并下载图片及nfo等相关文件。
6 | 2. 通过监听115生活事件,实现增量数据入库,自动生成strm文件,下载图片及nfo文件。
7 | 3.
8 | 结合[quark-auto-save](https://github.com/Cp0204/quark-auto-save)、[cloud189-auto-save](https://github.com/1307super/cloud189-auto-save)
9 | 等项目,可自动化完成追剧、刮削信息、入库操作。
10 |
11 | #### 安装教程
12 | ##### 通过Docker安装
13 | **docker-compose.yml**
14 |
15 | ```yml
16 | services:
17 | sgys-media-tools:
18 | container_name: sgys-media-tools
19 | image: sgys-media-tools:2.0
20 | network_mode: bridge
21 | environment:
22 | TZ: Asia/Shanghai
23 | volumes:
24 | - /etc/localtime:/etc/localtime:ro
25 | - /data/sgys-media-tools/logs:/app/logs
26 | - /data/sgys-media-tools/config/application.yml:/app/config/application.yml
27 | - /data/sgys-media-tools/config/application-media.yml:/app/config/application-media.yml
28 | - /data/sgys-media-tools/db:/app/db
29 | - /mnt/strm:/mnt/strm
30 | ports:
31 | - 8082:8080
32 | restart: always
33 |
34 | ```
35 | #### 配置文件
36 |
37 | **applition-media.yml**
38 | ```yml
39 | app:
40 | apiRateLimit: 1 # API 速率限制
41 | downloadMediaFile: true # 是否下载媒体文件
42 | encodeStrmPath: true # 是否编码strm路径
43 | # alist配置
44 | alist:
45 | token: alist-39c51876b-956d-4f2c-8243-e280439243YcXc2424ivJ6J3oHzgXVItn245222222cG7goPpUIX42HP2T44ClK06rhbcsb8AlTkT # alist token
46 | url: http://113.45.219.178:5244 # alist地址
47 | mediaUrl: http://127.0.0.1:5244/d # alist媒体地址
48 | driver115Path: /115网盘 # alist115网盘路径
49 | # 待处理媒体路径
50 | # mediaPath:
51 | # - /115网盘/电视剧
52 | # - /115网盘/电影
53 | # - /115网盘/综艺
54 | # - /115网盘/纪录片
55 | # - /115网盘/节目晚会
56 | # 刮削路径
57 | scrapPath:
58 | "[斗罗大陆2绝世唐门]": /115网盘/未刮削/连载动漫/斗罗大陆Ⅱ绝世唐门 (2023)/Season 1
59 | "[斗破苍穹]": /115网盘/未刮削/连载动漫/斗破苍穹 (2017)/Season 5
60 | "[吞噬星空]": /115网盘/未刮削/连载动漫/吞噬星空 (2020)/Season 1
61 | "[完美世界]": /115网盘/未刮削/连载动漫/完美世界 (2021)/Season 1
62 | "[遮天]": /115网盘/未刮削/连载动漫/遮天 (2023)/Season 1
63 | "[凡人修仙传]": /115网盘/未刮削/连载动漫/凡人修仙传 (2020)/Season 7
64 | "[棋士]": /天翼云盘/未刮削/连载电视/棋士 (2025)/Season 1
65 | # 文件最终保存路径
66 | serializedTvShow:
67 | "[斗罗大陆Ⅱ绝世唐门 (2023)]": /115网盘/动漫/国漫/斗罗大陆Ⅱ绝世唐门 (2023)/Season 1
68 | "[斗破苍穹 (2017)]": /115网盘/动漫/国漫/斗破苍穹 (2017)/Season 5
69 | "[吞噬星空 (2020)]": /115网盘/动漫/国漫/吞噬星空 (2020)/Season 1
70 | "[完美世界 (2021)]": /115网盘/动漫/国漫/完美世界 (2021)/Season 1
71 | "[棋士 (2025)]": /天翼云盘/电视剧/棋士 (2025)/Season 1
72 | # 服务器配置
73 | server:
74 | basePath: /mnt/strm # 媒体文件本地保存路径
75 | driver115Path: /mnt/strm/115网盘 # 115网盘本地保存路径
76 | # 115网盘配置
77 | driver115:
78 | enabled: false # 是否开启115网盘生活监听
79 | intervalMinutes: 30 # 监听间隔
80 | cookie: UID=341890318_D1_1740142749;CID=cae3a0b208kd675e45f2sdc2245dda4d9;SEID=7405c2ad1b10c90646c1342378b8e403ab543ce17b243ca99b0cf5b568ed8dd213132c1d62309e76729bbad2ec;KID=7297fc8a238a0db9ff6b9b8916dff66d
81 | # 忽略文件夹
82 | ignoreFolders:
83 | - /未刮削
84 | - /音乐
85 | - /云下载
86 | - /我的接收
87 | - /游戏
88 | driverQuark:
89 | handleFolders:
90 | - /接收连载电视
91 | - /接收连载动漫
92 | driverCloud189:
93 | handleFolders:
94 | - /接收连载电视
95 | - /接收连载动漫
96 | # tinymediamanger配置
97 | ttm:
98 | enabled: false # 是否开启ttm刮削
99 | url: http://127.0.0.1:7878/api/tvshows
100 | apiKey: 3684ce7c-8917-4a75-af06-6be611bafbe5
101 | scrapTime: 60 # 刮削时间
102 | # cd2配置
103 | cloud-drive:
104 | enabled: false # 是否开启cd2刮削,不开启则默认刮削为alist挂载到本地的路径
105 | url: http://127.0.0.1:19798
106 | username: 1144804894@qq.com
107 | password: 123456
108 | emby:
109 | url: http://127.0.0.1:8096
110 | apiKey: 213214adsagfgasdgasga
111 | user-id: 123kskjjfafasf
112 | episodeGroup:
113 | - 凡人修仙传|S01E135-S01E152 -> S07E11-S07E28
114 | ```
115 |
116 | **application.yml**
117 |
118 | ```yml
119 | # telegram机器人配置
120 | telegrambots:
121 | enabled: false
122 | token: 81997859:AASSEm9iV_8Q23216xc-dsadBpn2Xwdaysj415aZzc
123 | chat-id: 56136141
124 | proxy:
125 | hostname: 192.168.2.3
126 | port: 7890
127 | ```
128 |
129 | #### quark-auto-save配置
130 | 
131 | > WEBHOOK_URL:**http://127.0.0.1:8082/receive/notification/quark/auto-save**
132 |
133 | > WEBHOOK_BODY:**body:"\$title","\$content"**
134 |
135 | > WEBHOOK_METHOD:**POST**
136 |
137 | > WEBHOOK_CONTENT_TYPE:**application/json**
138 |
139 | > WEBHOOK_HEADERS:**Content-Type:application/json**
140 |
141 | 
142 | 
143 |
144 | #### cloud189-auto-save配置
145 |
146 | config.json文件配置推送路径
147 | 
148 | 
149 |
150 | #### emby通知配置
151 |
152 | ##### 神医通知
153 |
154 | 
155 | 
156 |
157 | ##### tg机器人通知效果
158 |
159 | 
160 | 
161 | 
162 | #### 参与贡献
163 |
--------------------------------------------------------------------------------
/db/media.mv.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShangGuanYunShun/sgys-media-tools/c265f47093706232dd26a61e959b637f3b5cc232/db/media.mv.db
--------------------------------------------------------------------------------
/db/media.trace.db:
--------------------------------------------------------------------------------
1 | 2025-05-08 09:30:39.787222+08:00 jdbc[3]: exception
2 | org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "MEDIA_115" not found (this database is empty); SQL statement:
3 | SELECT id,file_id,parent_id,path,file_name,sha1,ext,create_time,update_time FROM MEDIA_115 WHERE (file_id = ?) [42104-224]
4 | 2025-05-08 09:30:52.375407+08:00 jdbc[3]: exception
5 | org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "MEDIA_115" not found (this database is empty); SQL statement:
6 | SELECT id,file_id,parent_id,path,file_name,sha1,ext,create_time,update_time FROM MEDIA_115 WHERE (file_id = ?) [42104-224]
7 | 2025-05-08 09:31:49.451803+08:00 jdbc[3]: exception
8 | org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "MEDIA_115" not found (this database is empty); SQL statement:
9 | INSERT INTO MEDIA_115 ( file_id, parent_id, path, file_name, sha1, ext, create_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? ) [42104-224]
10 | 2025-05-08 09:31:52.511454+08:00 jdbc[3]: exception
11 | org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "MEDIA_115" not found (this database is empty); SQL statement:
12 | SELECT id,file_id,parent_id,path,file_name,sha1,ext,create_time,update_time FROM MEDIA_115 WHERE (file_id = ?) [42104-224]
13 | 2025-05-08 09:32:49.523385+08:00 jdbc[3]: exception
14 | org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "MEDIA_115" not found (this database is empty); SQL statement:
15 | INSERT INTO MEDIA_115 ( file_id, parent_id, path, file_name, sha1, ext, create_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? ) [42104-224]
16 | 2025-05-08 09:32:56.819911+08:00 jdbc[3]: exception
17 | org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "MEDIA_115" not found (this database is empty); SQL statement:
18 | INSERT INTO MEDIA_115 ( file_id, parent_id, path, file_name, sha1, ext, create_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? ) [42104-224]
19 | 2025-05-08 09:34:34.995618+08:00 jdbc[3]: exception
20 | org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "MEDIA_115" not found (this database is empty); SQL statement:
21 | INSERT INTO MEDIA_115 ( file_id, parent_id, path, file_name, sha1, ext, create_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? ) [42104-224]
22 | 2025-05-08 09:34:36.506659+08:00 jdbc[3]: exception
23 | org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "MEDIA_115" not found (this database is empty); SQL statement:
24 | INSERT INTO MEDIA_115 ( file_id, parent_id, path, file_name, sha1, ext, create_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? ) [42104-224]
25 |
--------------------------------------------------------------------------------
/img/cloud189-auto-save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShangGuanYunShun/sgys-media-tools/c265f47093706232dd26a61e959b637f3b5cc232/img/cloud189-auto-save.png
--------------------------------------------------------------------------------
/img/cloud189-auto-save1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShangGuanYunShun/sgys-media-tools/c265f47093706232dd26a61e959b637f3b5cc232/img/cloud189-auto-save1.png
--------------------------------------------------------------------------------
/img/emby-shenyi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShangGuanYunShun/sgys-media-tools/c265f47093706232dd26a61e959b637f3b5cc232/img/emby-shenyi.png
--------------------------------------------------------------------------------
/img/emby-shenyi2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShangGuanYunShun/sgys-media-tools/c265f47093706232dd26a61e959b637f3b5cc232/img/emby-shenyi2.png
--------------------------------------------------------------------------------
/img/quark-auto-save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShangGuanYunShun/sgys-media-tools/c265f47093706232dd26a61e959b637f3b5cc232/img/quark-auto-save.png
--------------------------------------------------------------------------------
/img/tg通知/最爱更新.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShangGuanYunShun/sgys-media-tools/c265f47093706232dd26a61e959b637f3b5cc232/img/tg通知/最爱更新.png
--------------------------------------------------------------------------------
/img/tg通知/深度删除.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShangGuanYunShun/sgys-media-tools/c265f47093706232dd26a61e959b637f3b5cc232/img/tg通知/深度删除.png
--------------------------------------------------------------------------------
/img/tg通知/片头片尾更新.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShangGuanYunShun/sgys-media-tools/c265f47093706232dd26a61e959b637f3b5cc232/img/tg通知/片头片尾更新.png
--------------------------------------------------------------------------------
/img/任务配置1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShangGuanYunShun/sgys-media-tools/c265f47093706232dd26a61e959b637f3b5cc232/img/任务配置1.png
--------------------------------------------------------------------------------
/img/任务配置2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShangGuanYunShun/sgys-media-tools/c265f47093706232dd26a61e959b637f3b5cc232/img/任务配置2.png
--------------------------------------------------------------------------------
/lib/sgys-common-2.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShangGuanYunShun/sgys-media-tools/c265f47093706232dd26a61e959b637f3b5cc232/lib/sgys-common-2.0.jar
--------------------------------------------------------------------------------
/lib/sgys-common-2.0.pom:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.zq
7 | sgys-project
8 | 2.0
9 |
10 | sgys-common
11 | 2.0
12 |
13 |
14 | org.dromara.hutool
15 | hutool-all
16 |
17 |
18 | org.projectlombok
19 | lombok
20 |
21 |
22 | org.springframework.boot
23 | spring-boot-starter-json
24 |
25 |
26 | com.github.ben-manes.caffeine
27 | caffeine
28 |
29 |
30 | com.google.guava
31 | guava
32 |
33 |
34 | com.baomidou
35 | mybatis-plus-annotation
36 | true
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/lib/sgys-core-biz-rpc-2.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShangGuanYunShun/sgys-media-tools/c265f47093706232dd26a61e959b637f3b5cc232/lib/sgys-core-biz-rpc-2.0.jar
--------------------------------------------------------------------------------
/lib/sgys-core-biz-rpc-2.0.pom:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.zq
7 | sgys-core-biz-module
8 | 2.0
9 |
10 | sgys-core-biz-rpc
11 | 2.0
12 |
13 |
14 | com.zq
15 | sgys-common
16 |
17 |
18 | org.springframework.cloud
19 | spring-cloud-starter-loadbalancer
20 |
21 |
22 | org.springframework.cloud
23 | spring-cloud-starter-openfeign
24 |
25 |
26 | io.github.openfeign
27 | feign-java11
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/lib/sgys-core-mybatisplus-2.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShangGuanYunShun/sgys-media-tools/c265f47093706232dd26a61e959b637f3b5cc232/lib/sgys-core-mybatisplus-2.0.jar
--------------------------------------------------------------------------------
/lib/sgys-core-mybatisplus-2.0.pom:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.zq
7 | sgys-core-module
8 | 2.0
9 |
10 | sgys-core-mybatisplus
11 | 2.0
12 |
13 |
14 | com.zq
15 | sgys-core-web
16 |
17 |
18 | com.baomidou
19 | mybatis-plus-spring-boot3-starter
20 |
21 |
22 | com.baomidou
23 | mybatis-plus-jsqlparser
24 |
25 |
26 | com.alibaba
27 | druid-spring-boot-3-starter
28 |
29 |
30 | com.baomidou
31 | dynamic-datasource-spring-boot3-starter
32 |
33 |
34 | org.springframework.boot
35 | spring-boot-starter-undertow
36 |
37 |
38 |
39 |
40 | io.swagger.core.v3
41 | swagger-annotations-jakarta
42 | true
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/lib/sgys-core-web-2.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShangGuanYunShun/sgys-media-tools/c265f47093706232dd26a61e959b637f3b5cc232/lib/sgys-core-web-2.0.jar
--------------------------------------------------------------------------------
/lib/sgys-core-web-2.0.pom:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.zq
7 | sgys-core-module
8 | 2.0
9 |
10 | sgys-core-web
11 | 2.0
12 |
13 |
14 | com.zq
15 | sgys-core-redis
16 |
17 |
18 | org.springframework.boot
19 | spring-boot-starter-web
20 |
21 |
22 | org.springframework.boot
23 | spring-boot-starter-tomcat
24 |
25 |
26 |
27 |
28 | org.springframework.boot
29 | spring-boot-starter-undertow
30 |
31 |
32 | org.springframework.boot
33 | spring-boot-starter-validation
34 |
35 |
36 | org.springframework.boot
37 | spring-boot-starter-aop
38 |
39 |
40 | org.springframework.security
41 | spring-security-core
42 | true
43 |
44 |
45 | com.github.xiaoymin
46 | knife4j-openapi3-jakarta-spring-boot-starter
47 |
48 |
49 | cn.idev.excel
50 | fastexcel
51 |
52 |
53 | com.alibaba
54 | transmittable-thread-local
55 |
56 |
57 | com.baomidou
58 | mybatis-plus-annotation
59 | true
60 |
61 |
62 | org.apache.skywalking
63 | apm-toolkit-trace
64 |
65 |
66 | org.apache.tika
67 | tika-core
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/lib/sgys-dependencies-2.0.pom:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.zq
8 | sgys-dependencies
9 | ${revision}
10 | pom
11 |
12 |
13 | 2.0
14 |
15 | 17
16 | ${java.version}
17 | ${java.version}
18 | UTF-8
19 | 1.6.0
20 |
21 | 3.2.9
22 | 2023.0.3
23 | 2023.0.3.2
24 |
25 |
26 | 6.0.0-M21
27 | 33.2.1-jre
28 | 1.1.0
29 | 1.4.0
30 |
31 | 1.0.10
32 |
33 |
34 | 1.2.24
35 | 3.5.12
36 | 4.3.1
37 | 8.0.32
38 |
39 | 4.4.0
40 | 2.2.19
41 | 2.14.5
42 |
43 |
44 | 9.0.0
45 |
46 | 2.3
47 | 3.1.0
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | org.springframework.boot
57 | spring-boot-dependencies
58 | ${spring.boot.version}
59 | pom
60 | import
61 |
62 |
63 |
64 |
65 | org.springframework.cloud
66 | spring-cloud-dependencies
67 | ${spring.cloud.version}
68 | pom
69 | import
70 |
71 |
72 |
73 |
74 | com.alibaba.cloud
75 | spring-cloud-alibaba-dependencies
76 | ${spring.cloud.alibaba.version}
77 | pom
78 | import
79 |
80 |
81 |
82 | com.zq
83 | sgys-common
84 | ${revision}
85 |
86 |
87 |
88 |
89 | com.zq
90 | sgys-core-web
91 | ${revision}
92 |
93 |
94 |
95 | com.zq
96 | sgys-core-mybatisplus
97 | ${revision}
98 |
99 |
100 |
101 | com.zq
102 | sgys-core-redis
103 | ${revision}
104 |
105 |
106 |
107 | com.zq
108 | sgys-core-mq
109 | ${revision}
110 |
111 |
112 |
113 |
114 | com.zq
115 | sgys-core-biz-security
116 | ${revision}
117 |
118 |
119 |
120 | com.zq
121 | sgys-core-biz-tenant
122 | ${revision}
123 |
124 |
125 |
126 | com.zq
127 | sgys-core-biz-rpc
128 | ${revision}
129 |
130 |
131 |
132 |
133 | com.zq
134 | sgys-system-service-api
135 | ${revision}
136 |
137 |
138 |
139 |
140 | com.google.guava
141 | guava
142 | ${guava.version}
143 |
144 |
145 |
146 | org.dromara.hutool
147 | hutool-all
148 | ${hutool.version}
149 |
150 |
151 |
152 | com.baomidou
153 | mybatis-plus-annotation
154 | ${mybatis.plus.version}
155 |
156 |
157 |
158 | io.swagger.core.v3
159 | swagger-annotations-jakarta
160 | ${swagger.core.version}
161 |
162 |
163 |
164 | com.github.xiaoymin
165 | knife4j-openapi3-jakarta-spring-boot-starter
166 | ${knife4j.version}
167 |
168 |
169 |
170 | com.github.xiaoymin
171 | knife4j-gateway-spring-boot-starter
172 | ${knife4j.version}
173 |
174 |
175 |
176 |
177 | cn.idev.excel
178 | fastexcel
179 | ${fastexcel.version}
180 |
181 |
182 |
183 | com.alibaba
184 | transmittable-thread-local
185 | ${transmittable.thread-local.version}
186 |
187 |
188 |
189 | com.baomidou
190 | mybatis-plus-spring-boot3-starter
191 | ${mybatis.plus.version}
192 |
193 |
194 |
195 | com.baomidou
196 | mybatis-plus-jsqlparser
197 | ${mybatis.plus.version}
198 |
199 |
200 |
201 | com.baomidou
202 | mybatis-plus-generator
203 | ${mybatis.plus.version}
204 |
205 |
206 |
207 | com.alibaba
208 | druid-spring-boot-3-starter
209 | ${druid.version}
210 |
211 |
212 |
213 |
214 | com.baomidou
215 | dynamic-datasource-spring-boot3-starter
216 | ${dynamic.datasource.version}
217 |
218 |
219 |
220 |
221 | com.anji-plus
222 | captcha-spring-boot-starter
223 | ${anji-plus-captcha.version}
224 |
225 |
226 |
227 |
228 | org.apache.skywalking
229 | apm-toolkit-trace
230 | ${skywalking.version}
231 |
232 |
233 |
234 |
235 | org.apache.velocity
236 | velocity-engine-core
237 | ${velocity.version}
238 |
239 |
240 |
241 |
242 | org.apache.tika
243 | tika-core
244 | ${tika-core.version}
245 |
246 |
247 |
248 |
249 |
250 |
251 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.zq
8 | sgys-media-tools
9 | 2.0
10 |
11 |
12 | 17
13 | ${java.version}
14 | ${java.version}
15 | UTF-8
16 |
17 | 2.0
18 |
19 | 8.3.0
20 | 1.17.2
21 |
22 |
23 |
24 |
25 |
26 |
27 | com.zq
28 | sgys-dependencies
29 | ${sgys.version}
30 | pom
31 | import
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | maven-nexus
41 | maven-nexus
42 | http://192.168.30.78:8081/repository/maven-public/
43 |
44 | true
45 |
46 |
47 | true
48 |
49 |
50 |
51 |
52 |
53 |
54 | sgys-releases
55 | Releases
56 | http://192.168.30.78:8081/repository/sgys/
57 |
58 |
59 | sgys-snapshots
60 | Snapshot
61 | http://192.168.30.78:8081/repository/sgys-snapshots/
62 |
63 |
64 |
65 |
66 |
67 |
68 | com.zq
69 | sgys-core-web
70 |
71 |
72 |
73 | com.zq
74 | sgys-core-biz-rpc
75 |
76 |
77 |
78 | com.zq
79 | sgys-core-mybatisplus
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | com.h2database
89 | h2
90 | runtime
91 |
92 |
93 |
94 | org.springframework.boot
95 | spring-boot-starter-test
96 |
97 |
98 |
99 |
100 | org.telegram
101 | telegrambots-springboot-longpolling-starter
102 | ${telegrambots.version}
103 |
104 |
105 |
106 | org.telegram
107 | telegrambots-client
108 | ${telegrambots.version}
109 |
110 |
111 |
112 | org.telegram
113 | telegrambots-extensions
114 | ${telegrambots.version}
115 |
116 |
117 | org.telegram
118 | telegrambots-webhook
119 |
120 |
121 |
122 |
123 |
124 | org.jsoup
125 | jsoup
126 | ${jsoup.version}
127 |
128 |
129 |
130 | com.squareup.okhttp3
131 | logging-interceptor
132 | 4.9.3
133 |
134 |
135 |
136 |
137 |
138 | ${project.artifactId}-${project.version}
139 |
140 |
141 |
142 | ${basedir}/src/main/resources
143 |
144 |
145 |
146 |
147 |
148 | org.apache.maven.plugins
149 | maven-compiler-plugin
150 |
151 | ${maven.compiler.source}
152 | ${maven.compiler.target}
153 | ${project.build.sourceEncoding}
154 |
155 | -parameters
156 |
157 |
158 |
159 |
160 | org.apache.maven.plugins
161 | maven-jar-plugin
162 |
163 |
164 |
165 | *.yml
166 | *.properties
167 | config/**
168 | Dockerfile
169 |
170 | ${project.build.directory}
171 |
172 |
173 |
174 |
175 |
176 | org.apache.maven.plugins
177 | maven-dependency-plugin
178 |
179 |
180 | copy-dependencies
181 | package
182 |
183 | copy-dependencies
184 |
185 |
186 | ${project.build.directory}/lib
187 | false
188 | false
189 | runtime
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 | maven-resources-plugin
198 |
199 |
200 |
201 | copy-resources
202 | package
203 |
204 | copy-resources
205 |
206 |
207 |
208 |
209 | src/main/resources
210 |
211 | *.yml
212 | config/**
213 |
214 |
215 |
216 | ${project.build.directory}/config
217 |
218 |
219 |
220 |
221 |
222 |
223 | org.springframework.boot
224 | spring-boot-maven-plugin
225 |
226 | true
227 | ZIP
228 |
229 |
230 | nothing
231 | nothing
232 |
233 |
234 |
235 |
236 | org.projectlombok
237 | lombok
238 |
239 |
240 |
241 |
242 |
243 |
244 | repackage
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 | org.apache.maven.plugins
253 | maven-surefire-plugin
254 |
255 | true
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/MediaToolsApplication.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.scheduling.annotation.EnableScheduling;
6 |
7 | /**
8 | * strm工具服务
9 | *
10 | * @author zhaoqiang
11 | * @version 1.0
12 | * @date 2024-11-11 14:04
13 | */
14 | @EnableScheduling
15 | @SpringBootApplication(scanBasePackages = "com.zq")
16 | public class MediaToolsApplication {
17 |
18 | public static void main(String[] args) {
19 | SpringApplication.run(MediaToolsApplication.class, args);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/config/StrmWebConfig.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.config;
2 |
3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.http.MediaType;
7 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
8 |
9 | import java.util.List;
10 |
11 | /**
12 | * @author zhaoqiang
13 | * @version 1.0
14 | * @date 2025-1-2 15:19
15 | */
16 | @Configuration
17 | @ConditionalOnProperty(prefix = "app.driver115", name = "enabled", havingValue = "true")
18 | public class StrmWebConfig {
19 |
20 | @Bean
21 | public CustomMappingJackson2HttpMessageConverter customMappingJackson2HttpMessageConverter() {
22 | return new CustomMappingJackson2HttpMessageConverter();
23 | }
24 |
25 | public static class CustomMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
26 |
27 | public CustomMappingJackson2HttpMessageConverter() {
28 | // 设置支持的内容类型
29 | super.setSupportedMediaTypes(List.of(
30 | MediaType.APPLICATION_JSON,
31 | MediaType.TEXT_HTML // 添加对 text/html 的支持
32 | ));
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/config/TelegramBotConfig.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.config;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import com.zq.media.tools.properties.TelegramBotProperties;
5 | import com.zq.media.tools.service.ITelegramBotService;
6 | import com.zq.media.tools.telegram.handler.CommandHandler;
7 | import com.zq.media.tools.telegram.handler.command.GetImageCommand;
8 | import lombok.RequiredArgsConstructor;
9 | import okhttp3.Credentials;
10 | import okhttp3.OkHttpClient;
11 | import okhttp3.Request;
12 | import okhttp3.logging.HttpLoggingInterceptor;
13 | import org.dromara.hutool.core.text.StrUtil;
14 | import org.springframework.beans.factory.annotation.Qualifier;
15 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
16 | import org.springframework.context.annotation.Bean;
17 | import org.springframework.context.annotation.Configuration;
18 | import org.telegram.telegrambots.client.okhttp.OkHttpTelegramClient;
19 | import org.telegram.telegrambots.extensions.bots.commandbot.commands.BotCommand;
20 | import org.telegram.telegrambots.extensions.bots.commandbot.commands.helpCommand.HelpCommand;
21 | import org.telegram.telegrambots.longpolling.TelegramBotsLongPollingApplication;
22 | import org.telegram.telegrambots.longpolling.util.TelegramOkHttpClientFactory;
23 | import org.telegram.telegrambots.meta.generics.TelegramClient;
24 |
25 | import java.net.InetSocketAddress;
26 | import java.net.Proxy;
27 | import java.util.List;
28 | import java.util.function.Supplier;
29 |
30 | import static java.util.Optional.ofNullable;
31 |
32 | /**
33 | * @author zhaoqiang
34 | * @version 1.0
35 | * @date 2025-4-15 15:04
36 | */
37 | @Configuration
38 | @ConditionalOnProperty(prefix = "telegrambots", name = "enabled", havingValue = "true", matchIfMissing = true)
39 | public class TelegramBotConfig {
40 |
41 | @Bean
42 | public OkHttpClient okClientHttp(TelegramBotProperties telegramBotProperties) {
43 | return new CustomHttpProxyOkHttpClientCreator(
44 | () -> new Proxy(Proxy.Type.HTTP, new InetSocketAddress(telegramBotProperties.getProxy().getHostname(), telegramBotProperties.getProxy().getPort())),
45 | () -> (route, response) -> {
46 | Request.Builder builder = response
47 | .request()
48 | .newBuilder();
49 | if (StrUtil.isNotBlank(telegramBotProperties.getProxy().getUsername()) || StrUtil.isNotBlank(telegramBotProperties.getProxy().getPassword())) {
50 | String credential = Credentials.basic(telegramBotProperties.getProxy().getUsername(), telegramBotProperties.getProxy().getPassword());
51 | builder.header("Proxy-Authorization", credential);
52 | }
53 | return builder.build();
54 | },
55 | new HttpLoggingInterceptor()
56 | ).get();
57 | }
58 |
59 | // @Bean
60 | // public OkHttpClient okClientSocks(TelegramBotProperties telegramBotProperties) {
61 | // Authenticator.setDefault(new Authenticator() {
62 | // @Override
63 | // protected PasswordAuthentication getPasswordAuthentication() {
64 | // if (getRequestingHost().equalsIgnoreCase(telegramBotProperties.getProxy().getHostname()) &&
65 | // telegramBotProperties.getProxy().getPort() == getRequestingPort()) {
66 | // return new PasswordAuthentication(telegramBotProperties.getProxy().getUsername(), telegramBotProperties.getProxy().getPassword().toCharArray());
67 | // }
68 | //
69 | // return null;
70 | // }
71 | // });
72 | //
73 | // return new TelegramOkHttpClientFactory.SocksProxyOkHttpClientCreator(
74 | // () -> new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(telegramBotProperties.getProxy().getHostname(), telegramBotProperties.getProxy().getPort()))
75 | // ).get();
76 | // }
77 |
78 | @Bean
79 | public TelegramClient telegramClient(@Qualifier("okClientHttp") OkHttpClient okClient, TelegramBotProperties telegramBotProperties) {
80 | return new OkHttpTelegramClient(okClient, telegramBotProperties.getToken());
81 | }
82 |
83 | @Bean
84 | public TelegramBotsLongPollingApplication telegramBotsApplication(@Qualifier("okClientHttp") OkHttpClient okClient, ObjectMapper objectMapper) {
85 | return new TelegramBotsLongPollingApplication(() -> objectMapper, () -> okClient);
86 | }
87 |
88 | // @Bean
89 | // public SgysTelegramBot sgysTelegramBot(ITelegramBotService telegramService, TelegramBotProperties telegramBotProperties) {
90 | // return new SgysTelegramBot(telegramService, telegramBotProperties);
91 | // }
92 |
93 | // region 机器人命令
94 | @Bean
95 | public HelpCommand helpCommand() {
96 | return new HelpCommand();
97 | }
98 |
99 | @Bean
100 | public GetImageCommand getImageCommand(ITelegramBotService telegramService) {
101 | return new GetImageCommand(telegramService);
102 | }
103 | // endregion
104 |
105 | @Bean
106 | public CommandHandler commandHandler(TelegramClient telegramClient, ITelegramBotService telegramService,
107 | TelegramBotProperties telegramBotProperties, List commands) {
108 | return new CommandHandler(telegramClient, telegramService, telegramBotProperties, commands);
109 | }
110 |
111 | @RequiredArgsConstructor
112 | public static class CustomHttpProxyOkHttpClientCreator extends TelegramOkHttpClientFactory.DefaultOkHttpClientCreator {
113 | private final Supplier proxySupplier;
114 | private final Supplier authenticatorSupplier;
115 | private final HttpLoggingInterceptor loggingInterceptor;
116 |
117 | @Override
118 | public OkHttpClient get() {
119 | OkHttpClient.Builder okHttpClientBuilder = getBaseClient();
120 | okHttpClientBuilder.addInterceptor(loggingInterceptor);
121 |
122 | // Proxy
123 | ofNullable(proxySupplier.get()).ifPresent(okHttpClientBuilder::proxy);
124 | ofNullable(authenticatorSupplier.get()).ifPresent(okHttpClientBuilder::proxyAuthenticator);
125 |
126 | return okHttpClientBuilder.build();
127 | }
128 | }
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/config/feign/Feign115Config.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.config.feign;
2 |
3 | import com.zq.media.tools.properties.ConfigProperties;
4 | import feign.RequestInterceptor;
5 | import org.springframework.context.annotation.Bean;
6 |
7 | /**
8 | * @author zhaoqiang
9 | * @version 1.0
10 | * @date 2024-11-12 14:32
11 | */
12 | public class Feign115Config {
13 |
14 | @Bean
15 | public RequestInterceptor requestInterceptor(ConfigProperties configProperties) {
16 | return requestTemplate -> requestTemplate.header("Cookie", configProperties.getDriver115().getCookie());
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/config/feign/FeignAlistConfig.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.config.feign;
2 |
3 | import com.zq.media.tools.properties.ConfigProperties;
4 | import feign.RequestInterceptor;
5 | import org.springframework.context.annotation.Bean;
6 |
7 | /**
8 | * alist feign配置
9 | *
10 | * @author zhaoqiang
11 | * @version 1.0
12 | * @date 2024-12-23 17:05
13 | */
14 | public class FeignAlistConfig {
15 |
16 | @Bean
17 | public RequestInterceptor requestInterceptor(ConfigProperties configProperties) {
18 | return requestTemplate -> requestTemplate.header("Authorization", configProperties.getAlist().getToken());
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/config/feign/FeignEmbyConfig.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.config.feign;
2 |
3 | import com.zq.media.tools.properties.ConfigProperties;
4 | import feign.RequestInterceptor;
5 | import org.springframework.context.annotation.Bean;
6 |
7 | /**
8 | * @author zhaoqiang
9 | * @version 1.0
10 | * @date 2025-4-21 9:41
11 | */
12 | public class FeignEmbyConfig {
13 |
14 | @Bean
15 | public RequestInterceptor requestInterceptor(ConfigProperties configProperties) {
16 | return requestTemplate -> {
17 | requestTemplate.uri(requestTemplate.url().replace("[userId]", configProperties.getEmby().getUserId()));
18 | requestTemplate.header("X-Emby-Token", configProperties.getEmby().getApiKey());
19 | };
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/config/feign/FeignTtmConfig.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.config.feign;
2 |
3 | import com.zq.media.tools.properties.ConfigProperties;
4 | import feign.RequestInterceptor;
5 | import org.springframework.context.annotation.Bean;
6 |
7 | /**
8 | * @author zhaoqiang
9 | * @version 1.0
10 | * @date 2024-12-23 17:13
11 | */
12 | public class FeignTtmConfig {
13 |
14 | @Bean
15 | public RequestInterceptor requestInterceptor(ConfigProperties configProperties) {
16 | return requestTemplate -> requestTemplate.header("api-key", configProperties.getTtm().getApiKey());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/controller/AlistController.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.controller;
2 |
3 | import com.zq.common.domain.Result;
4 | import com.zq.media.tools.service.IAlistService;
5 | import io.swagger.v3.oas.annotations.Operation;
6 | import io.swagger.v3.oas.annotations.tags.Tag;
7 | import lombok.RequiredArgsConstructor;
8 | import org.springframework.web.bind.annotation.PostMapping;
9 | import org.springframework.web.bind.annotation.RequestBody;
10 | import org.springframework.web.bind.annotation.RequestMapping;
11 | import org.springframework.web.bind.annotation.RestController;
12 |
13 | import java.util.List;
14 |
15 | /**
16 | * alist管理
17 | *
18 | * @author zhaoqiang
19 | * @version 1.0
20 | * @date 2025-2-20 11:23
21 | */
22 | @Tag(name = "alist管理")
23 | @RestController
24 | @RequestMapping("/alist")
25 | @RequiredArgsConstructor
26 | public class AlistController {
27 |
28 | private final IAlistService alistService;
29 |
30 | @Operation(summary = "创建strm文件")
31 | @PostMapping("/createStrm")
32 | public Result createStrm(@RequestBody List mediaPath) {
33 | mediaPath.forEach(alistService::processDic);
34 | return Result.success();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/controller/Driver115Controller.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.controller;
2 |
3 | import com.zq.common.domain.Result;
4 | import com.zq.media.tools.driver.Driver115;
5 | import io.swagger.v3.oas.annotations.Operation;
6 | import io.swagger.v3.oas.annotations.Parameter;
7 | import io.swagger.v3.oas.annotations.Parameters;
8 | import io.swagger.v3.oas.annotations.tags.Tag;
9 | import jakarta.validation.constraints.NotNull;
10 | import lombok.RequiredArgsConstructor;
11 | import org.springframework.validation.annotation.Validated;
12 | import org.springframework.web.bind.annotation.GetMapping;
13 | import org.springframework.web.bind.annotation.RequestMapping;
14 | import org.springframework.web.bind.annotation.RestController;
15 |
16 | import java.time.LocalDateTime;
17 |
18 | /**
19 | * 115网盘管理
20 | *
21 | * @author zhaoqiang
22 | * @version 1.0
23 | * @date 2025-1-21 10:19
24 | */
25 | @Tag(name = "115网盘管理")
26 | @RestController
27 | @RequestMapping("/driver/115")
28 | @RequiredArgsConstructor
29 | @Validated
30 | public class Driver115Controller {
31 |
32 | private final Driver115 driver115;
33 |
34 | @Operation(summary = "处理115网盘的生活动作")
35 | @Parameters({
36 | @Parameter(name = "beginTime", description = "开始时间", required = true),
37 | @Parameter(name = "endTime", description = "结束时间"),
38 | })
39 | @GetMapping("/behavior/handle")
40 | public Result handleBehavior(@NotNull(message = "开始时间不能为空") LocalDateTime beginTime, LocalDateTime endTime) {
41 | driver115.handleBehavior(beginTime, endTime);
42 | return Result.success();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/controller/ReceiveNotificationController.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.controller;
2 |
3 | import com.zq.common.domain.Result;
4 | import com.zq.media.tools.params.EmbyNotifyParam;
5 | import com.zq.media.tools.service.IReceiveNotificationService;
6 | import io.swagger.v3.oas.annotations.Operation;
7 | import io.swagger.v3.oas.annotations.tags.Tag;
8 | import lombok.RequiredArgsConstructor;
9 | import lombok.extern.slf4j.Slf4j;
10 | import org.springframework.web.bind.annotation.PostMapping;
11 | import org.springframework.web.bind.annotation.RequestBody;
12 | import org.springframework.web.bind.annotation.RequestMapping;
13 | import org.springframework.web.bind.annotation.RestController;
14 |
15 | import java.util.LinkedHashMap;
16 | import java.util.Map;
17 |
18 | /**
19 | * @author zhaoqiang
20 | * @version 1.0
21 | * @date 2025/1/1 0:11
22 | */
23 | @Slf4j
24 | @Tag(name = "接收通知")
25 | @RestController
26 | @RequestMapping("/receive/notification")
27 | @RequiredArgsConstructor
28 | public class ReceiveNotificationController {
29 |
30 | private final IReceiveNotificationService receiveNotificationService;
31 |
32 | @Operation(summary = "接收来自夸克的自动转存通知", tags = "网盘自动转存")
33 | @PostMapping("/quark/auto-save")
34 | public Result quarkAutoSave(@RequestBody Map request) {
35 | String content = (String) request.get("body");
36 | receiveNotificationService.receiveQuarkAutoSave(content);
37 | return Result.success();
38 | }
39 |
40 | @Operation(summary = "接收来自天翼云盘的自动转存通知", tags = "网盘自动转存")
41 | @PostMapping("/cloud189/auto-save")
42 | public Result cloud189AutoSave(@RequestBody Map request) {
43 | String content = (String) ((LinkedHashMap) request.get("markdown")).get("content");
44 | receiveNotificationService.receiveCloud189AutoSave(content);
45 | return Result.success();
46 | }
47 |
48 | @Operation(summary = "接收来自百度网盘的自动转存通知", tags = "网盘自动转存")
49 | @PostMapping("/baidu/auto-save")
50 | public Result baiduAutoSave(@RequestBody Map request) {
51 | //TODO 后续完善
52 | return Result.success();
53 | }
54 |
55 | @Operation(summary = "接收来自emby的神医通知", tags = "emby通知")
56 | @PostMapping("/emby/shenyi")
57 | public Result embyFromShenYi(@RequestBody EmbyNotifyParam embyNotifyParam) {
58 | receiveNotificationService.receiveEmbyFromShenYi(embyNotifyParam);
59 | return Result.success();
60 | }
61 |
62 | @Operation(summary = "接收来自emby的入库通知", tags = "emby通知")
63 | @PostMapping("/emby/media")
64 | public Result embyFromMedia(@RequestBody EmbyNotifyParam embyNotifyParam) {
65 | receiveNotificationService.receiveEmbyMedia(embyNotifyParam);
66 | return Result.success();
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/HandleFileDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import lombok.Setter;
7 | import lombok.ToString;
8 |
9 | import java.util.Set;
10 |
11 | /**
12 | * 夸克通知文件
13 | *
14 | * @author zhaoqiang
15 | * @version 1.0
16 | * @date 2025-1-8 15:46
17 | */
18 | @Getter
19 | @Setter
20 | @ToString
21 | @AllArgsConstructor
22 | @NoArgsConstructor
23 | public class HandleFileDTO {
24 |
25 | /**
26 | * 文件夹
27 | */
28 | private String folderPath;
29 |
30 | /**
31 | * 文件
32 | */
33 | private Set files;
34 |
35 | /**
36 | * 是否是单个任务
37 | */
38 | private Boolean isSingleTask;
39 |
40 | public HandleFileDTO(String folderPath, Set files) {
41 | this.folderPath = folderPath;
42 | this.files = files;
43 | this.isSingleTask = true;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/PendingProcessFileDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto;
2 |
3 | import com.zq.media.tools.enums.BehaviorType;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 | import lombok.experimental.Accessors;
8 |
9 | /**
10 | * 待处理文件
11 | *
12 | * @author zhaoqiang
13 | * @version 1.0
14 | * @date 2024-11-15 13:50
15 | */
16 | @Getter
17 | @Setter
18 | @ToString
19 | @Accessors(chain = true)
20 | public class PendingProcessFileDTO {
21 |
22 | /**
23 | * 文件id
24 | */
25 | private String fileId;
26 |
27 | /**
28 | * 文件名
29 | */
30 | private String fileName;
31 |
32 | /**
33 | * 旧文件名
34 | */
35 | private String oldFileName;
36 |
37 | /**
38 | * 选取代码
39 | */
40 | private String pickCode;
41 |
42 | /**
43 | * SHA1
44 | */
45 | private String sha1;
46 |
47 | /**
48 | * 文件路径
49 | */
50 | private String filePath;
51 |
52 | /**
53 | * 父文件id
54 | */
55 | private String parentId;
56 |
57 | /**
58 | * 文件扩展名
59 | */
60 | private String ext;
61 |
62 | /**
63 | * 行为类型
64 | */
65 | private BehaviorType behaviorType;
66 |
67 | /**
68 | * 是否是目录
69 | */
70 | private Boolean isDic;
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/req/alist/CopyFileReqDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.req.alist;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import lombok.ToString;
6 | import lombok.experimental.Accessors;
7 |
8 | /**
9 | * 复制文件请求体
10 | *
11 | * @author zhaoqiang
12 | * @version 1.0
13 | * @date 2025/1/1 16:14
14 | */
15 | @Getter
16 | @Setter
17 | @ToString
18 | @Accessors(chain = true)
19 | public class CopyFileReqDTO extends MoveFileReqDTO {
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/req/alist/DeleteFileReqDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.req.alist;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 | import lombok.experimental.Accessors;
8 |
9 | import java.util.Set;
10 |
11 | /**
12 | * 删除文件req
13 | *
14 | * @author zhaoqiang
15 | * @version 1.0
16 | * @date 2025/1/1 17:33
17 | */
18 | @Getter
19 | @Setter
20 | @ToString
21 | @Accessors(chain = true)
22 | @AllArgsConstructor
23 | public class DeleteFileReqDTO {
24 |
25 | /**
26 | * 目录
27 | */
28 | private String dir;
29 |
30 | /**
31 | * 文件名
32 | */
33 | private Set names;
34 |
35 | public DeleteFileReqDTO(String dir, String... names) {
36 | this.dir = dir;
37 | this.names = Set.of(names);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/req/alist/GetFileReqDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.req.alist;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import lombok.ToString;
6 | import lombok.experimental.Accessors;
7 |
8 | /**
9 | * 获取文件信息
10 | *
11 | * @author zhaoqiang
12 | * @version 1.0
13 | * @date 2025-1-8 10:45
14 | */
15 | @Getter
16 | @Setter
17 | @ToString
18 | @Accessors(chain = true)
19 | public class GetFileReqDTO extends ListFileReqDTO {
20 |
21 | public GetFileReqDTO(String path, Boolean refresh) {
22 | super(path, refresh);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/req/alist/ListFileReqDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.req.alist;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import lombok.ToString;
6 | import lombok.experimental.Accessors;
7 |
8 | /**
9 | * @author zhaoqiang
10 | * @version 1.0
11 | * @date 2024-12-23 16:29
12 | */
13 | @Getter
14 | @Setter
15 | @ToString
16 | @Accessors(chain = true)
17 | public class ListFileReqDTO {
18 |
19 | /**
20 | * 页数
21 | */
22 | private Integer page = 1;
23 | /**
24 | * 密码
25 | */
26 | private String password = "";
27 | /**
28 | * 路径
29 | */
30 | private String path;
31 | /**
32 | * 每页数目
33 | */
34 | private Integer perPage = 0;
35 | /**
36 | * 是否强制刷新
37 | */
38 | private Boolean refresh = false;
39 |
40 | public ListFileReqDTO(String path, Boolean refresh) {
41 | this.path = path;
42 | this.refresh = refresh;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/req/alist/MoveFileReqDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.req.alist;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 | import lombok.experimental.Accessors;
8 |
9 | import java.util.Set;
10 |
11 | /**
12 | * 移动文件req
13 | *
14 | * @author zhaoqiang
15 | * @version 1.0
16 | * @date 2024-12-25 17:52
17 | */
18 | @Getter
19 | @Setter
20 | @ToString
21 | @Accessors(chain = true)
22 | public class MoveFileReqDTO {
23 |
24 | /**
25 | * 源文件夹
26 | */
27 | @JsonProperty("src_dir")
28 | private String srcDir;
29 |
30 | /**
31 | * 目标文件夹
32 | */
33 | @JsonProperty("dst_dir")
34 | private String dstDir;
35 |
36 | /**
37 | * 文件名
38 | */
39 | private Set names;
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/req/alist/RenameFileReqDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.req.alist;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Getter;
6 | import lombok.NoArgsConstructor;
7 | import lombok.Setter;
8 | import lombok.ToString;
9 |
10 | import java.util.List;
11 |
12 | /**
13 | * @author zhaoqiang
14 | * @version 1.0
15 | * @date 2025-3-24 10:47
16 | */
17 | @Getter
18 | @Setter
19 | @ToString
20 | @AllArgsConstructor
21 | @NoArgsConstructor
22 | public class RenameFileReqDTO {
23 |
24 | /**
25 | * 目录
26 | */
27 | @JsonProperty("src_dir")
28 | private String srcDir;
29 |
30 | /**
31 | * 重命名文件列表
32 | */
33 | @JsonProperty("rename_objects")
34 | private List renameFileList;
35 |
36 | public RenameFileReqDTO(String srcDir, String srcName, String newName) {
37 | this.srcDir = srcDir;
38 | this.renameFileList = List.of(new RenameFile(srcName, newName));
39 | }
40 |
41 | @Getter
42 | @Setter
43 | @AllArgsConstructor
44 | public static class RenameFile {
45 |
46 | /**
47 | * 源名称
48 | */
49 | @JsonProperty("src_name")
50 | private String srcName;
51 |
52 | /**
53 | * 新名称
54 | */
55 | @JsonProperty("new_name")
56 | private String newName;
57 |
58 | @Override
59 | public String toString() {
60 | return srcName + "->" + newName;
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/req/ttm/TtmReqDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.req.ttm;
2 |
3 | import com.zq.media.tools.enums.TtmAction;
4 | import com.zq.media.tools.enums.TtmScopeName;
5 | import lombok.AllArgsConstructor;
6 | import lombok.Getter;
7 | import lombok.Setter;
8 | import lombok.ToString;
9 | import lombok.experimental.Accessors;
10 |
11 | import java.util.List;
12 | import java.util.Map;
13 |
14 | /**
15 | * ttm 请求
16 | *
17 | * @author zhaoqiang
18 | * @version 1.0
19 | * @date 2024-12-23 17:17
20 | */
21 | @Getter
22 | @Setter
23 | @ToString
24 | @Accessors(chain = true)
25 | public class TtmReqDTO {
26 |
27 | /**
28 | * The name of the action to trigger
29 | */
30 | private TtmAction action;
31 |
32 | /**
33 | * The scope for the action. This defines on which entries the action should be applied.
34 | * The optional parameter args can be used to fine-tune the scope (not available on all scope values).
35 | * Valid scope values depend on the action you trigger
36 | */
37 | private Scope scope;
38 |
39 | /**
40 | * Any extra arguments you may pass to the actions (optional - used by some actions)
41 | */
42 | private Map args;
43 |
44 | @Getter
45 | @Setter
46 | @ToString
47 | @AllArgsConstructor
48 | public static class Scope {
49 |
50 | private TtmScopeName name;
51 |
52 | private List args;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/resp/alist/GetFileRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.resp.alist;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 |
8 | /**
9 | * 获取文件信息
10 | *
11 | * @author zhaoqiang
12 | * @version 1.0
13 | * @date 2025-1-8 10:50
14 | */
15 | @Getter
16 | @Setter
17 | @ToString
18 | public class GetFileRespDTO {
19 |
20 | /**
21 | * 创建时间
22 | */
23 | private String created;
24 |
25 | @JsonProperty("hash_info")
26 | private HashInfo hashInfo;
27 |
28 | private String header;
29 | /**
30 | * 是否是文件夹
31 | */
32 | @JsonProperty("is_dir")
33 | private Boolean isDir;
34 | /**
35 | * 修改时间
36 | */
37 | private String modified;
38 | /**
39 | * 文件名
40 | */
41 | private String name;
42 |
43 | private String provider;
44 | /**
45 | * 原始url
46 | */
47 | private String rawurl;
48 | /**
49 | * 说明
50 | */
51 | private String readme;
52 |
53 | private GetFileRespDTO related;
54 | /**
55 | * 签名
56 | */
57 | private String sign;
58 | /**
59 | * 大小
60 | */
61 | private Long size;
62 | /**
63 | * 缩略图
64 | */
65 | private String thumb;
66 | /**
67 | * 类型
68 | */
69 | private Long type;
70 |
71 | @Getter
72 | @Setter
73 | @ToString
74 | public static class HashInfo {
75 |
76 | /**
77 | * SHA1
78 | */
79 | private String sha1;
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/resp/alist/TaskRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.resp.alist;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import lombok.ToString;
6 |
7 | /**
8 | * 任务返回
9 | *
10 | * @author zhaoqiang
11 | * @version 1.0
12 | * @date 2025/1/1 16:52
13 | */
14 | @Getter
15 | @Setter
16 | @ToString
17 | public class TaskRespDTO {
18 |
19 | /**
20 | * 错误信息
21 | */
22 | private String error;
23 | /**
24 | * id
25 | */
26 | private String id;
27 | /**
28 | * 任务名
29 | */
30 | private String name;
31 | /**
32 | * 进度
33 | */
34 | private Long progress;
35 | /**
36 | * 任务完成状态
37 | */
38 | private String state;
39 |
40 | private String status;
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/resp/alist/listFileRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.resp.alist;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 |
8 | import java.util.List;
9 |
10 | /**
11 | * @author zhaoqiang
12 | * @version 1.0
13 | * @date 2024-12-23 16:31
14 | */
15 | @Getter
16 | @Setter
17 | @ToString
18 | public class listFileRespDTO {
19 |
20 | /**
21 | * 内容
22 | */
23 | private List content;
24 | private String header;
25 | private String provider;
26 | /**
27 | * 说明
28 | */
29 | private String readme;
30 | /**
31 | * 总数
32 | */
33 | private Long total;
34 | /**
35 | * 是否可写入
36 | */
37 | private Boolean write;
38 |
39 | @Getter
40 | @Setter
41 | @ToString
42 | public static class Content {
43 | /**
44 | * 创建时间
45 | */
46 | private String created;
47 |
48 | /**
49 | * 哈希信息
50 | */
51 | @JsonProperty("hash_info")
52 | private HashInfo hashInfo;
53 | /**
54 | * 是否是文件夹
55 | */
56 | @JsonProperty("is_dir")
57 | private Boolean isDir;
58 | /**
59 | * 修改时间
60 | */
61 | private String modified;
62 | /**
63 | * 文件名
64 | */
65 | private String name;
66 | /**
67 | * 签名
68 | */
69 | private String sign;
70 | /**
71 | * 大小
72 | */
73 | private Long size;
74 | /**
75 | * 缩略图
76 | */
77 | private String thumb;
78 | /**
79 | * 类型
80 | */
81 | private Long type;
82 |
83 | @Getter
84 | @Setter
85 | @ToString
86 | public static class HashInfo {
87 |
88 | /**
89 | * SHA1
90 | */
91 | private String sha1;
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/resp/driver115/BehaviorDetailRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.resp.driver115;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import com.zq.media.tools.enums.FileCategory;
5 | import lombok.Getter;
6 | import lombok.Setter;
7 | import lombok.ToString;
8 |
9 | /**
10 | * 行为详情
11 | *
12 | * @author zhaoqiang
13 | * @version 1.0
14 | * @date 2025-3-31 13:56
15 | */
16 | @Getter
17 | @Setter
18 | @ToString
19 | public class BehaviorDetailRespDTO {
20 |
21 | /**
22 | * 条目 ID
23 | */
24 | private String id;
25 |
26 | /**
27 | * 文件或目录 ID
28 | */
29 | @JsonProperty("file_id")
30 | private String fileId;
31 |
32 | /**
33 | * 父目录 ID
34 | */
35 | @JsonProperty("parent_id")
36 | private String parentId;
37 |
38 | /**
39 | * 选取代码
40 | */
41 | @JsonProperty("pick_code")
42 | private String pickCode;
43 |
44 | /**
45 | * SHA1
46 | */
47 | private String sha1;
48 |
49 | /**
50 | * 文件或目录名称
51 | */
52 | @JsonProperty("file_name")
53 | private String fileName;
54 |
55 | /**
56 | * 类别(0 表示目录,1 表示文件)
57 | */
58 | @JsonProperty("file_category")
59 | private FileCategory fileCategory;
60 |
61 | /**
62 | * 原文件或目录名称
63 | */
64 | @JsonProperty("old_file_name")
65 | private String oldFileName;
66 |
67 | /**
68 | * 文件扩展名
69 | */
70 | @JsonProperty("ico")
71 | private String ext;
72 |
73 | /**
74 | * 更新时间
75 | */
76 | @JsonProperty("update_time")
77 | private Long updateTime;
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/resp/driver115/BehaviorDetailsRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.resp.driver115;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import lombok.ToString;
6 |
7 | import java.util.List;
8 |
9 | /**
10 | * @author zhaoqiang
11 | * @version 1.0
12 | * @date 2025-3-31 10:24
13 | */
14 | @Getter
15 | @Setter
16 | @ToString
17 | public class BehaviorDetailsRespDTO {
18 |
19 | /**
20 | * 请求状态
21 | */
22 | private Boolean state;
23 |
24 | /**
25 | * 响应码
26 | */
27 | private Integer code;
28 |
29 | /**
30 | * 响应消息
31 | */
32 | private String msg;
33 |
34 | private BehaviorDetailsDataDTO data;
35 |
36 | @Getter
37 | @Setter
38 | @ToString
39 | public static class BehaviorDetailsDataDTO {
40 |
41 | /**
42 | * 计数
43 | */
44 | private String count;
45 |
46 | /**
47 | * 是否存在下一页
48 | */
49 | private Boolean nextPage;
50 |
51 | private List list;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/resp/driver115/FileListRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.resp.driver115;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 |
8 | import java.util.List;
9 |
10 | /**
11 | * 接口返回的文件列表
12 | *
13 | * @author zhaoqiang
14 | * @version 1.0
15 | * @date 2024-11-12 14:08
16 | */
17 | @Getter
18 | @Setter
19 | @ToString
20 | public class FileListRespDTO {
21 |
22 | /**
23 | * 文件数据列表
24 | */
25 | private List data;
26 |
27 | /**
28 | * 路径列表,用于表示完整路径
29 | */
30 | private List path;
31 |
32 | /**
33 | * FileDataDTO 表示单个文件的数据。
34 | */
35 | @Getter
36 | @Setter
37 | @ToString
38 | public static class FileDataDTO {
39 |
40 | /**
41 | * 文件 ID
42 | */
43 | @JsonProperty("fid")
44 | private String fileId;
45 |
46 | /**
47 | * 目父录 ID,本身为目录,则为目录id
48 | */
49 | @JsonProperty("cid")
50 | private String catalogId;
51 |
52 | /**
53 | * 父id,只针对于目录
54 | */
55 | @JsonProperty("pid")
56 | private String parentId;
57 |
58 | /**
59 | * 文件名称
60 | */
61 | @JsonProperty("n")
62 | private String fileName;
63 |
64 | /**
65 | * 选取代码
66 | */
67 | @JsonProperty("pc")
68 | private String pickCode;
69 |
70 | /**
71 | * sha1
72 | */
73 | @JsonProperty("sha")
74 | private String sha1;
75 |
76 | /**
77 | * 文件扩展名
78 | */
79 | @JsonProperty("ico")
80 | private String ext;
81 | }
82 |
83 | /**
84 | * PathDTO 表示路径中的单个路径节点。
85 | */
86 | @Getter
87 | @Setter
88 | @ToString
89 | public static class PathDTO {
90 |
91 |
92 | /**
93 | * 目录 ID
94 | */
95 | private String cid;
96 |
97 | /**
98 | * 目录名
99 | */
100 | private String name;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/resp/driver115/GetDownloadUrlRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.resp.driver115;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import lombok.ToString;
5 | import lombok.Getter;
6 | import lombok.Setter;
7 |
8 | /**
9 | * @author zhaoqiang
10 | * @version 1.0
11 | * @date 2024-11-13 9:58
12 | */
13 | @Getter
14 | @Setter
15 | @ToString
16 | public class GetDownloadUrlRespDTO {
17 |
18 | /**
19 | * 请求状态
20 | */
21 | private boolean state;
22 |
23 | /**
24 | * 响应码
25 | */
26 | @JsonProperty("msg_code")
27 | private int msgCode;
28 |
29 | /**
30 | * 响应消息
31 | */
32 | private String msg;
33 |
34 | /**
35 | * 文件名
36 | */
37 |
38 | @JsonProperty("file_name")
39 | private String fileName;
40 |
41 | /**
42 | * 文件 URL
43 | */
44 | @JsonProperty("file_url")
45 | private String fileUrl;
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/resp/driver115/GetPathRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.resp.driver115;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 |
8 | import java.util.List;
9 |
10 | /**
11 | * 返回的文件或目录路径信息
12 | *
13 | * @author zhaoqiang
14 | * @version 1.0
15 | * @date 2024-11-12 14:07
16 | */
17 | @Getter
18 | @Setter
19 | @ToString
20 | public class GetPathRespDTO {
21 |
22 | /**
23 | * 文件或目录名称
24 | */
25 | @JsonProperty("file_name")
26 | private String fileName;
27 |
28 |
29 | /**
30 | * 路径列表,用于表示完整路径
31 | */
32 | private List paths;
33 |
34 | /**
35 | * PathDTO 表示路径中的单个路径节点。
36 | */
37 | @Getter
38 | @Setter
39 | @ToString
40 | public static class PathDTO {
41 |
42 |
43 | /**
44 | * 路径节点的文件或目录 ID
45 | */
46 | @JsonProperty("file_id")
47 | private String fileId;
48 |
49 | /**
50 | * 路径节点的名称
51 | */
52 | @JsonProperty("file_name")
53 | private String fileName;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/resp/driver115/LifeListRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.resp.driver115;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import com.zq.media.tools.enums.BehaviorType;
5 | import lombok.Getter;
6 | import lombok.Setter;
7 | import lombok.ToString;
8 |
9 | import java.util.List;
10 |
11 | /**
12 | * 表示 life_list 接口的响应结构
13 | *
14 | * @author zhaoqiang
15 | * @version 1.0
16 | * @date 2024-11-12 14:07
17 | */
18 | @Getter
19 | @Setter
20 | @ToString
21 | public class LifeListRespDTO {
22 |
23 | /**
24 | * 请求状态
25 | */
26 | private Boolean state;
27 |
28 | /**
29 | * 响应码
30 | */
31 | private Integer code;
32 |
33 | /**
34 | * 响应消息
35 | */
36 | private String message;
37 |
38 | /**
39 | * 包含实际事件列表数据的对象
40 | */
41 | private LifeListDataDTO data;
42 |
43 | /**
44 | * LifeListDataDTO 表示 life_list 数据字段的结构。
45 | */
46 | @Getter
47 | @Setter
48 | @ToString
49 | public static class LifeListDataDTO {
50 |
51 | /**
52 | * 事件数量
53 | */
54 | private Integer count;
55 |
56 | /**
57 | * 事件行为列表
58 | */
59 | private List list;
60 |
61 | /**
62 | * 上一条数据,主要用于分页请求
63 | */
64 | @JsonProperty("last_data")
65 | private String lastData;
66 | }
67 |
68 | /**
69 | * BehaviorDTO 表示单个行为事件的结构。
70 | */
71 | @Getter
72 | @Setter
73 | @ToString
74 | public static class BehaviorDTO {
75 |
76 | /**
77 | * 总条目数
78 | */
79 | private Integer total;
80 |
81 | /**
82 | * 更新时间
83 | */
84 | @JsonProperty("update_time")
85 | private Long updateTime;
86 |
87 | /**
88 | * 行为类型
89 | */
90 | @JsonProperty("behavior_type")
91 | private BehaviorType behaviorType;
92 |
93 | /**
94 | * 日期
95 | */
96 | private String date;
97 |
98 | /**
99 | * 行为条目列表
100 | */
101 | private List items;
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/resp/emby/FavoriteItemsDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.resp.emby;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import com.zq.media.tools.enums.EmbyMediaType;
5 | import lombok.Data;
6 |
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | /**
11 | * 最爱item
12 | *
13 | * @author zhaoqiang
14 | * @since 1.0.0 2025-5-26
15 | */
16 | @Data
17 | public class FavoriteItemsDTO {
18 |
19 | @JsonProperty("Items")
20 | private List items;
21 |
22 | @JsonProperty("TotalRecordCount")
23 | private Integer totalRecordCount;
24 |
25 | @Data
26 | public static class ItemDTO {
27 | /**
28 | * 节目名称
29 | */
30 | @JsonProperty("Name")
31 | private String name;
32 |
33 | /**
34 | * 服务器唯一ID
35 | */
36 | @JsonProperty("ServerId")
37 | private String serverId;
38 |
39 | /**
40 | * 节目唯一ID
41 | */
42 | @JsonProperty("Id")
43 | private String id;
44 |
45 | /**
46 | * 播放时长(单位:Ticks)
47 | */
48 | @JsonProperty("RunTimeTicks")
49 | private Long runTimeTicks;
50 |
51 | /**
52 | * 是否为文件夹(系列)
53 | */
54 | @JsonProperty("IsFolder")
55 | private Boolean isFolder;
56 |
57 | /**
58 | * 媒体类型
59 | */
60 | @JsonProperty("Type")
61 | private EmbyMediaType type;
62 |
63 | /**
64 | * 用户播放数据
65 | */
66 | @JsonProperty("UserData")
67 | private UserData userData;
68 |
69 | /**
70 | * 播出日期(如每周几播出)
71 | */
72 | @JsonProperty("AirDays")
73 | private List airDays;
74 |
75 | /**
76 | * 图片标签(主图、Logo 等)
77 | */
78 | @JsonProperty("ImageTags")
79 | private Map imageTags;
80 |
81 | /**
82 | * 背景图标签
83 | */
84 | @JsonProperty("BackdropImageTags")
85 | private List backdropImageTags;
86 |
87 | }
88 |
89 | @Data
90 | public static class UserData {
91 |
92 | /**
93 | * 未播放的项目数量
94 | */
95 | @JsonProperty("UnplayedItemCount")
96 | private Integer unplayedItemCount;
97 |
98 | /**
99 | * 当前播放进度(单位:Ticks)
100 | */
101 | @JsonProperty("PlaybackPositionTicks")
102 | private Long playbackPositionTicks;
103 |
104 | /**
105 | * 播放次数
106 | */
107 | @JsonProperty("PlayCount")
108 | private Integer playCount;
109 |
110 | /**
111 | * 是否收藏
112 | */
113 | @JsonProperty("IsFavorite")
114 | private Boolean isFavorite;
115 |
116 | /**
117 | * 是否已播放
118 | */
119 | @JsonProperty("Played")
120 | private Boolean played;
121 |
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/resp/emby/ItemRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.resp.emby;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import com.zq.media.tools.enums.EmbyMediaType;
5 | import lombok.Getter;
6 | import lombok.Setter;
7 | import lombok.ToString;
8 |
9 | /**
10 | * @author zhaoqiang
11 | * @version 1.0
12 | * @date 2025/4/26 12:08
13 | */
14 | @Getter
15 | @Setter
16 | @ToString
17 | public class ItemRespDTO {
18 |
19 | /**
20 | * 名称
21 | */
22 | @JsonProperty("Name")
23 | private String name;
24 |
25 | /**
26 | * 原始标题
27 | */
28 | @JsonProperty("OriginalTitle")
29 | private String originalTitle;
30 |
31 | /**
32 | * 服务器ID
33 | */
34 | @JsonProperty("ServerId")
35 | private String serverId;
36 |
37 | /**
38 | * ID
39 | */
40 | @JsonProperty("Id")
41 | private String id;
42 |
43 | /**
44 | * 创建日期
45 | */
46 | @JsonProperty("DateCreated")
47 | private String dateCreated;
48 |
49 | /**
50 | * 排序名称
51 | */
52 | @JsonProperty("SortName")
53 | private String sortName;
54 |
55 | /**
56 | * 首映日期
57 | */
58 | @JsonProperty("PremiereDate")
59 | private String premiereDate;
60 |
61 | /**
62 | * 外部链接
63 | */
64 | @JsonProperty("ExternalUrls")
65 | private ExternalUrl[] externalUrls;
66 |
67 | /**
68 | * 路径
69 | */
70 | @JsonProperty("Path")
71 | private String path;
72 |
73 | /**
74 | * 简介
75 | */
76 | @JsonProperty("Overview")
77 | private String overview;
78 |
79 | /**
80 | * 标签行
81 | */
82 | @JsonProperty("Taglines")
83 | private String[] taglines;
84 |
85 | /**
86 | * 类型
87 | */
88 | @JsonProperty("Genres")
89 | private String[] genres;
90 |
91 | /**
92 | * 社区评分
93 | */
94 | @JsonProperty("CommunityRating")
95 | private Double communityRating;
96 |
97 | /**
98 | * 运行时间(以毫秒为单位)
99 | */
100 | @JsonProperty("RunTimeTicks")
101 | private Long runTimeTicks;
102 |
103 | /**
104 | * 文件名
105 | */
106 | @JsonProperty("FileName")
107 | private String fileName;
108 |
109 | /**
110 | * 生产年份
111 | */
112 | @JsonProperty("ProductionYear")
113 | private Integer productionYear;
114 |
115 | /**
116 | * 远程预告片
117 | */
118 | @JsonProperty("RemoteTrailers")
119 | private String[] remoteTrailers;
120 |
121 | /**
122 | * 提供商ID
123 | */
124 | @JsonProperty("ProviderIds")
125 | private ProviderIds providerIds;
126 |
127 | /**
128 | * 是否为文件夹
129 | */
130 | @JsonProperty("IsFolder")
131 | private Boolean isFolder;
132 |
133 | /**
134 | * 父级ID
135 | */
136 | @JsonProperty("ParentId")
137 | private String parentId;
138 |
139 | /**
140 | * 类型
141 | */
142 | @JsonProperty("Type")
143 | private EmbyMediaType type;
144 |
145 | /**
146 | * 主图像标签
147 | */
148 | @JsonProperty("SeriesPrimaryImageTag")
149 | private String seriesPrimaryImageTag;
150 |
151 | /**
152 | * 工作室
153 | */
154 | @JsonProperty("Studios")
155 | private Studio[] studios;
156 |
157 | /**
158 | * 类型项
159 | */
160 | @JsonProperty("GenreItems")
161 | private GenreItem[] genreItems;
162 |
163 | /**
164 | * 标签项
165 | */
166 | @JsonProperty("TagItems")
167 | private TagItem[] tagItems;
168 |
169 | /**
170 | * 子项数量
171 | */
172 | @JsonProperty("ChildCount")
173 | private Integer childCount;
174 |
175 | /**
176 | * 状态
177 | */
178 | @JsonProperty("Status")
179 | private String status;
180 |
181 | /**
182 | * 播放日
183 | */
184 | @JsonProperty("AirDays")
185 | private String[] airDays;
186 |
187 | /**
188 | * 主图宽高比
189 | */
190 | @JsonProperty("PrimaryImageAspectRatio")
191 | private Double primaryImageAspectRatio;
192 |
193 | /**
194 | * 图像标签
195 | */
196 | @JsonProperty("ImageTags")
197 | private ImageTags imageTags;
198 |
199 | /**
200 | * 背景图像标签
201 | */
202 | @JsonProperty("BackdropImageTags")
203 | private String[] backdropImageTags;
204 |
205 | /**
206 | * 外部链接静态内部类
207 | */
208 | @Getter
209 | @Setter
210 | @ToString
211 | public static class ExternalUrl {
212 | /**
213 | * 名称
214 | */
215 | @JsonProperty("Name")
216 | private String name;
217 |
218 | /**
219 | * URL
220 | */
221 | @JsonProperty("Url")
222 | private String url;
223 | }
224 |
225 | /**
226 | * 提供商ID静态内部类
227 | */
228 | @Getter
229 | @Setter
230 | @ToString
231 | public static class ProviderIds {
232 | /**
233 | * TheTVDB ID
234 | */
235 | @JsonProperty("Tvdb")
236 | private String tvdb;
237 |
238 | /**
239 | * IMDb ID
240 | */
241 | @JsonProperty("Imdb")
242 | private String imdb;
243 |
244 | /**
245 | * TMDB ID
246 | */
247 | @JsonProperty("Tmdb")
248 | private String tmdb;
249 | }
250 |
251 | /**
252 | * 工作室静态内部类
253 | */
254 | @Getter
255 | @Setter
256 | @ToString
257 | public static class Studio {
258 | /**
259 | * 名称
260 | */
261 | @JsonProperty("Name")
262 | private String name;
263 |
264 | /**
265 | * ID
266 | */
267 | @JsonProperty("Id")
268 | private String id;
269 | }
270 |
271 | /**
272 | * 类型项静态内部类
273 | */
274 | @Getter
275 | @Setter
276 | @ToString
277 | public static class GenreItem {
278 | /**
279 | * 名称
280 | */
281 | @JsonProperty("Name")
282 | private String name;
283 |
284 | /**
285 | * ID
286 | */
287 | @JsonProperty("Id")
288 | private String id;
289 | }
290 |
291 | /**
292 | * 标签项静态内部类
293 | */
294 | @Getter
295 | @Setter
296 | @ToString
297 | public static class TagItem {
298 | /**
299 | * 名称
300 | */
301 | @JsonProperty("Name")
302 | private String name;
303 |
304 | /**
305 | * ID
306 | */
307 | @JsonProperty("Id")
308 | private String id;
309 | }
310 |
311 | /**
312 | * 图像标签静态内部类
313 | */
314 | @Getter
315 | @Setter
316 | @ToString
317 | public static class ImageTags {
318 | /**
319 | * 主图标签
320 | */
321 | @JsonProperty("Primary")
322 | private String primary;
323 |
324 | /**
325 | * 背景标签
326 | */
327 | @JsonProperty("Banner")
328 | private String banner;
329 |
330 | /**
331 | * Logo标签
332 | */
333 | @JsonProperty("Logo")
334 | private String logo;
335 | }
336 |
337 | }
338 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/dto/resp/emby/MediaPlaybackInfoRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.dto.resp.emby;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 |
8 | import java.util.List;
9 |
10 | /**
11 | * 媒体播放信息
12 | *
13 | * @author zhaoqiang
14 | * @version 1.0
15 | * @date 2025-4-24 14:17
16 | */
17 | @Getter
18 | @Setter
19 | @ToString
20 | public class MediaPlaybackInfoRespDTO {
21 |
22 | /**
23 | * 媒体源列表
24 | */
25 | @JsonProperty("MediaSources")
26 | private List mediaSources;
27 |
28 | /**
29 | * 播放会话 ID
30 | */
31 | @JsonProperty("PlaySessionId")
32 | private String playSessionId;
33 |
34 | /**
35 | * 媒体源实体类
36 | */
37 | @Getter
38 | @Setter
39 | @ToString
40 | public static class MediaSource {
41 |
42 | /**
43 | * 章节列表
44 | */
45 | @JsonProperty("Chapters")
46 | private List chapters;
47 |
48 | /**
49 | * 协议类型
50 | */
51 | @JsonProperty("Protocol")
52 | private String protocol;
53 |
54 | /**
55 | * 媒体源 ID
56 | */
57 | @JsonProperty("Id")
58 | private String id;
59 |
60 | /**
61 | * 媒体路径
62 | */
63 | @JsonProperty("Path")
64 | private String path;
65 |
66 | /**
67 | * 媒体类型
68 | */
69 | @JsonProperty("Type")
70 | private String type;
71 |
72 | /**
73 | * 容器格式
74 | */
75 | @JsonProperty("Container")
76 | private String container;
77 |
78 | /**
79 | * 文件大小
80 | */
81 | @JsonProperty("Size")
82 | private Long size;
83 |
84 | /**
85 | * 媒体名称
86 | */
87 | @JsonProperty("Name")
88 | private String name;
89 |
90 | /**
91 | * 是否为远程媒体
92 | */
93 | @JsonProperty("IsRemote")
94 | private boolean isRemote;
95 |
96 | /**
97 | * 是否包含混合协议
98 | */
99 | @JsonProperty("HasMixedProtocols")
100 | private boolean hasMixedProtocols;
101 |
102 | /**
103 | * 总时长(以 ticks 为单位)
104 | */
105 | @JsonProperty("RunTimeTicks")
106 | private Long runTimeTicks;
107 |
108 | /**
109 | * 是否支持转码
110 | */
111 | @JsonProperty("SupportsTranscoding")
112 | private boolean supportsTranscoding;
113 |
114 | /**
115 | * 是否支持直接流媒体
116 | */
117 | @JsonProperty("SupportsDirectStream")
118 | private boolean supportsDirectStream;
119 |
120 | /**
121 | * 是否支持直接播放
122 | */
123 | @JsonProperty("SupportsDirectPlay")
124 | private boolean supportsDirectPlay;
125 |
126 | /**
127 | * 是否为无限流
128 | */
129 | @JsonProperty("IsInfiniteStream")
130 | private boolean isInfiniteStream;
131 |
132 | /**
133 | * 是否需要打开操作
134 | */
135 | @JsonProperty("RequiresOpening")
136 | private boolean requiresOpening;
137 |
138 | /**
139 | * 是否需要关闭操作
140 | */
141 | @JsonProperty("RequiresClosing")
142 | private boolean requiresClosing;
143 |
144 | /**
145 | * 是否需要循环
146 | */
147 | @JsonProperty("RequiresLooping")
148 | private boolean requiresLooping;
149 |
150 | /**
151 | * 是否支持探测
152 | */
153 | @JsonProperty("SupportsProbing")
154 | private boolean supportsProbing;
155 |
156 | /**
157 | * 媒体流列表
158 | */
159 | @JsonProperty("MediaStreams")
160 | private List mediaStreams;
161 |
162 | /**
163 | * 格式列表
164 | */
165 | @JsonProperty("Formats")
166 | private List formats;
167 |
168 | /**
169 | * 比特率
170 | */
171 | @JsonProperty("Bitrate")
172 | private Integer bitrate;
173 |
174 | /**
175 | * 必需的 HTTP 头部信息
176 | */
177 | @JsonProperty("RequiredHttpHeaders")
178 | private Object requiredHttpHeaders;
179 |
180 | /**
181 | * 直接流媒体 URL
182 | */
183 | @JsonProperty("DirectStreamUrl")
184 | private String directStreamUrl;
185 |
186 | /**
187 | * 是否在直接流媒体 URL 中添加 API 密钥
188 | */
189 | @JsonProperty("AddApiKeyToDirectStreamUrl")
190 | private boolean addApiKeyToDirectStreamUrl;
191 |
192 | /**
193 | * 转码 URL
194 | */
195 | @JsonProperty("TranscodingUrl")
196 | private String transcodingUrl;
197 |
198 | /**
199 | * 转码子协议
200 | */
201 | @JsonProperty("TranscodingSubProtocol")
202 | private String transcodingSubProtocol;
203 |
204 | /**
205 | * 转码容器格式
206 | */
207 | @JsonProperty("TranscodingContainer")
208 | private String transcodingContainer;
209 |
210 | /**
211 | * 是否以原始帧率读取
212 | */
213 | @JsonProperty("ReadAtNativeFramerate")
214 | private boolean readAtNativeFramerate;
215 |
216 | /**
217 | * 默认音频流索引
218 | */
219 | @JsonProperty("DefaultAudioStreamIndex")
220 | private Integer defaultAudioStreamIndex;
221 |
222 | /**
223 | * 媒体项 ID
224 | */
225 | @JsonProperty("ItemId")
226 | private String itemId;
227 |
228 | }
229 |
230 | @Getter
231 | @Setter
232 | @ToString
233 | public static class Chapter {
234 |
235 | /**
236 | * 开始位置(以 ticks 为单位)
237 | */
238 | @JsonProperty("StartPositionTicks")
239 | private Long startPositionTicks;
240 |
241 | /**
242 | * 章节名称
243 | */
244 | @JsonProperty("Name")
245 | private String name;
246 |
247 | /**
248 | * 标记类型
249 | */
250 | @JsonProperty("MarkerType")
251 | private String markerType;
252 |
253 | /**
254 | * 章节索引
255 | */
256 | @JsonProperty("ChapterIndex")
257 | private Integer chapterIndex;
258 | }
259 |
260 | @Getter
261 | @Setter
262 | @ToString
263 | public static class MediaStream {
264 |
265 | /**
266 | * 编解码器
267 | */
268 | @JsonProperty("Codec")
269 | private String codec;
270 |
271 | /**
272 | * 编解码器标签
273 | */
274 | @JsonProperty("CodecTag")
275 | private String codecTag;
276 |
277 | /**
278 | * 语言
279 | */
280 | @JsonProperty("Language")
281 | private String language;
282 |
283 | /**
284 | * 时间基准
285 | */
286 | @JsonProperty("TimeBase")
287 | private String timeBase;
288 |
289 | /**
290 | * 视频范围
291 | */
292 | @JsonProperty("VideoRange")
293 | private String videoRange;
294 |
295 | /**
296 | * 显示标题
297 | */
298 | @JsonProperty("DisplayTitle")
299 | private String displayTitle;
300 |
301 | /**
302 | * 是否为隔行扫描
303 | */
304 | @JsonProperty("IsInterlaced")
305 | private boolean isInterlaced;
306 |
307 | /**
308 | * 比特率
309 | */
310 | @JsonProperty("BitRate")
311 | private Integer bitRate;
312 |
313 | /**
314 | * 比特深度
315 | */
316 | @JsonProperty("BitDepth")
317 | private Integer bitDepth;
318 |
319 | /**
320 | * 参考帧数
321 | */
322 | @JsonProperty("RefFrames")
323 | private Integer refFrames;
324 |
325 | /**
326 | * 是否为默认流
327 | */
328 | @JsonProperty("IsDefault")
329 | private boolean isDefault;
330 |
331 | /**
332 | * 是否为强制流
333 | */
334 | @JsonProperty("IsForced")
335 | private boolean isForced;
336 |
337 | /**
338 | * 是否为听力障碍辅助流
339 | */
340 | @JsonProperty("IsHearingImpaired")
341 | private boolean isHearingImpaired;
342 |
343 | /**
344 | * 高度(视频流)
345 | */
346 | @JsonProperty("Height")
347 | private Integer height;
348 |
349 | /**
350 | * 宽度(视频流)
351 | */
352 | @JsonProperty("Width")
353 | private Integer width;
354 |
355 | /**
356 | * 平均帧率
357 | */
358 | @JsonProperty("AverageFrameRate")
359 | private Double averageFrameRate;
360 |
361 | /**
362 | * 实际帧率
363 | */
364 | @JsonProperty("RealFrameRate")
365 | private Double realFrameRate;
366 |
367 | /**
368 | * 配置文件
369 | */
370 | @JsonProperty("Profile")
371 | private String profile;
372 |
373 | /**
374 | * 流类型
375 | */
376 | @JsonProperty("Type")
377 | private String type;
378 |
379 | /**
380 | * 宽高比
381 | */
382 | @JsonProperty("AspectRatio")
383 | private String aspectRatio;
384 |
385 | /**
386 | * 流索引
387 | */
388 | @JsonProperty("Index")
389 | private Integer index;
390 |
391 | /**
392 | * 是否为外部流
393 | */
394 | @JsonProperty("IsExternal")
395 | private boolean isExternal;
396 |
397 | /**
398 | * 是否为文本字幕流
399 | */
400 | @JsonProperty("IsTextSubtitleStream")
401 | private boolean isTextSubtitleStream;
402 |
403 | /**
404 | * 是否支持外部流
405 | */
406 | @JsonProperty("SupportsExternalStream")
407 | private boolean supportsExternalStream;
408 |
409 | /**
410 | * 协议类型
411 | */
412 | @JsonProperty("Protocol")
413 | private String protocol;
414 |
415 | /**
416 | * 像素格式
417 | */
418 | @JsonProperty("PixelFormat")
419 | private String pixelFormat;
420 |
421 | /**
422 | * 编码级别
423 | */
424 | @JsonProperty("Level")
425 | private Integer level;
426 |
427 | /**
428 | * 是否为变形宽高比
429 | */
430 | @JsonProperty("IsAnamorphic")
431 | private boolean isAnamorphic;
432 |
433 | /**
434 | * 扩展视频类型
435 | */
436 | @JsonProperty("ExtendedVideoType")
437 | private String extendedVideoType;
438 |
439 | /**
440 | * 扩展视频子类型
441 | */
442 | @JsonProperty("ExtendedVideoSubType")
443 | private String extendedVideoSubType;
444 |
445 | /**
446 | * 扩展视频子类型描述
447 | */
448 | @JsonProperty("ExtendedVideoSubTypeDescription")
449 | private String extendedVideoSubTypeDescription;
450 |
451 | /**
452 | * 附件大小
453 | */
454 | @JsonProperty("AttachmentSize")
455 | private Integer attachmentSize;
456 | }
457 | }
458 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/entity/Media115.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.entity;
2 |
3 | import com.baomidou.mybatisplus.annotation.FieldFill;
4 | import com.baomidou.mybatisplus.annotation.IdType;
5 | import com.baomidou.mybatisplus.annotation.TableField;
6 | import com.baomidou.mybatisplus.annotation.TableId;
7 | import com.baomidou.mybatisplus.annotation.TableName;
8 | import lombok.Getter;
9 | import lombok.Setter;
10 | import lombok.ToString;
11 | import lombok.experimental.Accessors;
12 |
13 | import java.time.LocalDateTime;
14 |
15 | /**
16 | * 115媒体库
17 | *
18 | * @author zhaoqiang
19 | * @version 1.0
20 | * @date 2024-11-15 11:41
21 | */
22 | @Getter
23 | @Setter
24 | @ToString
25 | @Accessors(chain = true)
26 | @TableName("MEDIA_115")
27 | public class Media115 {
28 |
29 | @TableId(value = "id", type = IdType.AUTO)
30 | private Long id;
31 |
32 | /**
33 | * 文件id
34 | */
35 | private String fileId;
36 |
37 | /**
38 | * 父文件id
39 | */
40 | private String parentId;
41 |
42 | /**
43 | * 路径(包含文件名)
44 | */
45 | private String path;
46 |
47 | /**
48 | * 文件名
49 | */
50 | private String fileName;
51 |
52 | /**
53 | * SHA1
54 | */
55 | private String sha1;
56 |
57 | /**
58 | * 文件扩展名
59 | */
60 | private String ext;
61 |
62 | @TableField(fill = FieldFill.INSERT)
63 | private LocalDateTime createTime;
64 |
65 | @TableField(fill = FieldFill.UPDATE)
66 | private LocalDateTime updateTime;
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/enums/BehaviorType.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.enums;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonValue;
5 | import com.zq.common.enums.IEnum;
6 | import lombok.AllArgsConstructor;
7 | import lombok.Getter;
8 | import org.dromara.hutool.core.array.ArrayUtil;
9 |
10 | /**
11 | * 115生活操作类型
12 | *
13 | * @author zhaoqiang
14 | * @version 1.0
15 | * @date 2024-11-12 15:28
16 | */
17 | @Getter
18 | @AllArgsConstructor
19 | public enum BehaviorType implements IEnum {
20 |
21 | ACCOUNT_SECURITY("account_security", "账号安全"),
22 |
23 | /** 浏览文档 */
24 | BROWSE_DOCUMENT("browse_document", "浏览文档"),
25 |
26 | /** 浏览图片 */
27 | BROWSE_IMAGE("browse_image", "浏览图片"),
28 |
29 | /** 浏览音频 */
30 | BROWSE_AUDIO("browse_audio", "浏览音频"),
31 |
32 | /** 浏览视频 */
33 | BROWSE_VIDEO("browse_video", "浏览视频"),
34 |
35 | /** 新增目录 */
36 | NEW_FOLDER("new_folder", "新增目录"),
37 |
38 | /** 复制目录 */
39 | COPY_FOLDER("copy_folder", "复制目录"),
40 |
41 | /** 目录改名 */
42 | FOLDER_RENAME("folder_rename", "目录改名"),
43 |
44 | /** 目录设置标签 */
45 | FOLDER_LABEL("folder_label", "目录设置标签"),
46 |
47 | /** 设置星标 */
48 | STAR_FILE("star_file", "设置星标"),
49 |
50 | /** 移动文件或目录(不包括图片) */
51 | MOVE_FILE("move_file", "移动文件或目录"),
52 |
53 | /** 移动图片 */
54 | MOVE_IMAGE_FILE("move_image_file", "移动图片"),
55 |
56 | /** 删除文件或目录 */
57 | DELETE_FILE("delete_file", "删除文件或目录"),
58 |
59 | /** 上传文件 */
60 | UPLOAD_FILE("upload_file", "上传文件"),
61 |
62 | /** 上传图片 */
63 | UPLOAD_IMAGE_FILE("upload_image_file", "上传图片"),
64 |
65 | /** 接收文件 */
66 | RECEIVE_FILES("receive_files", "接收文件"),
67 |
68 | /** 文件改名(未实现) */
69 | RENAME_FILE("rename_file", "文件改名"),
70 |
71 | /** 复制文件(未实现) */
72 | COPY_FILE("copy_file", "复制文件");
73 |
74 | @JsonValue
75 | private final String code;
76 | private final String desc;
77 |
78 | @JsonCreator
79 | public static BehaviorType of(String code) {
80 | return ArrayUtil.firstMatch(behaviorType -> behaviorType.getCode().equals(code), values());
81 | }
82 |
83 | }
84 |
85 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/enums/EmbyEvent.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.enums;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonValue;
5 | import com.zq.common.enums.IEnum;
6 | import lombok.AllArgsConstructor;
7 | import lombok.Getter;
8 | import org.dromara.hutool.core.array.ArrayUtil;
9 |
10 | /**
11 | * emby事件
12 | *
13 | * @author zhaoqiang
14 | * @since V1.0.0 2025-4-14
15 | */
16 | @Getter
17 | @AllArgsConstructor
18 | public enum EmbyEvent implements IEnum {
19 |
20 | DEEP_DELETE("deep.delete", "媒体深度删除", EmbyNotifyType.SHENYI),
21 | FAVORITES_UPDATE("favorites.update", "追更模式更新", EmbyNotifyType.SHENYI),
22 | INTRO_SKIP_UPDATE("introskip.update", "片头片尾更新", EmbyNotifyType.SHENYI);
23 |
24 | @JsonValue
25 | private final String code;
26 | private final String desc;
27 | private final EmbyNotifyType notifyType;
28 |
29 | @JsonCreator
30 | public static EmbyEvent of(String code) {
31 | return ArrayUtil.firstMatch(embyEvent -> embyEvent.getCode().equals(code), values());
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/enums/EmbyMediaType.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.enums;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonValue;
5 | import com.zq.common.enums.IEnum;
6 | import lombok.AllArgsConstructor;
7 | import lombok.Getter;
8 | import org.dromara.hutool.core.array.ArrayUtil;
9 |
10 | /**
11 | * emby媒体类型
12 | *
13 | * @author zhaoqiang
14 | * @since V1.0.0 2025-4-14
15 | */
16 | @Getter
17 | @AllArgsConstructor
18 | public enum EmbyMediaType implements IEnum {
19 |
20 | SERIES("Series", "电视节目"),
21 | SEASON("Season", "季"),
22 | EPISODE("Episode", "剧集"),
23 | MOVIE("Movie", "电影");
24 |
25 | @JsonValue
26 | private final String code;
27 | private final String desc;
28 |
29 | @JsonCreator
30 | public static EmbyMediaType of(String code) {
31 | return ArrayUtil.firstMatch(mediaType -> mediaType.getCode().equals(code), values());
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/enums/EmbyNotifyType.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.enums;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 |
6 | /**
7 | * emby通知类型
8 | *
9 | * @author zhaoqiang
10 | * @version 1.0
11 | * @date 2025-4-15 9:42
12 | */
13 | @Getter
14 | @AllArgsConstructor
15 | public enum EmbyNotifyType {
16 |
17 | EMBY_DEFAULT("emby默认"),
18 | SHENYI("神医助手");
19 |
20 | private final String desc;
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/enums/FileCategory.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.enums;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonValue;
5 | import com.zq.common.enums.IEnum;
6 | import lombok.AllArgsConstructor;
7 | import lombok.Getter;
8 | import org.dromara.hutool.core.array.ArrayUtil;
9 |
10 | /**
11 | * 文件类型
12 | *
13 | * @author zhaoqiang
14 | * @version 1.0
15 | * @date 2024-11-15 11:44
16 | */
17 | @Getter
18 | @AllArgsConstructor
19 | public enum FileCategory implements IEnum {
20 |
21 | CATALOG(0, "目录"),
22 | FILE(1, "文件");
23 |
24 | @JsonValue
25 | private final Integer code;
26 | private final String desc;
27 |
28 | @JsonCreator
29 | public static FileCategory of(Integer code) {
30 | return ArrayUtil.firstMatch(fileCategory -> fileCategory.getCode().equals(code), values());
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/enums/TelegramBotCommand.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.enums;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 |
6 | /**
7 | * tg机器人命令
8 | *
9 | * @author zhaoqiang
10 | * @version 1.0
11 | * @date 2025-4-15 16:33
12 | */
13 | @Getter
14 | @AllArgsConstructor
15 | public enum TelegramBotCommand {
16 |
17 | START("start"),
18 | STOP("stop"),
19 | PIC("pic"),
20 | UNKNOWN("unknown");
21 |
22 | private final String command;
23 |
24 | public static TelegramBotCommand of(String command) {
25 | for (TelegramBotCommand value : values()) {
26 | if (("/" + value.getCommand()).equals(command)) {
27 | return value;
28 | }
29 | }
30 | return UNKNOWN;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/enums/TtmAction.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.enums;
2 |
3 | import com.fasterxml.jackson.annotation.JsonValue;
4 | import com.zq.common.enums.IEnum;
5 | import lombok.AllArgsConstructor;
6 | import lombok.Getter;
7 |
8 | /**
9 | * ttm触发操作
10 | *
11 | * @author zhaoqiang
12 | * @since V1.0.0 2024-12-23
13 | */
14 | @Getter
15 | @AllArgsConstructor
16 | public enum TtmAction implements IEnum {
17 |
18 | UPDATE("update", "刷新媒体库"),
19 | SCRAPE("scrape", "刮削媒体库");
20 |
21 | @JsonValue
22 | private final String code;
23 | private final String desc;
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/enums/TtmScopeName.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.enums;
2 |
3 | import com.fasterxml.jackson.annotation.JsonValue;
4 | import com.zq.common.enums.IEnum;
5 | import lombok.AllArgsConstructor;
6 | import lombok.Getter;
7 |
8 | /**
9 | * @author zhaoqiang
10 | * @version 1.0
11 | * @date 2024-12-23 17:26
12 | */
13 | @Getter
14 | @AllArgsConstructor
15 | public enum TtmScopeName implements IEnum {
16 |
17 | NEW("new", "未处理的媒体"),
18 | PATH("path", "指定路径的媒体"),
19 | SINGLE("single", "指定单个数据源的媒体"),
20 | DATASOURCE("dataSource", "指定数据源的媒体"),
21 | UN_SCRAPED("unscraped", "未刮削的媒体"),
22 | ALL("all", "所有媒体");
23 |
24 | @JsonValue
25 | private final String code;
26 | private final String desc;
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/feign/AlistClient.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.feign;
2 |
3 | import com.zq.common.domain.Result;
4 | import com.zq.media.tools.config.feign.FeignAlistConfig;
5 | import com.zq.media.tools.dto.req.alist.CopyFileReqDTO;
6 | import com.zq.media.tools.dto.req.alist.DeleteFileReqDTO;
7 | import com.zq.media.tools.dto.req.alist.GetFileReqDTO;
8 | import com.zq.media.tools.dto.req.alist.ListFileReqDTO;
9 | import com.zq.media.tools.dto.req.alist.MoveFileReqDTO;
10 | import com.zq.media.tools.dto.req.alist.RenameFileReqDTO;
11 | import com.zq.media.tools.dto.resp.alist.GetFileRespDTO;
12 | import com.zq.media.tools.dto.resp.alist.TaskRespDTO;
13 | import com.zq.media.tools.dto.resp.alist.listFileRespDTO;
14 | import org.springframework.cloud.openfeign.FeignClient;
15 | import org.springframework.web.bind.annotation.GetMapping;
16 | import org.springframework.web.bind.annotation.PostMapping;
17 | import org.springframework.web.bind.annotation.RequestBody;
18 |
19 | import java.util.List;
20 |
21 | /**
22 | * alist api
23 | *
24 | * @author zhaoqiang
25 | * @since V1.0.0 2024-12-23
26 | */
27 | @FeignClient(name = "alist", url = "${app.alist.url}", configuration = FeignAlistConfig.class)
28 | public interface AlistClient {
29 |
30 | // region 文件目录相关
31 | /**
32 | * 列出文件目录
33 | *
34 | * @param listFileReqDTO 列出文件目录req
35 | * @return {@link Result }<{@link listFileRespDTO }>
36 | */
37 | @PostMapping("/api/fs/list")
38 | Result listFile(@RequestBody ListFileReqDTO listFileReqDTO);
39 |
40 | /**
41 | * 获取文件信息
42 | *
43 | * @param getFileReqDTO 获取文件请求 DTO
44 | * @return {@link Result }<{@link GetFileRespDTO }>
45 | */
46 | @PostMapping("/api/fs/get")
47 | Result getFileInfo(@RequestBody GetFileReqDTO getFileReqDTO);
48 |
49 | /**
50 | * 移动文件
51 | *
52 | * @param moveFileReqDTO 移动文件req dto
53 | * @return {@link Result }
54 | */
55 | @PostMapping("/api/fs/move")
56 | Result moveFile(@RequestBody MoveFileReqDTO moveFileReqDTO);
57 |
58 | /**
59 | *
60 | * @return {@link Result }
61 | */
62 | @PostMapping("/api/fs/copy")
63 | Result copyFile(@RequestBody CopyFileReqDTO copyFileReqDTO);
64 |
65 | /**
66 | * 删除文件
67 | *
68 | * @param deleteFileReqDTO 删除文件req
69 | * @return {@link Result }
70 | */
71 | @PostMapping("/api/fs/remove")
72 | Result deleteFile(DeleteFileReqDTO deleteFileReqDTO);
73 |
74 | /**
75 | * 批量重命名文件
76 | *
77 | * @param renameFileReqDTO 将 file req dto 重命名为
78 | * @return {@link Result }
79 | */
80 | @PostMapping("/api/fs/batch_rename")
81 | Result renameFile(RenameFileReqDTO renameFileReqDTO);
82 | // endregion
83 |
84 | // region 任务相关
85 | /**
86 | * 查询已完成复制任务
87 | *
88 | * @return {@link Result }<{@link List }<{@link TaskRespDTO }>>
89 | */
90 | @GetMapping("/api/admin/task/copy/done")
91 | Result> listCopyDoneTask();
92 |
93 | /**
94 | * 查询未完成复制任务
95 | *
96 | * @return {@link Result }<{@link List }<{@link TaskRespDTO }>>
97 | */
98 | @GetMapping("/api/admin/task/copy/undone")
99 | Result> listCopyUndoneTask();
100 |
101 | /**
102 | * 清除已成功复制任务
103 | *
104 | * @return {@link Result }
105 | */
106 | @PostMapping("/api/admin/task/copy/clear_succeeded")
107 | Result clearCopySuccessTask();
108 | // endregion
109 |
110 | }
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/feign/Driver115Client.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.feign;
2 |
3 | import com.zq.media.tools.config.feign.Feign115Config;
4 | import com.zq.media.tools.dto.resp.driver115.BehaviorDetailsRespDTO;
5 | import com.zq.media.tools.dto.resp.driver115.FileListRespDTO;
6 | import com.zq.media.tools.dto.resp.driver115.GetDownloadUrlRespDTO;
7 | import com.zq.media.tools.dto.resp.driver115.GetPathRespDTO;
8 | import org.springframework.cloud.openfeign.FeignClient;
9 | import org.springframework.web.bind.annotation.GetMapping;
10 | import org.springframework.web.bind.annotation.RequestParam;
11 |
12 | /**
13 | * 用于与 115 API 进行通信的 Feign 客户端接口。
14 | *
15 | * @author zhaoqiang
16 | * @version 1.0
17 | * @date 2024-11-12 14:07
18 | */
19 | @FeignClient(name = "driver115Client", url = "https://webapi.115.com", configuration = Feign115Config.class)
20 | public interface Driver115Client {
21 |
22 | /**
23 | * 调用 get 接口获取文件或目录的完整路径信息。
24 | *
25 | * @param cid 文件或目录的 ID
26 | * @return 包含路径信息的响应对象
27 | */
28 | @GetMapping("/category/get")
29 | GetPathRespDTO getFilePath(@RequestParam("cid") String cid);
30 |
31 | /**
32 | * 调用 files 接口列出指定目录下的文件。
33 | *
34 | * @param cid 目录 ID
35 | * @param limit 最大返回文件数量
36 | * @return 包含文件列表的响应对象
37 | */
38 | @GetMapping("/files")
39 | FileListRespDTO listFiles(@RequestParam("cid") String cid, @RequestParam("limit") int limit);
40 |
41 | /**
42 | * 获取下载 URL
43 | *
44 | * @param pickCode 选取代码
45 | * @return {@link GetDownloadUrlRespDTO }
46 | */
47 | @GetMapping("/files/download")
48 | GetDownloadUrlRespDTO getDownloadUrl(@RequestParam("pickcode") String pickCode);
49 |
50 | /**
51 | * 获取行为详细信息
52 | *
53 | * @param offset 分页偏移量
54 | * @param limit 分页大小,最大值1000
55 | * @param type 类型
56 | * @param date 日期
57 | * @return {@link BehaviorDetailsRespDTO }
58 | */
59 | @GetMapping("/behavior/detail")
60 | BehaviorDetailsRespDTO getBehaviorDetails(@RequestParam("offset") int offset, @RequestParam("limit") int limit, @RequestParam("type") String type, @RequestParam("date") String date);
61 | }
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/feign/EmbyClient.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.feign;
2 |
3 | import com.zq.media.tools.config.feign.FeignEmbyConfig;
4 | import com.zq.media.tools.dto.resp.emby.FavoriteItemsDTO;
5 | import com.zq.media.tools.dto.resp.emby.ItemRespDTO;
6 | import com.zq.media.tools.dto.resp.emby.MediaPlaybackInfoRespDTO;
7 | import feign.Response;
8 | import org.springframework.cloud.openfeign.FeignClient;
9 | import org.springframework.web.bind.annotation.GetMapping;
10 | import org.springframework.web.bind.annotation.PathVariable;
11 | import org.springframework.web.bind.annotation.RequestParam;
12 |
13 | /**
14 | * emby feign
15 | *
16 | * @author zhaoqiang
17 | * @since V1.0.0 2025-4-21
18 | */
19 | @FeignClient(name = "emby", url = "${app.emby.url}", configuration = FeignEmbyConfig.class)
20 | public interface EmbyClient {
21 |
22 | /**
23 | * 下载图片
24 | *
25 | * @param itemId 项目id
26 | * @param tag 标记
27 | * @return {@link Response }
28 | */
29 | @GetMapping("/emby/Items/{itemId}/Images/Primary")
30 | Response downloadImage(@PathVariable("itemId") String itemId, @RequestParam("tag") String tag);
31 |
32 | /**
33 | * 获取项目
34 | *
35 | * @param itemId 项目id
36 | * @return {@link ItemRespDTO }
37 | */
38 | @GetMapping("/emby/Users/[userId]/Items/{itemId}")
39 | ItemRespDTO getItem(@PathVariable("itemId") String itemId);
40 |
41 | /**
42 | * 获取播放信息
43 | *
44 | * @param itemId 项目id
45 | * @return {@link MediaPlaybackInfoRespDTO }
46 | */
47 | @GetMapping("/emby/Items/{itemId}/PlaybackInfo")
48 | MediaPlaybackInfoRespDTO getPlaybackInfo(@PathVariable("itemId") String itemId);
49 |
50 | /**
51 | * 获取收藏项目
52 | *
53 | * @return {@link FavoriteItemsDTO }
54 | */
55 | @GetMapping("/emby/Users/[userId]/Items?Filters=IsFavorite&IncludeItemTypes=Series&Recursive=true")
56 | FavoriteItemsDTO getFavoriteItems();
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/feign/Life115Client.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.feign;
2 |
3 | import com.zq.media.tools.config.feign.Feign115Config;
4 | import com.zq.media.tools.dto.resp.driver115.LifeListRespDTO;
5 | import org.springframework.cloud.openfeign.FeignClient;
6 | import org.springframework.web.bind.annotation.GetMapping;
7 | import org.springframework.web.bind.annotation.RequestParam;
8 |
9 | /**
10 | * 用于与 115生活 API 进行通信的 Feign 客户端接口。
11 | * @author zhaoqiang
12 | * @version 1.0
13 | * @date 2024-11-12 15:04
14 | */
15 | @FeignClient(name = "life115Client", url = "https://life.115.com/api/1.0/web/1.0", configuration = Feign115Config.class)
16 | public interface Life115Client {
17 |
18 | /**
19 | * 调用 life_list 接口获取事件列表。
20 | *
21 | * @param startTime 起始时间戳
22 | * @param limit 最大记录数
23 | * @param lastData 上一条数据
24 | * @return 包含事件列表的响应对象
25 | */
26 | @GetMapping("/life/life_list")
27 | LifeListRespDTO queryLifeList(@RequestParam("start_time") long startTime, @RequestParam("limit") int limit,@RequestParam("last_data") String lastData);
28 |
29 | /**
30 | * 调用 life_list 接口获取事件列表。
31 | *
32 | * @param startTime 起始时间戳
33 | * @param endTime 结束时间戳
34 | * @param limit 最大记录数
35 | * @param lastData 上一条数据
36 | * @return 包含事件列表的响应对象
37 | */
38 | @GetMapping("/life/life_list")
39 | LifeListRespDTO queryLifeList(@RequestParam("start_time") long startTime, @RequestParam("end_time") long endTime, @RequestParam("limit") int limit,@RequestParam("last_data") String lastData);
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/feign/TtmClient.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.feign;
2 |
3 | import com.zq.media.tools.config.feign.FeignTtmConfig;
4 | import com.zq.media.tools.dto.req.ttm.TtmReqDTO;
5 | import org.springframework.cloud.openfeign.FeignClient;
6 | import org.springframework.web.bind.annotation.PostMapping;
7 | import org.springframework.web.bind.annotation.RequestBody;
8 |
9 | import java.util.List;
10 |
11 | /**
12 | * tinyMediaManager api
13 | *
14 | * @author zhaoqiang
15 | * @version 1.0
16 | * @date 2024-12-23 16:56
17 | */
18 | @FeignClient(name = "ttm", url = "${app.ttm.url}", configuration = FeignTtmConfig.class)
19 | public interface TtmClient {
20 |
21 | /**
22 | * 执行ttm命令
23 | *
24 | * @param ttmReqDTO ttm req dto
25 | */
26 | @PostMapping
27 | void execute(@RequestBody List ttmReqDTO);
28 |
29 | /**
30 | * 更新媒体库
31 | *
32 | * @param ttmReqDTO ttm req dto
33 | */
34 | @PostMapping
35 | void update(@RequestBody TtmReqDTO ttmReqDTO);
36 |
37 | /**
38 | * 刮削
39 | *
40 | * @param ttmReqDTO ttm req dto
41 | */
42 | @PostMapping
43 | void scrape(@RequestBody TtmReqDTO ttmReqDTO);
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/init/AlistStrmInit.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.init;
2 |
3 | import com.zq.media.tools.properties.ConfigProperties;
4 | import com.zq.media.tools.service.impl.AlistServiceImpl;
5 | import lombok.RequiredArgsConstructor;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.springframework.boot.context.event.ApplicationStartedEvent;
8 | import org.springframework.context.ApplicationListener;
9 | import org.springframework.scheduling.annotation.Async;
10 | import org.springframework.stereotype.Component;
11 |
12 | import java.util.List;
13 |
14 | /**
15 | * alist文件strm初始化
16 | *
17 | * @author zhaoqiang
18 | * @version 1.0
19 | * @date 2025-2-20 10:10
20 | */
21 | @Slf4j
22 | @Component
23 | @RequiredArgsConstructor
24 | public class AlistStrmInit implements ApplicationListener {
25 |
26 | private final ConfigProperties configProperties;
27 | private final AlistServiceImpl alistService;
28 |
29 | @Override
30 | @Async
31 | public void onApplicationEvent(ApplicationStartedEvent event) {
32 | List mediaPath = configProperties.getAlist().getMediaPath();
33 | for (String path : mediaPath) {
34 | alistService.processDic(path);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/mapper/Media115Mapper.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.mapper;
2 |
3 | import com.zq.core.mybatisplus.mapper.BaseMapperX;
4 | import com.zq.media.tools.entity.Media115;
5 |
6 | /**
7 | * 115媒体库mapper
8 | *
9 | * @author zhaoqiang
10 | * @since V1.0.0 2024-11-15
11 | */
12 | public interface Media115Mapper extends BaseMapperX {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/params/EmbyNotifyParam.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.params;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import com.zq.media.tools.dto.resp.emby.ItemRespDTO;
5 | import com.zq.media.tools.enums.EmbyEvent;
6 | import io.swagger.v3.oas.annotations.media.Schema;
7 | import lombok.Getter;
8 | import lombok.Setter;
9 | import lombok.ToString;
10 |
11 | /**
12 | * emby通知请求参数
13 | *
14 | * @author zhaoqiang
15 | * @version 1.0
16 | * @date 2025-4-14 15:59
17 | */
18 | @Getter
19 | @Setter
20 | @ToString
21 | @Schema(description = "emby通知请求参数")
22 | public class EmbyNotifyParam {
23 |
24 | /**
25 | * 标题
26 | */
27 | @JsonProperty("Title")
28 | private String title;
29 |
30 | /**
31 | * 描述信息
32 | */
33 | @JsonProperty("Description")
34 | private String description;
35 |
36 | /**
37 | * 事件日期
38 | */
39 | @JsonProperty("Date")
40 | private String date;
41 |
42 | /**
43 | * 事件类型
44 | */
45 | @JsonProperty("Event")
46 | private EmbyEvent event;
47 |
48 | /**
49 | * 严重性
50 | */
51 | @JsonProperty("Severity")
52 | private String severity;
53 |
54 | /**
55 | * 用户信息
56 | */
57 | @JsonProperty("User")
58 | private User user;
59 |
60 | /**
61 | * 项目信息
62 | */
63 | @JsonProperty("Item")
64 | private Item item;
65 |
66 | /**
67 | * 服务器信息
68 | */
69 | @JsonProperty("Server")
70 | private Server server;
71 |
72 | /**
73 | * 用户信息静态内部类
74 | */
75 | @Getter
76 | @Setter
77 | @ToString
78 | public static class User {
79 | /**
80 | * 用户名
81 | */
82 | @JsonProperty("Name")
83 | private String name;
84 |
85 | /**
86 | * 用户ID
87 | */
88 | @JsonProperty("Id")
89 | private String id;
90 | }
91 |
92 | /**
93 | * 项目信息静态内部类
94 | */
95 | @Getter
96 | @Setter
97 | @ToString(callSuper = true)
98 | public static class Item extends ItemRespDTO {
99 |
100 | /**
101 | * 剧集id
102 | */
103 | @JsonProperty("SeriesId")
104 | private String seriesId;
105 |
106 | /**
107 | * 剧集名称
108 | */
109 | @JsonProperty("SeriesName")
110 | private String seriesName;
111 |
112 | /**
113 | * 季名称
114 | */
115 | @JsonProperty("SeasonName")
116 | private String seasonName;
117 |
118 | /**
119 | * 季索引号
120 | */
121 | @JsonProperty("ParentIndexNumber")
122 | private Integer parentIndexNumber;
123 |
124 | /**
125 | * 索引号
126 | */
127 | @JsonProperty("IndexNumber")
128 | private Integer indexNumber;
129 | }
130 |
131 | /**
132 | * 服务器信息静态内部类
133 | */
134 | @Getter
135 | @Setter
136 | @ToString
137 | public static class Server {
138 | /**
139 | * 名称
140 | */
141 | @JsonProperty("Name")
142 | private String name;
143 |
144 | /**
145 | * ID
146 | */
147 | @JsonProperty("Id")
148 | private String id;
149 |
150 | /**
151 | * 版本
152 | */
153 | @JsonProperty("Version")
154 | private String version;
155 | }
156 | }
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/properties/ConfigProperties.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.properties;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import lombok.ToString;
6 | import org.springframework.boot.context.properties.ConfigurationProperties;
7 | import org.springframework.stereotype.Component;
8 |
9 | import java.util.ArrayList;
10 | import java.util.HashMap;
11 | import java.util.List;
12 | import java.util.Map;
13 |
14 | /**
15 | * @author zhaoqiang
16 | * @version 1.0
17 | * @date 2024-11-11 16:48
18 | */
19 | @Getter
20 | @Setter
21 | @ToString
22 | @Component
23 | @ConfigurationProperties(prefix = "app")
24 | public class ConfigProperties {
25 |
26 | /**
27 | * API 速率限制
28 | */
29 | private Integer apiRateLimit = 1;
30 |
31 | /**
32 | * 是否下载媒体文件
33 | */
34 | private Boolean downloadMediaFile = true;
35 |
36 | /**
37 | * 对 strm 路径进行编码
38 | */
39 | private Boolean encodeStrmPath = true;
40 |
41 | /**
42 | * alist 配置
43 | */
44 | private Alist alist;
45 |
46 | /**
47 | * cd2 配置
48 | */
49 | private CloudDrive cloudDrive;
50 |
51 | /**
52 | * 服务器信息
53 | */
54 | private Server server;
55 |
56 | /**
57 | * 115网盘配置
58 | */
59 | private Driver115 driver115;
60 |
61 | /**
62 | * 夸克网盘配置
63 | */
64 | private DriverQuark driverQuark;
65 |
66 | /**
67 | * 天翼云盘配置
68 | */
69 | private DriverCloud189 driverCloud189;
70 |
71 | /**
72 | * tinyMediaManager 配置
73 | */
74 | private Ttm ttm;
75 |
76 | /**
77 | * emby配置
78 | */
79 | private Emby emby;
80 |
81 | /**
82 | * 剧集组
83 | */
84 | private List episodeGroup = new ArrayList<>();
85 |
86 | @Getter
87 | @Setter
88 | @ToString
89 | public static class Alist {
90 |
91 | private Boolean enabled;
92 | /**
93 | * 令 牌
94 | */
95 | private String token;
96 | /**
97 | * 间隔分钟
98 | */
99 | private Integer intervalMinutes;
100 | /**
101 | * url
102 | */
103 | private String url;
104 | /**
105 | * 媒体 URL
106 | */
107 | private String mediaUrl;
108 |
109 | /**
110 | * 115网盘路径
111 | */
112 | private String driver115Path;
113 |
114 | /**
115 | * 媒体路径
116 | */
117 | private List mediaPath = new ArrayList<>();
118 |
119 | /**
120 | * 刮削路径
121 | */
122 | private Map scrapPath;
123 |
124 | /**
125 | * 连续电视节目到目标文件夹的映射,比如
126 | * 遮天 (2023): /动漫/国产动漫/遮天 (2023)
127 | */
128 | private Map serializedTvShow = new HashMap<>();
129 | }
130 |
131 | @Getter
132 | @Setter
133 | @ToString
134 | public static class Server {
135 | /**
136 | * 文件存放基本路径
137 | */
138 | private String basePath;
139 |
140 | /**
141 | * 115网盘本地路径
142 | */
143 | private String driver115Path;
144 | }
145 |
146 | @Getter
147 | @Setter
148 | @ToString
149 | public static class Driver115 {
150 |
151 | private Boolean enabled;
152 |
153 | /**
154 | * 监听间隔分钟
155 | */
156 | private Integer intervalMinutes = 5;
157 |
158 | private String cookie;
159 |
160 | private String userAgent = "";
161 |
162 | private Integer limit = 1000;
163 |
164 | /**
165 | * 忽略文件夹
166 | */
167 | private List ignoreFolders = new ArrayList<>();
168 |
169 | }
170 |
171 | @Getter
172 | @Setter
173 | @ToString
174 | public static class DriverQuark {
175 |
176 | /**
177 | * 处理文件夹
178 | */
179 | private List handleFolders = new ArrayList<>();
180 | }
181 |
182 | @Getter
183 | @Setter
184 | @ToString
185 | public static class DriverCloud189 {
186 |
187 | /**
188 | * 处理文件夹
189 | */
190 | private List handleFolders = new ArrayList<>();
191 | }
192 |
193 | @Getter
194 | @Setter
195 | @ToString
196 | public static class Ttm {
197 |
198 | private Boolean enabled;
199 |
200 | private String apiKey;
201 |
202 | private String url;
203 |
204 | /**
205 | * 刮削时间:秒
206 | */
207 | private Integer scrapTime = 300;
208 | }
209 |
210 | @Getter
211 | @Setter
212 | @ToString
213 | public static class CloudDrive {
214 |
215 | private Boolean enabled;
216 |
217 | private String url;
218 |
219 | private String username;
220 |
221 | private String password;
222 |
223 | }
224 |
225 | @Getter
226 | @Setter
227 | @ToString
228 | public static class Emby {
229 |
230 | private String url = "http://127.0.0.1:8096";
231 |
232 | private String apiKey;
233 |
234 | private String userId;
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/properties/TelegramBotProperties.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.properties;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import lombok.ToString;
6 | import org.springframework.boot.context.properties.ConfigurationProperties;
7 | import org.springframework.stereotype.Component;
8 |
9 | /**
10 | * tg机器人配置
11 | *
12 | * @author zhaoqiang
13 | * @version 1.0
14 | * @date 2025-4-15 15:08
15 | */
16 | @Getter
17 | @Setter
18 | @ToString
19 | @Component
20 | @ConfigurationProperties(prefix = "telegrambots")
21 | public class TelegramBotProperties {
22 |
23 | private boolean enabled;
24 |
25 | /**
26 | * 机器人token
27 | */
28 | private String token;
29 |
30 | /**
31 | * 机器人名称
32 | */
33 | private String botName;
34 |
35 | /**
36 | * 聊天id
37 | */
38 | private Long chatId;
39 |
40 | /**
41 | * 代理
42 | */
43 | private Proxy proxy;
44 |
45 | @Getter
46 | @Setter
47 | @ToString
48 | public static class Proxy {
49 |
50 | private String hostname;
51 |
52 | private int port;
53 |
54 | private String username;
55 |
56 | private String password;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/service/IAlistService.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.service;
2 |
3 | import com.zq.media.tools.dto.HandleFileDTO;
4 |
5 | import java.util.Set;
6 |
7 | /**
8 | * @author zhaoqiang
9 | * @version 1.0
10 | * @date 2025/1/1 13:03
11 | */
12 | public interface IAlistService {
13 |
14 | /**
15 | * 处理云盘自动保存
16 | *
17 | * @param handleFile 处理文件
18 | */
19 | void handleCloudAutoSave(HandleFileDTO handleFile);
20 |
21 | /**
22 | * 处理目录(创建 STRM 并下载文件)
23 | *
24 | * @param mediaPath 媒体路径
25 | */
26 | void processDic(String mediaPath);
27 |
28 | /**
29 | * 查询列表文件通过目录
30 | *
31 | * @param folderPath 文件夹路径
32 | * @return {@link Set }<{@link String }>
33 | */
34 | Set queryListFileByDic(String folderPath);
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/service/IMedia115Service.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.service;
2 |
3 | import com.baomidou.mybatisplus.extension.service.IService;
4 | import com.zq.media.tools.entity.Media115;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * 115媒体库service
10 | *
11 | * @author zhaoqiang
12 | * @since V1.0.0 2024-11-15
13 | */
14 | public interface IMedia115Service extends IService {
15 |
16 | /**
17 | * 获取通过文件id
18 | *
19 | * @param fileId 文件id
20 | * @return {@link Media115 }
21 | */
22 | Media115 getByFileId(String fileId);
23 |
24 | /**
25 | * 查询树列表通过文件id
26 | *
27 | * @param fileId 文件id
28 | * @return {@link List }<{@link Media115 }>
29 | */
30 | List queryDescendantsByFileId(String fileId);
31 |
32 | /**
33 | * 查询列表通过SHA1和路径
34 | *
35 | * @param sha1 SHA1
36 | * @param path 路径
37 | * @return {@link Media115 }
38 | */
39 | Media115 getBySha1AndPath(String sha1, String path);
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/service/IReceiveNotificationService.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.service;
2 |
3 | import com.zq.media.tools.params.EmbyNotifyParam;
4 |
5 | /**
6 | * 接收通知service
7 | *
8 | * @author zhaoqiang
9 | * @since V1.0.0 2025/1/8
10 | */
11 | public interface IReceiveNotificationService {
12 | /**
13 | * 接收夸克自动保存
14 | *
15 | * @param content 内容
16 | */
17 | void receiveQuarkAutoSave(String content);
18 |
19 | /**
20 | * 接收天翼云盘自动保存
21 | *
22 | * @param content 内容
23 | */
24 | void receiveCloud189AutoSave(String content);
25 |
26 | /**
27 | * 接收 Emby 的神医通知
28 | *
29 | * @param embyNotifyParam emby 通知参数
30 | */
31 | void receiveEmbyFromShenYi(EmbyNotifyParam embyNotifyParam);
32 |
33 | /**
34 | * 接收 Emby的通知
35 | *
36 | * @param embyNotifyParam emby 通知参数
37 | */
38 | void receiveEmbyMedia(EmbyNotifyParam embyNotifyParam);
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/service/ITelegramBotService.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.service;
2 |
3 | import org.telegram.telegrambots.meta.api.methods.botapimethods.BotApiMethod;
4 |
5 | import java.io.File;
6 | import java.io.Serializable;
7 |
8 | /**
9 | * tg
10 | *
11 | * @author zhaoqiang
12 | * @version 1.0
13 | * @date 2025-4-15 16:17
14 | */
15 | public interface ITelegramBotService {
16 |
17 | /**
18 | * 发送消息
19 | *
20 | * @param chatId 聊天id
21 | * @param message 消息
22 | */
23 | void sendMessage(Long chatId, String message);
24 |
25 | /**
26 | * 发送消息(markdown语法)
27 | *
28 | * @param chatId 聊天id
29 | * @param message 消息
30 | */
31 | void sendMarkdownMessage(Long chatId, String message);
32 |
33 | /**
34 | * 发送文件
35 | *
36 | * @param chatId 聊天id
37 | * @param file 文件
38 | */
39 | void sendFile(Long chatId, File file);
40 |
41 | /**
42 | * 发送文件
43 | *
44 | * @param chatId 聊天id
45 | * @param file 文件
46 | * @param caption 标题
47 | */
48 | void sendFile(Long chatId, File file, String caption);
49 |
50 | /**
51 | * 发送文件(markdown语法)
52 | *
53 | * @param chatId 聊天id
54 | * @param file 文件
55 | * @param caption 标题
56 | */
57 | void sendMarkdownFile(Long chatId, File file, String caption);
58 |
59 | /**
60 | * 发送消息
61 | *
62 | * @param method 消息
63 | */
64 | > void execute(BotMethod method);
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/service/impl/AlistServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.service.impl;
2 |
3 | import com.zq.common.domain.Result;
4 | import com.zq.common.util.ThreadUtil;
5 | import com.zq.media.tools.dto.HandleFileDTO;
6 | import com.zq.media.tools.dto.req.alist.CopyFileReqDTO;
7 | import com.zq.media.tools.dto.req.alist.ListFileReqDTO;
8 | import com.zq.media.tools.dto.req.alist.MoveFileReqDTO;
9 | import com.zq.media.tools.dto.req.alist.RenameFileReqDTO;
10 | import com.zq.media.tools.dto.req.ttm.TtmReqDTO;
11 | import com.zq.media.tools.dto.resp.alist.TaskRespDTO;
12 | import com.zq.media.tools.dto.resp.alist.listFileRespDTO;
13 | import com.zq.media.tools.enums.TtmAction;
14 | import com.zq.media.tools.enums.TtmScopeName;
15 | import com.zq.media.tools.feign.AlistClient;
16 | import com.zq.media.tools.feign.TtmClient;
17 | import com.zq.media.tools.properties.ConfigProperties;
18 | import com.zq.media.tools.service.IAlistService;
19 | import com.zq.media.tools.util.MediaUtil;
20 | import com.zq.media.tools.util.StrmUtil;
21 | import lombok.RequiredArgsConstructor;
22 | import lombok.extern.slf4j.Slf4j;
23 | import org.dromara.hutool.core.collection.CollUtil;
24 | import org.dromara.hutool.core.io.file.FileNameUtil;
25 | import org.dromara.hutool.core.io.file.FileUtil;
26 | import org.dromara.hutool.core.text.StrUtil;
27 | import org.dromara.hutool.core.util.RuntimeUtil;
28 | import org.dromara.hutool.http.client.HttpDownloader;
29 | import org.jetbrains.annotations.NotNull;
30 | import org.springframework.stereotype.Service;
31 |
32 | import java.nio.file.Path;
33 | import java.nio.file.Paths;
34 | import java.time.temporal.ChronoUnit;
35 | import java.util.ArrayList;
36 | import java.util.Arrays;
37 | import java.util.HashMap;
38 | import java.util.HashSet;
39 | import java.util.List;
40 | import java.util.Map;
41 | import java.util.Set;
42 | import java.util.concurrent.TimeUnit;
43 | import java.util.concurrent.atomic.AtomicBoolean;
44 | import java.util.concurrent.atomic.AtomicInteger;
45 | import java.util.regex.Matcher;
46 | import java.util.regex.Pattern;
47 |
48 | import static com.zq.common.util.CollectionUtil.anyMatch;
49 | import static com.zq.common.util.CollectionUtil.convertSet;
50 |
51 | /**
52 | * @author zhaoqiang
53 | * @version 1.0
54 | * @date 2025/1/1 13:04
55 | */
56 | @Service
57 | @RequiredArgsConstructor
58 | @Slf4j
59 | public class AlistServiceImpl implements IAlistService {
60 |
61 | private final ConfigProperties configProperties;
62 | private final AlistClient alistClient;
63 | private final TtmClient ttmClient;
64 |
65 | private final Map taskIds = new HashMap<>();
66 | AtomicInteger taskIndex = new AtomicInteger(0);
67 |
68 | /**
69 | * 处理云盘自动保存
70 | *
71 | * @param handleFile handle 文件
72 | */
73 | @Override
74 | public void handleCloudAutoSave(HandleFileDTO handleFile) {
75 | // 1、获取目标文件夹下已存在的文件列表
76 | Result listFileResult = alistClient.listFile(new ListFileReqDTO(handleFile.getFolderPath(), false));
77 | Set existingEpisodes = convertSet(listFileResult.getCheckedData().getContent(), listFileRespDTO.Content::getName);
78 |
79 | // 2、过滤出需要复制的新文件
80 | Set newFiles = new HashSet<>();
81 | List handleFiles = filterDuplicateEpisodes(handleFile.getFiles());
82 | for (String file : handleFiles) {
83 | boolean exists = false;
84 | if (handleFile.getIsSingleTask()) {
85 | exists = anyMatch(existingEpisodes,
86 | existingFile -> !existingFile.equals(file) && MediaUtil.areEpisodesEqual(existingFile, file));
87 | }
88 | if (exists) {
89 | log.info("剧集已存在: {}\n{}", handleFile.getFolderPath(), file);
90 | } else {
91 | newFiles.add(file);
92 | }
93 | }
94 |
95 | if (newFiles.isEmpty()) {
96 | log.info("所有剧集已存在,无需复制: {}\n{}", handleFile.getFolderPath(), handleFiles);
97 | return;
98 | }
99 |
100 | // 3、刷新文件列表并准备复制
101 | alistClient.listFile(new ListFileReqDTO(handleFile.getFolderPath(), true));
102 | // 剧集名
103 | String seriesName = FileNameUtil.getName(handleFile.getFolderPath());
104 | String scrapPath = configProperties.getAlist().getScrapPath().get(seriesName);
105 |
106 | // 4、判断目标网盘是否已经存在此文件了
107 | Result destListFileResult = alistClient.listFile(new ListFileReqDTO(scrapPath, true));
108 | Set existingFiles = convertSet(destListFileResult.getCheckedData().getContent(), listFileRespDTO.Content::getName);
109 | newFiles.removeIf(file -> {
110 | if (existingFiles.contains(file)) {
111 | log.info("目标网盘已存在此文件: {}", file);
112 | return true;
113 | }
114 | return false;
115 | });
116 |
117 | if (newFiles.isEmpty()) {
118 | log.info("目标网盘已存在所有文件,无需复制,直接刮削: {}", scrapPath);
119 | // 直接触发刮削和移动文件
120 | processCompletedCopy(seriesName, scrapPath);
121 | return;
122 | }
123 |
124 | // 5、执行文件复制
125 | CopyFileReqDTO copyRequest = new CopyFileReqDTO();
126 | copyRequest.setSrcDir(handleFile.getFolderPath())
127 | .setDstDir(scrapPath)
128 | .setNames(newFiles);
129 |
130 | alistClient.copyFile(copyRequest);
131 | log.info("复制文件: {} -> {}, 文件列表: {}", handleFile.getFolderPath(), scrapPath, newFiles);
132 |
133 | // 6、启动复制监控任务
134 | AtomicBoolean copyTaskDone = new AtomicBoolean(false);
135 | int taskIndex = this.taskIndex.addAndGet(1);
136 | try {
137 | // 等待10秒,防止同网盘内复制,无复制任务的情况
138 | TimeUnit.SECONDS.sleep(10);
139 | } catch (InterruptedException ignored) {
140 | }
141 | int taskId = ThreadUtil.executeCycle(
142 | () -> copyFileMonitor(copyTaskDone, taskIndex, seriesName, scrapPath),
143 | 5,
144 | ChronoUnit.MINUTES
145 | );
146 | taskIds.put(taskIndex, taskId);
147 | }
148 |
149 | /**
150 | * 处理目录(创建 STRM 并下载文件)
151 | *
152 | * @param mediaPath 媒体路径
153 | */
154 | @Override
155 | public void processDic(String mediaPath) {
156 | try {
157 | TimeUnit.SECONDS.sleep(configProperties.getApiRateLimit());
158 | } catch (InterruptedException ignored) {
159 | }
160 | log.info("处理alist目录: {}", mediaPath);
161 | Result listFileResult = alistClient.listFile(new ListFileReqDTO(mediaPath, true));
162 | if (listFileResult.isSuccess()) {
163 | for (listFileRespDTO.Content content : listFileResult.getCheckedData().getContent()) {
164 | if (content.getIsDir()) {
165 | // 构建子目录的完整路径
166 | String subDirPath = Paths.get(mediaPath, content.getName()).toString();
167 | processDic(subDirPath);
168 | } else {
169 | // 构建文件的完整路径
170 | Path filePath = Paths.get(mediaPath, content.getName());
171 | createStrmAndDownloadFile(filePath);
172 | }
173 | }
174 | } else {
175 | log.error("获取alist文件列表失败: {}", mediaPath);
176 | }
177 | }
178 |
179 | /**
180 | * 查询列表文件通过目录
181 | *
182 | * @param folderPath 文件夹路径
183 | * @return {@link Set }<{@link String }>
184 | */
185 | @Override
186 | public Set queryListFileByDic(String folderPath) {
187 | Result listFileResult = alistClient.listFile(new ListFileReqDTO(folderPath, true));
188 | return convertSet(listFileResult.getCheckedData().getContent(), listFileRespDTO.Content::getName);
189 | }
190 |
191 | /**
192 | * 刮削
193 | */
194 | private void scrap() {
195 | List ttmReqDTOList = new ArrayList<>();
196 | TtmReqDTO ttmReqDTO = new TtmReqDTO();
197 |
198 | ttmReqDTO.setAction(TtmAction.UPDATE)
199 | .setScope(new TtmReqDTO.Scope(TtmScopeName.ALL, new ArrayList<>()));
200 | ttmReqDTOList.add(ttmReqDTO);
201 | ttmReqDTO = new TtmReqDTO();
202 | ttmReqDTO.setAction(TtmAction.SCRAPE)
203 | .setScope(new TtmReqDTO.Scope(TtmScopeName.UN_SCRAPED, new ArrayList<>()));
204 | ttmReqDTOList.add(ttmReqDTO);
205 | ttmClient.execute(ttmReqDTOList);
206 | try {
207 | TimeUnit.SECONDS.sleep(30);
208 | } catch (InterruptedException ignored) {
209 | }
210 | for (int i = 0; i < 2; i++) {
211 | ttmClient.execute(ttmReqDTOList);
212 | try {
213 | // 等待刮削完成
214 | TimeUnit.SECONDS.sleep(configProperties.getTtm().getScrapTime());
215 | } catch (InterruptedException ignored) {
216 | }
217 | }
218 | }
219 |
220 | /**
221 | * 监控文件复制进度
222 | *
223 | * @param copyTaskDone 复制任务完成标志
224 | * @param index 任务索引
225 | * @param seriesName 剧集名
226 | * @param scrapPath 刮削路径
227 | */
228 | private void copyFileMonitor(AtomicBoolean copyTaskDone, int index, String seriesName, String scrapPath) {
229 | // 获取复制任务列表
230 | Result> copyUndoneTaskListResult = alistClient.listCopyUndoneTask();
231 | log.debug("复制文件监视器:{}", copyUndoneTaskListResult.getCheckedData());
232 |
233 | // 检查任务是否完成
234 | if (copyUndoneTaskListResult.getCheckedData().isEmpty()) {
235 | copyTaskDone.set(true);
236 | }
237 |
238 | if (!copyTaskDone.get()) {
239 | return;
240 | }
241 |
242 | // 异步处理后续任务
243 | ThreadUtil.execute(() -> processCompletedCopy(seriesName, scrapPath));
244 |
245 | // 停止监听任务
246 | log.info("复制文件已完成,停止监听任务:{}-{}", taskIds.get(index), scrapPath);
247 | ThreadUtil.stop(taskIds.get(index));
248 | }
249 |
250 | /**
251 | * 处理复制完成后的任务
252 | * 包括刷新目录、刮削文件和移动文件等操作
253 | *
254 | * @param seriesName 剧集名
255 | * @param scrapPath 刮削路径
256 | */
257 | private void processCompletedCopy(String seriesName, String scrapPath) {
258 | // 剧集组重命名
259 | if (!configProperties.getEpisodeGroup().isEmpty()) {
260 | for (String episodeGroup : configProperties.getEpisodeGroup()) {
261 | if (episodeGroup.contains(seriesName)) {
262 | // 重命名剧集组
263 | List renameFileList = getRenameFiles(scrapPath, episodeGroup);
264 | log.info("剧集组重命名:{}: {}", scrapPath, renameFileList);
265 | alistClient.renameFile(new RenameFileReqDTO(scrapPath, renameFileList));
266 | }
267 | }
268 | }
269 |
270 | if (configProperties.getCloudDrive().getEnabled()) {
271 | // 刷新cd2目录
272 | refreshCD2Directory(scrapPath);
273 | }
274 |
275 | if (configProperties.getTtm().getEnabled()) {
276 | // 刮削文件
277 | log.info("刮削文件:{}", scrapPath);
278 | scrap();
279 | }
280 |
281 | Result listFileResult = alistClient.listFile(new ListFileReqDTO(scrapPath, true));
282 | Set fileNames = convertSet(listFileResult.getCheckedData().getContent(),
283 | content -> !"season.nfo".equals(content.getName()),
284 | listFileRespDTO.Content::getName);
285 |
286 | // 移动文件到目标目录
287 | String targetPath = moveFilesToTarget(scrapPath, fileNames);
288 |
289 | // 非115网盘触发生成strm和下载文件
290 | if (!scrapPath.contains("115")) {
291 | // 生成strm和下载文件
292 | for (String fileName : fileNames) {
293 | createStrmAndDownloadFile(Paths.get(targetPath + "/" + fileName));
294 | }
295 | }
296 | }
297 |
298 | @NotNull
299 | private List getRenameFiles(String scrapPath, String episodeGroup) {
300 | Result listFileResult = alistClient.listFile(new ListFileReqDTO(scrapPath, false));
301 | List renameFileList = new ArrayList<>();
302 | listFileResult.getCheckedData().getContent().forEach(content -> {
303 | if (!content.getIsDir()) {
304 | String[] split = episodeGroup.split("\\|");
305 | // 解析替换规则。比如:S01E135-S01E152 -> S07E11-S07E28
306 | String[] parts = split[1].split(" -> ");
307 | String[] oldParts = parts[0].split("-");
308 | String[] newParts = parts[1].split("-");
309 |
310 | // 旧季号,如 01
311 | String oldSeason = MediaUtil.getSeason(oldParts[0]);
312 | // 135
313 | int oldStart = MediaUtil.getEpisode(oldParts[0]);
314 | // 152
315 | int oldEnd = MediaUtil.getEpisode(oldParts[1]);
316 |
317 | // 新季号,如 07
318 | String newSeason = MediaUtil.getSeason(newParts[0]);
319 | // 11
320 | int newStart = MediaUtil.getEpisode(newParts[0]);
321 |
322 | // 动态构造正则匹配 SxxE135 - SxxE252
323 | String regex = "S" + oldSeason + "E(" + oldStart;
324 | for (int i = oldStart + 1; i <= oldEnd; i++) {
325 | regex += "|" + i;
326 | }
327 | regex += ")";
328 | Pattern pattern = Pattern.compile(regex);
329 | Matcher matcher = pattern.matcher(content.getName());
330 |
331 | // 替换逻辑
332 | StringBuffer result = new StringBuffer();
333 | while (matcher.find()) {
334 | String oldEpisode = matcher.group();
335 | // 提取集数
336 | int oldEpisodeNum = MediaUtil.getEpisode(oldEpisode);
337 | // 计算新集数
338 | int newEpisodeNum = newStart + (oldEpisodeNum - oldStart);
339 | String newEpisode = StrUtil.format("S{}E{}", newSeason, newEpisodeNum);
340 |
341 | matcher.appendReplacement(result, newEpisode);
342 | }
343 | matcher.appendTail(result);
344 |
345 | renameFileList.add(new RenameFileReqDTO.RenameFile(content.getName(), result.toString()));
346 | }
347 | });
348 | return renameFileList;
349 | }
350 |
351 | /**
352 | * 刷新CD2目录
353 | * 通过执行Python脚本来刷新目录内容
354 | * 主要针对于115网盘,因为通过alist挂载刮削115容易出现冗余文件
355 | *
356 | * @param scrapPath 需要刷新的目录路径
357 | */
358 | private void refreshCD2Directory(String scrapPath) {
359 | try {
360 | String[] script = {"python3", "/app/python/clouddrive_api.py",
361 | configProperties.getCloudDrive().getUrl(),
362 | configProperties.getCloudDrive().getUsername(),
363 | configProperties.getCloudDrive().getPassword(),
364 | "list_files", scrapPath};
365 |
366 | log.debug("执行python脚本:{}", Arrays.asList(script));
367 | String execResult = RuntimeUtil.execForStr(script);
368 | log.debug("python执行结果:{}", execResult);
369 |
370 | TimeUnit.SECONDS.sleep(10);
371 | } catch (Exception ignored) {
372 | }
373 | }
374 |
375 | /**
376 | * 将文件移动到目标目录
377 | * 获取文件列表并移动到对应的电视剧目录
378 | *
379 | * @param scrapPath 源文件路径
380 | * @param fileNames 文件名列表
381 | */
382 | private String moveFilesToTarget(String scrapPath, Set fileNames) {
383 | // 获取电视节目名称和目标路径
384 | String tvShowName = FileNameUtil.mainName(FileUtil.getParent(scrapPath, 1));
385 | String targetPath = configProperties.getAlist().getSerializedTvShow().get(tvShowName);
386 | // 构建移动请求
387 | MoveFileReqDTO moveFileReqDTO = new MoveFileReqDTO()
388 | .setSrcDir(scrapPath)
389 | .setDstDir(targetPath)
390 | .setNames(fileNames);
391 |
392 | log.info("移动文件:{}-{} -> {}", scrapPath, fileNames, targetPath);
393 | alistClient.moveFile(moveFileReqDTO);
394 | try {
395 | TimeUnit.SECONDS.sleep(configProperties.getApiRateLimit());
396 | } catch (InterruptedException ignored) {
397 | }
398 | // alist刷新目标目录
399 | alistClient.listFile(new ListFileReqDTO(targetPath, true));
400 | log.info("新增剧集处理完成:{}-{}", tvShowName, MediaUtil.getEpisodes(CollUtil.getFirst(fileNames)));
401 | return targetPath;
402 | }
403 |
404 | /**
405 | * 下载文件并创建strm文件
406 | *
407 | * @param path 路径
408 | */
409 | private void createStrmAndDownloadFile(Path path) {
410 | if (MediaUtil.isVideoFile(path)) {
411 | String strmPath = StrmUtil.generateStrmFiles(path);
412 | log.info("生成strm文件: {}", strmPath);
413 | } else {
414 | if (configProperties.getDownloadMediaFile()) {
415 | Path fullPath = Paths.get(configProperties.getServer().getBasePath(), path.toString());
416 | try {
417 | TimeUnit.SECONDS.sleep(configProperties.getApiRateLimit());
418 | } catch (InterruptedException ignored) {
419 | }
420 | HttpDownloader.of(configProperties.getAlist().getMediaUrl() + path).downloadFile(fullPath.toFile());
421 | log.info("下载文件:{}", fullPath);
422 | }
423 | }
424 | }
425 |
426 | /**
427 | * 过滤所有重复的剧集文件名
428 | *
429 | * @param fileList 文件名列表
430 | * @return 过滤后的文件名列表
431 | */
432 | public static List filterDuplicateEpisodes(Set fileList) {
433 | // 用于存储已经处理过的剧集编号
434 | Set seenEpisodes = new HashSet<>();
435 | // 用于存储过滤后的文件名
436 | List filteredList = new ArrayList<>();
437 |
438 | // 正则表达式匹配剧集编号(如 S01E150)
439 | Pattern pattern = Pattern.compile("S\\d{2}E\\d+");
440 |
441 | for (String fileName : fileList) {
442 | Matcher matcher = pattern.matcher(fileName);
443 | if (matcher.find()) {
444 | // 提取剧集编号
445 | String episode = matcher.group();
446 | if (!seenEpisodes.contains(episode)) {
447 | // 标记为已处理
448 | seenEpisodes.add(episode);
449 | // 添加到过滤后的列表
450 | filteredList.add(fileName);
451 | }
452 | } else {
453 | // 如果没有剧集编号,直接添加到过滤后的列表
454 | filteredList.add(fileName);
455 | }
456 | }
457 |
458 | return filteredList;
459 | }
460 | }
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/service/impl/Media115ServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.service.impl;
2 |
3 | import com.baomidou.mybatisplus.core.toolkit.Wrappers;
4 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
5 | import com.zq.media.tools.entity.Media115;
6 | import com.zq.media.tools.mapper.Media115Mapper;
7 | import com.zq.media.tools.service.IMedia115Service;
8 | import lombok.RequiredArgsConstructor;
9 | import org.dromara.hutool.core.text.StrUtil;
10 | import org.springframework.stereotype.Service;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | /**
16 | * 115媒体库service实现
17 | *
18 | * @author zhaoqiang
19 | * @version 1.0
20 | * @date 2024-11-15 11:55
21 | */
22 | @Service
23 | @RequiredArgsConstructor
24 | public class Media115ServiceImpl extends ServiceImpl implements IMedia115Service {
25 |
26 | private final Media115Mapper media115Mapper;
27 |
28 | /**
29 | * 获取通过文件id
30 | *
31 | * @param fileId 文件id
32 | * @return {@link Media115 }
33 | */
34 | @Override
35 | public Media115 getByFileId(String fileId) {
36 | return media115Mapper.selectOne(Media115::getFileId, fileId);
37 | }
38 |
39 | /**
40 | * 查询树列表通过文件id
41 | *
42 | * @param fileId 文件id
43 | * @return {@link List }<{@link Media115 }>
44 | */
45 | @Override
46 | public List queryDescendantsByFileId(String fileId) {
47 | List descendants = new ArrayList<>();
48 | findDescendants(fileId, descendants);
49 | Media115 media115 = getByFileId(fileId);
50 | if (media115 != null) {
51 | descendants.add(media115);
52 | }
53 | return descendants;
54 | }
55 |
56 | /**
57 | * 查询列表通过SHA1
58 | *
59 | * @param sha1 SHA1
60 | * @param path 路径
61 | * @return {@link Media115 }
62 | */
63 | @Override
64 | public Media115 getBySha1AndPath(String sha1, String path) {
65 | return media115Mapper.selectOne(Wrappers.lambdaQuery(Media115.class)
66 | .eq(StrUtil.isNotBlank(sha1), Media115::getSha1, sha1)
67 | .eq(Media115::getPath, path));
68 | }
69 |
70 | private void findDescendants(String fileId, List descendants) {
71 | List children = media115Mapper.selectList(Media115::getParentId, fileId);
72 | if (!children.isEmpty()) {
73 | descendants.addAll(children);
74 | for (Media115 child : children) {
75 | findDescendants(child.getFileId(), descendants);
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/service/impl/ReceiveNotificationServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.service.impl;
2 |
3 | import com.zq.common.domain.Result;
4 | import com.zq.common.util.ThreadUtil;
5 | import com.zq.media.tools.dto.HandleFileDTO;
6 | import com.zq.media.tools.dto.req.alist.DeleteFileReqDTO;
7 | import com.zq.media.tools.dto.req.alist.ListFileReqDTO;
8 | import com.zq.media.tools.dto.resp.alist.listFileRespDTO;
9 | import com.zq.media.tools.dto.resp.emby.FavoriteItemsDTO;
10 | import com.zq.media.tools.dto.resp.emby.ItemRespDTO;
11 | import com.zq.media.tools.dto.resp.emby.MediaPlaybackInfoRespDTO;
12 | import com.zq.media.tools.enums.EmbyMediaType;
13 | import com.zq.media.tools.feign.AlistClient;
14 | import com.zq.media.tools.feign.EmbyClient;
15 | import com.zq.media.tools.params.EmbyNotifyParam;
16 | import com.zq.media.tools.properties.ConfigProperties;
17 | import com.zq.media.tools.properties.TelegramBotProperties;
18 | import com.zq.media.tools.service.IAlistService;
19 | import com.zq.media.tools.service.IReceiveNotificationService;
20 | import com.zq.media.tools.service.ITelegramBotService;
21 | import com.zq.media.tools.util.MediaUtil;
22 | import feign.Response;
23 | import lombok.RequiredArgsConstructor;
24 | import lombok.SneakyThrows;
25 | import lombok.extern.slf4j.Slf4j;
26 | import org.dromara.hutool.core.io.file.FileUtil;
27 | import org.dromara.hutool.core.net.url.UrlDecoder;
28 | import org.dromara.hutool.core.text.StrUtil;
29 | import org.dromara.hutool.core.text.split.SplitUtil;
30 | import org.dromara.hutool.http.meta.HttpStatus;
31 | import org.springframework.stereotype.Service;
32 |
33 | import java.io.File;
34 | import java.util.ArrayList;
35 | import java.util.Arrays;
36 | import java.util.Collections;
37 | import java.util.HashSet;
38 | import java.util.List;
39 | import java.util.Optional;
40 | import java.util.Set;
41 | import java.util.StringJoiner;
42 | import java.util.regex.Matcher;
43 | import java.util.regex.Pattern;
44 |
45 | import static com.zq.common.util.CollectionUtil.*;
46 |
47 | /**
48 | * @author zhaoqiang
49 | * @version 1.0
50 | * @date 2025/1/8 22:21
51 | */
52 | @Slf4j
53 | @Service
54 | @RequiredArgsConstructor
55 | public class ReceiveNotificationServiceImpl implements IReceiveNotificationService {
56 |
57 | private final IAlistService alistService;
58 | private final ConfigProperties configProperties;
59 | private final AlistClient alistClient;
60 | private final EmbyClient embyClient;
61 | private final ITelegramBotService telegramService;
62 | private final TelegramBotProperties telegramBotProperties;
63 |
64 | /**
65 | * 接收夸克自动保存
66 | *
67 | * @param content 内容
68 | */
69 | @Override
70 | public void receiveQuarkAutoSave(String content) {
71 | ThreadUtil.execute(() -> {
72 | List split = SplitUtil.split(content, "\\n");
73 | List list = new ArrayList<>();
74 |
75 | // 初始化文件 DTO
76 | HandleFileDTO handleFileDTO = createHandleFileDTO();
77 | boolean isFolderPathSet = false;
78 |
79 | for (String str : split) {
80 | str = str.trim(); // 去除前后空格
81 | log.debug(str);
82 | if (configProperties.getDriverQuark().getHandleFolders().stream().anyMatch(str::contains)) {
83 | // 设置文件夹路径
84 | handleFileDTO.setFolderPath("/夸克网盘" + str);
85 | isFolderPathSet = true;
86 | } else if (str.isEmpty() || "\"".equals(str)) {
87 | // 处理结束,保存当前 DTO
88 | if (!handleFileDTO.getFiles().isEmpty() && handleFileDTO.getFolderPath() != null) {
89 | // 判断当前文件夹是否已经在list中存在
90 | boolean isExist = false;
91 | for (HandleFileDTO fileDTO : list) {
92 | if (fileDTO.getFolderPath().equals(handleFileDTO.getFolderPath())) {
93 | fileDTO.setIsSingleTask(false);
94 | isExist = true;
95 | }
96 | }
97 | if (!isExist) {
98 | list.add(handleFileDTO);
99 | }
100 | }
101 | handleFileDTO = createHandleFileDTO(); // 重置 DTO
102 | isFolderPathSet = false;
103 | } else if (isFolderPathSet && MediaUtil.isEpisodes(str)) {
104 | // 处理文件路径
105 | String cleanedPath = str.substring(str.indexOf("──") + 2) // 去掉前缀符号
106 | .replaceAll("[\\p{Cf}\\uFEFF🎞️]", "") // 清理特殊字符
107 | .trim();
108 | handleFileDTO.getFiles().add(cleanedPath);
109 | }
110 | }
111 |
112 | // 添加最后一个 DTO
113 | if (!handleFileDTO.getFiles().isEmpty() || handleFileDTO.getFolderPath() != null) {
114 | list.add(handleFileDTO);
115 | }
116 |
117 | // 调用服务方法处理
118 | list.forEach(alistService::handleCloudAutoSave);
119 | });
120 | }
121 |
122 | /**
123 | * 接收天翼云盘自动保存
124 | *
125 | * @param content 内容
126 | */
127 | @Override
128 | public void receiveCloud189AutoSave(String content) {
129 | String seriesName = content.split("\n")[0].split("/")[0];
130 | Set episodes = getEpisodesByCloud189Content(content);
131 | for (String handleFolder : configProperties.getDriverCloud189().getHandleFolders()) {
132 | String folderPath = "/天翼云盘" + handleFolder;
133 | Set fileNames = alistService.queryListFileByDic(folderPath);
134 | if (anyMatch(fileNames, seriesName::equals)) {
135 | alistService.handleCloudAutoSave(new HandleFileDTO(folderPath + "/" + seriesName, episodes));
136 | }
137 | }
138 | }
139 |
140 | /**
141 | * 获取转存剧集通过天翼与云盘自动转存内容
142 | *
143 | * @param content 内容
144 | * @return {@link Set }<{@link String }>
145 | */
146 | private Set getEpisodesByCloud189Content(String content) {
147 | Pattern pattern = Pattern.compile("]*>(.*?)");
148 | Matcher matcher = pattern.matcher(content);
149 |
150 | Set episodes = new HashSet<>();
151 | while (matcher.find()) {
152 | episodes.add(matcher.group(1).trim());
153 | }
154 | return episodes;
155 | }
156 |
157 | /**
158 | * 接收 Emby 的神医通知
159 | *
160 | * @param embyNotifyParam emby 通知参数
161 | */
162 | @Override
163 | public void receiveEmbyFromShenYi(EmbyNotifyParam embyNotifyParam) {
164 | switch (embyNotifyParam.getEvent()) {
165 | case DEEP_DELETE -> deepDelete(embyNotifyParam);
166 | case INTRO_SKIP_UPDATE -> introSkipUpdate(embyNotifyParam);
167 | case FAVORITES_UPDATE -> favoritesUpdate(embyNotifyParam);
168 | }
169 | }
170 |
171 | /**
172 | * 接收 Emby的通知
173 | *
174 | * @param embyNotifyParam emby 通知参数
175 | */
176 | @Override
177 | public void receiveEmbyMedia(EmbyNotifyParam embyNotifyParam) {
178 | FavoriteItemsDTO favoriteItems = embyClient.getFavoriteItems();
179 | Set mediaIds = convertSet(favoriteItems.getItems(), FavoriteItemsDTO.ItemDTO::getId);
180 | if (mediaIds.contains(embyNotifyParam.getItem().getSeriesId())) {
181 | return;
182 | }
183 | mediaUpdate(embyNotifyParam, "影视入库");
184 | }
185 |
186 | @SneakyThrows
187 | private void mediaUpdate(EmbyNotifyParam embyNotifyParam, String notificationTitle) {
188 | ItemRespDTO itemInfo = embyClient.getItem(embyNotifyParam.getItem().getSeriesId());
189 | MediaPlaybackInfoRespDTO mediaPlaybackInfoRespDTO = embyClient.getPlaybackInfo(embyNotifyParam.getItem().getId());
190 | MediaPlaybackInfoRespDTO.MediaSource mediaSource = mediaPlaybackInfoRespDTO.getMediaSources().get(0);
191 | String sizeStr = MediaUtil.formatSize(mediaSource.getSize());
192 | String bitrateStr = MediaUtil.formatBitrate(mediaSource.getBitrate());
193 |
194 | StringJoiner messageJoiner = new StringJoiner("\n");
195 | messageJoiner.add(StrUtil.format("#{} #{} #{}\"", notificationTitle, embyNotifyParam.getItem().getSeriesName(), embyNotifyParam.getServer().getName()));
196 | messageJoiner.add("\\[" + embyNotifyParam.getItem().getType().getDesc() + "]");
197 | appendSeriesInfo(embyNotifyParam, messageJoiner);
198 | messageJoiner.add("视频大小:" + sizeStr);
199 | messageJoiner.add("视频信息:" + mediaSource.getMediaStreams().get(0).getDisplayTitle());
200 | messageJoiner.add(StrUtil.format("分辨率:{}x{}", mediaSource.getMediaStreams().get(0).getWidth(), mediaSource.getMediaStreams().get(0).getHeight()));
201 | messageJoiner.add(StrUtil.format("比特率:{}mbps", bitrateStr));
202 | messageJoiner.add("帧率:" + mediaSource.getMediaStreams().get(0).getAverageFrameRate().intValue());
203 | messageJoiner.add("上映日期:" + embyNotifyParam.getItem().getProductionYear());
204 | messageJoiner.add("内容简介:" + Optional.ofNullable(embyNotifyParam.getItem().getOverview()).orElse(""));
205 | messageJoiner.add(StrUtil.format("相关链接: [TMDB](https://www.themoviedb.org/tv/{}/season/{}/episode/{})", itemInfo.getProviderIds().getTmdb(),
206 | embyNotifyParam.getItem().getParentIndexNumber(), embyNotifyParam.getItem().getIndexNumber()));
207 |
208 | Response response = null;
209 | try {
210 | response = embyClient.downloadImage(embyNotifyParam.getItem().getId(), embyNotifyParam.getItem().getSeriesPrimaryImageTag());
211 | // 剧集图片获取失败,尝试获取剧集主图
212 | if (response.status() != HttpStatus.HTTP_OK) {
213 | response = embyClient.downloadImage(embyNotifyParam.getItem().getSeriesId(), embyNotifyParam.getItem().getSeriesPrimaryImageTag());
214 | }
215 | File file = FileUtil.writeFromStream(response.body().asInputStream(), FileUtil.createTempFile(".jpg", true));
216 | telegramService.sendMarkdownFile(telegramBotProperties.getChatId(), file, messageJoiner.toString());
217 | } finally {
218 | if (response != null) {
219 | response.close();
220 | }
221 | }
222 | }
223 |
224 | /**
225 | * 片头片尾跳过更新
226 | *
227 | * @param embyNotifyParam emby 通知参数
228 | */
229 | private void introSkipUpdate(EmbyNotifyParam embyNotifyParam) {
230 | String introSkipUpdate = "片头";
231 | if (embyNotifyParam.getDescription().contains("片尾标记")) {
232 | introSkipUpdate = "片尾";
233 | return;
234 | }
235 | StringJoiner messageJoiner = new StringJoiner("\n");
236 | messageJoiner.add(StrUtil.format("#{}更新 #{} #{}", introSkipUpdate, embyNotifyParam.getItem().getSeriesName(), embyNotifyParam.getServer().getName()));
237 | messageJoiner.add(embyNotifyParam.getDescription());
238 | telegramService.sendMessage(telegramBotProperties.getChatId(), messageJoiner.toString());
239 | }
240 |
241 | /**
242 | * 最爱更新
243 | *
244 | * @param embyNotifyParam emby 通知参数
245 | */
246 | @SneakyThrows
247 | private void favoritesUpdate(EmbyNotifyParam embyNotifyParam) {
248 | mediaUpdate(embyNotifyParam, "最爱更新");
249 | }
250 |
251 | private static void appendSeriesInfo(EmbyNotifyParam embyNotifyParam, StringJoiner messageJoiner) {
252 | appendSeason(embyNotifyParam, messageJoiner);
253 | if (MediaUtil.isEpisodeNumberMatched(embyNotifyParam.getItem().getName())) {
254 | messageJoiner.add("集:" + embyNotifyParam.getItem().getName());
255 | } else {
256 | messageJoiner.add(StrUtil.format("集:第 {} 集 {}", embyNotifyParam.getItem().getIndexNumber(), embyNotifyParam.getItem().getName()));
257 | }
258 | }
259 |
260 | private static void appendSeason(EmbyNotifyParam embyNotifyParam, StringJoiner messageJoiner) {
261 | messageJoiner.add("片名:" + embyNotifyParam.getItem().getSeriesName());
262 | if (StrUtil.isBlank(embyNotifyParam.getItem().getSeasonName())) {
263 | messageJoiner.add(StrUtil.format("季:第 {} 季", embyNotifyParam.getItem().getParentIndexNumber()));
264 | } else if (MediaUtil.isSeasonNumberMatched(embyNotifyParam.getItem().getSeasonName())) {
265 | messageJoiner.add("季:" + embyNotifyParam.getItem().getSeasonName());
266 | } else {
267 | messageJoiner.add(StrUtil.format("季:第 {} 季 {}", embyNotifyParam.getItem().getParentIndexNumber(), embyNotifyParam.getItem().getSeasonName()));
268 | }
269 | }
270 |
271 |
272 | /**
273 | * 深度删除
274 | *
275 | * @param embyNotifyParam emby 通知参数
276 | */
277 | private void deepDelete(EmbyNotifyParam embyNotifyParam) {
278 | List deleteItems = extractItemPaths(embyNotifyParam.getDescription());
279 | String deleteDic;
280 | Set deleteNames = new HashSet<>();
281 | StringJoiner messageJoiner = new StringJoiner("\n");
282 | if (embyNotifyParam.getItem().getType() == EmbyMediaType.MOVIE) {
283 | messageJoiner.add(StrUtil.format("#影视删除 #{} #{}", embyNotifyParam.getItem().getName(), embyNotifyParam.getServer().getName()));
284 | } else {
285 | messageJoiner.add(StrUtil.format("#影视删除 #{} #{}", embyNotifyParam.getItem().getSeriesName(), embyNotifyParam.getServer().getName()));
286 | }
287 | messageJoiner.add("[" + embyNotifyParam.getItem().getType().getDesc() + "]");
288 | switch (embyNotifyParam.getItem().getType()) {
289 | case MOVIE:
290 | deleteDic = getPrefixByLevel(deleteItems.get(0), 2);
291 | deleteNames.add(getLastSegments(deleteItems.get(0), 2).get(0));
292 | messageJoiner.add("片名:" + embyNotifyParam.getItem().getName());
293 | break;
294 | case SEASON:
295 | deleteDic = getPrefixByLevel(deleteItems.get(0), 2);
296 | deleteNames.add(getLastSegments(deleteItems.get(0), 2).get(0));
297 | appendSeason(embyNotifyParam, messageJoiner);
298 | break;
299 | case SERIES:
300 | deleteDic = getPrefixByLevel(deleteItems.get(0), 3);
301 | deleteNames.add(getLastSegments(deleteItems.get(0), 3).get(0));
302 | messageJoiner.add("片名:" + embyNotifyParam.getItem().getName());
303 | break;
304 | case EPISODE:
305 | deleteDic = getPrefixByLevel(deleteItems.get(0), 1);
306 | for (String item : deleteItems) {
307 | deleteNames.add(getLastSegments(item, 1).get(0));
308 | }
309 | Result listFileResult = alistClient.listFile(new ListFileReqDTO(deleteDic, true));
310 | deleteNames.addAll(convertList(listFileResult.getCheckedData().getContent(),
311 | file -> anyMatch(deleteNames, episodeName -> MediaUtil.areEpisodesEqual(file.getName(), episodeName)),
312 | listFileRespDTO.Content::getName));
313 | appendSeriesInfo(embyNotifyParam, messageJoiner);
314 | break;
315 | default:
316 | log.warn("不支持的媒体类型:{}", embyNotifyParam.getItem().getType());
317 | return;
318 | }
319 | log.info("接收到emby深度删除,调用alist删除文件,目录:{},文件:{}", deleteDic, deleteNames);
320 | alistClient.deleteFile(new DeleteFileReqDTO(deleteDic, deleteNames));
321 | telegramService.sendMessage(telegramBotProperties.getChatId(), messageJoiner.toString());
322 | }
323 |
324 | /**
325 | * 提取项路径
326 | *
327 | * @param input 输入
328 | * @return {@link List }<{@link String }>
329 | */
330 | private List extractItemPaths(String input) {
331 | List paths = new ArrayList<>();
332 | String[] lines = input.split("\n");
333 | boolean isMountPathSection = false;
334 |
335 | for (String line : lines) {
336 | line = line.trim();
337 |
338 | if (line.startsWith("Mount Paths:")) {
339 | isMountPathSection = true;
340 | continue;
341 | }
342 |
343 | if (isMountPathSection && line.startsWith(configProperties.getAlist().getMediaUrl())) {
344 | if (configProperties.getEncodeStrmPath()) {
345 | paths.add(UrlDecoder.decode(line.substring(configProperties.getAlist().getMediaUrl().length())));
346 | } else {
347 | paths.add(line.substring(configProperties.getAlist().getMediaUrl().length()));
348 | }
349 | }
350 | }
351 | return paths;
352 | }
353 |
354 | /**
355 | * 获取路径中倒数指定层级数的目录/文件名
356 | *
357 | * @param path 原始路径
358 | * @param level 要获取的倒数层级数(例如3表示获取倒数三层)
359 | * @return 倒数层级的字符串列表,若不满足层数则返回空列表
360 | */
361 | public static List getLastSegments(String path, int level) {
362 | if (path == null || path.isEmpty() || level <= 0) {
363 | return Collections.emptyList();
364 | }
365 |
366 | String[] parts = path.split("/");
367 | if (parts.length < level) {
368 | return Collections.emptyList();
369 | }
370 |
371 | return new ArrayList<>(Arrays.asList(parts).subList(parts.length - level, parts.length));
372 | }
373 |
374 | /**
375 | * 截取路径中前面 N - level 层(正向)
376 | * 例如路径有10层,level=2,则保留前8层
377 | *
378 | * @param path 原始路径
379 | * @param level 倒向层级数(表示要排除最后几层)
380 | * @return 拼接后的子路径字符串
381 | */
382 | public static String getPrefixByLevel(String path, int level) {
383 | if (path == null || path.isEmpty() || level < 0) {
384 | return "";
385 | }
386 |
387 | String[] parts = path.split("/");
388 | int keepLength = parts.length - level;
389 | if (keepLength <= 0) {
390 | return "";
391 | }
392 |
393 | String[] subParts = Arrays.copyOfRange(parts, 0, keepLength);
394 | String result = String.join("/", subParts);
395 | return !path.startsWith("/") ? "/" + result : result;
396 | }
397 |
398 | private HandleFileDTO createHandleFileDTO() {
399 | HandleFileDTO dto = new HandleFileDTO();
400 | dto.setFiles(new HashSet<>());
401 | dto.setIsSingleTask(true);
402 | return dto;
403 | }
404 | }
405 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/service/impl/TelegramBotServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.service.impl;
2 |
3 | import com.zq.media.tools.service.ITelegramBotService;
4 | import com.zq.media.tools.util.FileUtil;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.dromara.hutool.core.io.file.FileTypeUtil;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.stereotype.Service;
9 | import org.telegram.telegrambots.meta.api.methods.botapimethods.BotApiMethod;
10 | import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
11 | import org.telegram.telegrambots.meta.api.methods.send.SendPhoto;
12 | import org.telegram.telegrambots.meta.api.objects.InputFile;
13 | import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
14 | import org.telegram.telegrambots.meta.generics.TelegramClient;
15 |
16 | import java.io.File;
17 | import java.io.Serializable;
18 |
19 | /**
20 | * tg
21 | *
22 | * @author zhaoqiang
23 | * @version 1.0
24 | * @date 2025-4-15 16:19
25 | */
26 | @Slf4j
27 | @Service
28 | public class TelegramBotServiceImpl implements ITelegramBotService {
29 |
30 | @Autowired(required = false)
31 | private TelegramClient telegramClient;
32 |
33 | /**
34 | * 发送消息
35 | *
36 | * @param chatId 聊天id
37 | * @param message 消息
38 | */
39 | @Override
40 | public void sendMessage(Long chatId, String message) {
41 | sendMessage(chatId, message, null);
42 | }
43 |
44 | /**
45 | * 发送消息(markdown语法)
46 | *
47 | * @param chatId 聊天id
48 | * @param message 消息
49 | */
50 | @Override
51 | public void sendMarkdownMessage(Long chatId, String message) {
52 | sendMessage(chatId, message, "Markdown");
53 | }
54 |
55 | private void sendMessage(Long chatId, String message, String parseMode) {
56 | log.debug("发送tg消息:{},{}", chatId, message);
57 | SendMessage sendMessage = SendMessage
58 | .builder()
59 | .chatId(chatId)
60 | .text(message)
61 | .parseMode(parseMode)
62 | .build();
63 | sendMessage.enableHtml(true);
64 | try {
65 | telegramClient.execute(sendMessage);
66 | } catch (TelegramApiException e) {
67 | log.error("发送tg消息失败, {}", sendMessage, e);
68 | }
69 | }
70 |
71 |
72 | /**
73 | * 发送文件
74 | *
75 | * @param chatId 聊天id
76 | * @param file 文件
77 | */
78 | @Override
79 | public void sendFile(Long chatId, File file) {
80 | sendFile(chatId, file, null);
81 | }
82 |
83 | /**
84 | * 发送文件
85 | *
86 | * @param chatId 聊天id
87 | * @param file 文件
88 | * @param caption 标题
89 | */
90 | @Override
91 | public void sendFile(Long chatId, File file, String caption) {
92 | sendFile(chatId, file, caption, null);
93 | }
94 |
95 | /**
96 | * 发送文件(markdown语法)
97 | *
98 | * @param chatId 聊天id
99 | * @param file 文件
100 | * @param caption 标题
101 | */
102 | @Override
103 | public void sendMarkdownFile(Long chatId, File file, String caption) {
104 | sendFile(chatId, file, caption, "Markdown");
105 | }
106 |
107 | private void sendFile(Long chatId, File file, String caption, String parseMode) {
108 | String type = FileTypeUtil.getType(file);
109 | log.debug("发送tg文件消息:{},文件类型:{},文件名:{},文件描述:{}", chatId, type, file.getName(), caption);
110 | try {
111 | if (FileUtil.isImage(type)) {
112 | SendPhoto sendPhoto = SendPhoto
113 | .builder()
114 | .chatId(chatId)
115 | .photo(new InputFile(file))
116 | .caption(caption)
117 | .parseMode(parseMode)
118 | .build();
119 | telegramClient.execute(sendPhoto);
120 | }
121 | } catch (TelegramApiException e) {
122 | log.error("发送tg消息失败:{},{}", chatId, file.getName(), e);
123 | }
124 | }
125 |
126 | /**
127 | * 发送消息
128 | *
129 | * @param method 消息
130 | */
131 | @Override
132 | public > void execute(BotMethod method) {
133 | try {
134 | log.debug("发送tg消息:{}", method);
135 | telegramClient.execute(method);
136 | } catch (TelegramApiException e) {
137 | log.error("发送tg消息失败:{}", method, e);
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/task/Driver115LifeMonitor.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.task;
2 |
3 | import com.zq.media.tools.driver.Driver115;
4 | import com.zq.media.tools.properties.ConfigProperties;
5 | import lombok.RequiredArgsConstructor;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
8 | import org.springframework.scheduling.annotation.Scheduled;
9 | import org.springframework.stereotype.Component;
10 |
11 | import java.time.LocalDateTime;
12 |
13 | /**
14 | * 115生活监听
15 | * // TODO 删除文件时未删除文件夹
16 | *
17 | * @author zhaoqiang
18 | * @version 1.0
19 | * @date 2024-11-12 14:19
20 | */
21 | @Slf4j
22 | @Component
23 | @RequiredArgsConstructor
24 | @ConditionalOnProperty(prefix = "app.driver115", name = "enabled", havingValue = "true")
25 | public class Driver115LifeMonitor {
26 |
27 | private final Driver115 driver115;
28 | private final ConfigProperties configProperties;
29 |
30 | /**
31 | * 监控115生活事件
32 | * 定时任务,监控115网盘的文件变化并同步到本地
33 | */
34 | @Scheduled(cron = "0 */${app.driver115.intervalMinutes} * * * ?")
35 | public void monitorLifeEvents() {
36 | try {
37 | driver115.handleBehavior(LocalDateTime.now().minusMinutes(configProperties.getDriver115().getIntervalMinutes()), LocalDateTime.now());
38 | } catch (Exception e) {
39 | log.error("监控生活事件时发生错误: ", e);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/telegram/handler/CommandHandler.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.telegram.handler;
2 |
3 | import com.zq.common.util.CollectionUtil;
4 | import com.zq.media.tools.properties.TelegramBotProperties;
5 | import com.zq.media.tools.service.ITelegramBotService;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.telegram.telegrambots.extensions.bots.commandbot.CommandLongPollingTelegramBot;
8 | import org.telegram.telegrambots.extensions.bots.commandbot.commands.BotCommand;
9 | import org.telegram.telegrambots.extensions.bots.commandbot.commands.helpCommand.HelpCommand;
10 | import org.telegram.telegrambots.longpolling.interfaces.LongPollingUpdateConsumer;
11 | import org.telegram.telegrambots.longpolling.starter.SpringLongPollingBot;
12 | import org.telegram.telegrambots.meta.api.methods.commands.SetMyCommands;
13 | import org.telegram.telegrambots.meta.api.objects.Update;
14 | import org.telegram.telegrambots.meta.api.objects.message.Message;
15 | import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
16 | import org.telegram.telegrambots.meta.generics.TelegramClient;
17 |
18 | import java.util.List;
19 |
20 | /**
21 | * 命令处理器
22 | *
23 | * @author zhaoqiang
24 | * @version 1.0
25 | * @date 2025-4-27 11:53
26 | */
27 | @Slf4j
28 | public class CommandHandler extends CommandLongPollingTelegramBot implements SpringLongPollingBot {
29 |
30 | private final ITelegramBotService telegramService;
31 | private final TelegramBotProperties telegramBotProperties;
32 |
33 | public CommandHandler(TelegramClient telegramClient, ITelegramBotService telegramBotService, TelegramBotProperties telegramBotProperties,
34 | List commands) {
35 | super(telegramClient, false, () -> "");
36 | this.telegramService = telegramBotService;
37 | this.telegramBotProperties = telegramBotProperties;
38 | commands.forEach(this::register);
39 | // 设置命令
40 | setCommands(commands);
41 | registerDefaultAction((tgClient, message) -> {
42 | telegramBotService.sendMessage(message.getChatId(), "Unknown command. Here comes some help");
43 | telegramBotService.sendMessage(message.getChatId(), HelpCommand.getHelpText(getRegisteredCommands()));
44 | });
45 | }
46 |
47 | private void setCommands(List commands) {
48 | List botCommands = CollectionUtil.convertList(commands,
49 | botCommand -> new org.telegram.telegrambots.meta.api.objects.commands.BotCommand(botCommand.getCommandIdentifier(), botCommand.getDescription()));
50 | SetMyCommands setMyCommands = new SetMyCommands(botCommands);
51 | try {
52 | telegramClient.execute(setMyCommands);
53 | } catch (TelegramApiException e) {
54 | log.error("设置命令失败", e);
55 | }
56 | }
57 |
58 | @Override
59 | public void processNonCommandUpdate(Update update) {
60 | if (update.hasMessage()) {
61 | Message message = update.getMessage();
62 | if (message.hasText()) {
63 | telegramService.sendMessage(message.getChatId(), "You said: " + message.getText());
64 | }
65 | }
66 | }
67 |
68 | @Override
69 | public String getBotToken() {
70 | return telegramBotProperties.getToken();
71 | }
72 |
73 | @Override
74 | public LongPollingUpdateConsumer getUpdatesConsumer() {
75 | return this;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/telegram/handler/SgysTelegramBot.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.telegram.handler;
2 |
3 | import com.zq.media.tools.enums.TelegramBotCommand;
4 | import com.zq.media.tools.properties.TelegramBotProperties;
5 | import com.zq.media.tools.service.ITelegramBotService;
6 | import lombok.RequiredArgsConstructor;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.dromara.hutool.core.io.file.FileUtil;
9 | import org.dromara.hutool.core.io.resource.Resource;
10 | import org.dromara.hutool.core.io.resource.ResourceUtil;
11 | import org.telegram.telegrambots.longpolling.BotSession;
12 | import org.telegram.telegrambots.longpolling.interfaces.LongPollingUpdateConsumer;
13 | import org.telegram.telegrambots.longpolling.starter.AfterBotRegistration;
14 | import org.telegram.telegrambots.longpolling.starter.SpringLongPollingBot;
15 | import org.telegram.telegrambots.longpolling.util.LongPollingSingleThreadUpdateConsumer;
16 | import org.telegram.telegrambots.meta.api.objects.Update;
17 |
18 | import java.io.File;
19 |
20 | /**
21 | * 默认处理器
22 | *
23 | * @author zhaoqiang
24 | * @version 1.0
25 | * @date 2025-4-15 14:20
26 | */
27 | @Slf4j
28 | @RequiredArgsConstructor
29 | public class SgysTelegramBot implements SpringLongPollingBot, LongPollingSingleThreadUpdateConsumer {
30 |
31 | private final ITelegramBotService telegramService;
32 | private final TelegramBotProperties telegramBotProperties;
33 |
34 | @Override
35 | public String getBotToken() {
36 | return telegramBotProperties.getToken();
37 | }
38 |
39 | @Override
40 | public LongPollingUpdateConsumer getUpdatesConsumer() {
41 | return this;
42 | }
43 |
44 | @Override
45 | public void consume(Update update) {
46 | if (update.hasMessage() && update.getMessage().hasText()) {
47 | String messageText = update.getMessage().getText();
48 | long chatId = update.getMessage().getChatId();
49 | TelegramBotCommand botCommand = TelegramBotCommand.of(messageText);
50 | log.info("Received command: {} from chatId: {}", botCommand, chatId);
51 | switch (botCommand) {
52 | case START -> telegramService.sendMessage(chatId, "启动成功");
53 | case STOP -> telegramService.sendMessage(chatId, "停止成功");
54 | case PIC -> {
55 | Resource resource = ResourceUtil.getResource("static/pic.jpg");
56 | File file = FileUtil.writeFromStream(resource.getStream(), FileUtil.createTempFile(".jpg", true));
57 | telegramService.sendFile(chatId, file);
58 | }
59 | default -> telegramService.sendMessage(chatId, "未知命令");
60 | }
61 | }
62 | }
63 |
64 | @AfterBotRegistration
65 | public void afterRegistration(BotSession botSession) {
66 | try {
67 | log.info("Registered bot running state is: {}", botSession.isRunning());
68 | if (!botSession.isRunning()) {
69 | log.error("Bot session is not running. Please check the configuration and try again.");
70 | }
71 | } catch (Exception e) {
72 | log.error("Error during bot registration: ", e);
73 | }
74 | }
75 |
76 | }
--------------------------------------------------------------------------------
/src/main/java/com/zq/media/tools/telegram/handler/command/GetImageCommand.java:
--------------------------------------------------------------------------------
1 | package com.zq.media.tools.telegram.handler.command;
2 |
3 | import com.fasterxml.jackson.core.type.TypeReference;
4 | import com.zq.common.util.JsonUtil;
5 | import com.zq.media.tools.service.ITelegramBotService;
6 | import lombok.SneakyThrows;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.dromara.hutool.core.io.file.FileUtil;
9 | import org.dromara.hutool.core.net.url.UrlEncoder;
10 | import org.dromara.hutool.core.util.RandomUtil;
11 | import org.dromara.hutool.http.client.HttpDownloader;
12 | import org.jsoup.Jsoup;
13 | import org.jsoup.nodes.Document;
14 | import org.jsoup.nodes.Element;
15 | import org.jsoup.select.Elements;
16 | import org.telegram.telegrambots.extensions.bots.commandbot.commands.BotCommand;
17 | import org.telegram.telegrambots.meta.api.objects.User;
18 | import org.telegram.telegrambots.meta.api.objects.chat.Chat;
19 | import org.telegram.telegrambots.meta.generics.TelegramClient;
20 |
21 | import java.io.File;
22 | import java.nio.charset.StandardCharsets;
23 | import java.util.ArrayList;
24 | import java.util.List;
25 | import java.util.Map;
26 |
27 | /**
28 | * 获取图片命令,通过必应搜索
29 | * /getImage
30 | *
31 | * @author zhaoqiang
32 | * @version 1.0
33 | * @date 2025-4-27 13:48
34 | */
35 | @Slf4j
36 | public class GetImageCommand extends BotCommand {
37 |
38 | private final ITelegramBotService telegramBotService;
39 |
40 | public GetImageCommand(ITelegramBotService telegramBotService) {
41 | super("getImage", "根据关键字随机获取一张图片,不传入关键字随机获取一张美女图片");
42 | this.telegramBotService = telegramBotService;
43 | }
44 |
45 | @Override
46 | public void execute(TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
47 | List imageUrls;
48 | if (arguments != null && arguments.length > 0) {
49 | imageUrls = fetchImageUrls(arguments[0]);
50 | } else {
51 | imageUrls = fetchImageUrls("美女");
52 | }
53 | String imageUrl = imageUrls.get(RandomUtil.randomInt(imageUrls.size()));
54 | File file = FileUtil.createTempFile(".jpg", true);
55 | HttpDownloader.of(imageUrl).downloadFile(file);
56 | telegramBotService.sendFile(chat.getId(), file);
57 | }
58 |
59 | @SneakyThrows
60 | private List fetchImageUrls(String keyword) {
61 | List imageUrls = new ArrayList<>();
62 | String encodedKeyword = UrlEncoder.encodeQuery(keyword, StandardCharsets.UTF_8);
63 |
64 | String url = "https://www.bing.com/images/async?q=" + encodedKeyword +
65 | "&first=0&count=20&adlt=off&lostate=r";
66 |
67 | Document doc = Jsoup.connect(url)
68 | .userAgent("Mozilla/5.0")
69 | .header("Accept-Language", "zh-CN,zh;q=0.9")
70 | .get();
71 |
72 | Elements links = doc.select("a.iusc");
73 | for (Element link : links) {
74 | String attr = link.attr("m");
75 | if (!attr.isEmpty()) {
76 | try {
77 | Map json = JsonUtil.parseObject(attr, new TypeReference