├── .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 | ![quark-auto-save](./img/quark-auto-save.png) 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 | ![任务配置1](./img/任务配置1.png) 142 | ![任务配置2](./img/任务配置2.png) 143 | 144 | #### cloud189-auto-save配置 145 | 146 | config.json文件配置推送路径 147 | ![config.json推送配置](./img/cloud189-auto-save.png) 148 | ![任务配置](./img/cloud189-auto-save1.png) 149 | 150 | #### emby通知配置 151 | 152 | ##### 神医通知 153 | 154 | ![神医通知](./img/emby-shenyi.png) 155 | ![神医通知](./img/emby-shenyi2.png) 156 | 157 | ##### tg机器人通知效果 158 | 159 | ![最爱更新](./img/tg通知/最爱更新.png) 160 | ![片头片尾更新](./img/tg通知/片头片尾更新.png) 161 | ![深度删除](./img/tg通知/深度删除.png) 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>() { 78 | }); 79 | String imageUrl = (String) json.get("murl"); 80 | if (imageUrl != null && imageUrl.startsWith("http")) { 81 | imageUrls.add(imageUrl); 82 | } 83 | } catch (Exception ignored) { 84 | // skip malformed json 85 | } 86 | } 87 | } 88 | 89 | return imageUrls; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/zq/media/tools/util/FileUtil.java: -------------------------------------------------------------------------------- 1 | package com.zq.media.tools.util; 2 | 3 | import org.dromara.hutool.core.array.ArrayUtil; 4 | 5 | /** 6 | * @author zhaoqiang 7 | * @version 1.0 8 | * @date 2025-4-15 17:28 9 | */ 10 | public class FileUtil { 11 | 12 | /** 13 | * 判断指定文件是否图像 14 | * 15 | * @param fileType 文件类型 16 | * @return boolean 17 | */ 18 | public static boolean isImage(String fileType) { 19 | String[] imageSuffix = new String[]{"jpg", ".peg", "png", "gif", "bmp", "webp"}; 20 | return ArrayUtil.contains(imageSuffix, fileType); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/zq/media/tools/util/MediaUtil.java: -------------------------------------------------------------------------------- 1 | package com.zq.media.tools.util; 2 | 3 | import java.nio.file.Path; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | 7 | /** 8 | * 媒体工具类 9 | * 10 | * @author zhaoqiang 11 | * @version 1.0 12 | * @date 2025-4-25 9:19 13 | */ 14 | public class MediaUtil { 15 | 16 | /** 17 | * 判断两个文件名是否包含相同的季数和集数(格式 SxxExx)。 18 | * 19 | * @param fileName1 第一个文件名 20 | * @param fileName2 第二个文件名 21 | * @return 如果季数和集数一致,返回 true;否则返回 false 22 | */ 23 | public static boolean areEpisodesEqual(String fileName1, String fileName2) { 24 | // 正则表达式匹配 SxxExx 格式 25 | String regex = "S(\\d{2})E(\\d+)"; 26 | Pattern pattern = Pattern.compile(regex); 27 | 28 | // 提取第一个文件名中的季数和集数 29 | Matcher matcher1 = pattern.matcher(fileName1); 30 | Matcher matcher2 = pattern.matcher(fileName2); 31 | 32 | if (matcher1.find() && matcher2.find()) { 33 | // 获取季数和集数 34 | String season1 = matcher1.group(1); 35 | String episode1 = matcher1.group(2); 36 | String season2 = matcher2.group(1); 37 | String episode2 = matcher2.group(2); 38 | 39 | // 比较季数和集数是否相等 40 | return season1.equals(season2) && episode1.equals(episode2); 41 | } 42 | 43 | // 如果任一文件名不包含 SxxExx 格式,则视为不匹配 44 | return false; 45 | } 46 | 47 | /** 48 | * 判断文件名是否是剧集(格式 SxxExx)。 49 | * 50 | * @param fileName 文件名 51 | * @return 如果是剧集,返回 true;否则返回 false 52 | * } 53 | */ 54 | public static boolean isEpisodes(String fileName) { 55 | // 正则表达式匹配 SxxExx 格式 56 | String regex = "[Ss](\\d{2})[Ee](\\d+)"; 57 | Pattern pattern = Pattern.compile(regex); 58 | 59 | // 提取文件名中的季数和集数 60 | Matcher matcher = pattern.matcher(fileName); 61 | 62 | return matcher.find(); 63 | } 64 | 65 | /** 66 | * 获取剧集 67 | * 68 | * @param fileName 文件名 69 | * @return {@link String } 70 | */ 71 | public static String getEpisodes(String fileName) { 72 | String regex = "[Ss](\\d{2})[Ee](\\d+)"; 73 | Pattern pattern = Pattern.compile(regex); 74 | Matcher matcher = pattern.matcher(fileName); 75 | if (matcher.find()) { 76 | String season = matcher.group(1); 77 | String episode = matcher.group(2); 78 | return "S" + season + "E" + episode; 79 | } 80 | return null; 81 | } 82 | 83 | /** 84 | * 获取集数 85 | * 86 | * @param fileName 文件名 87 | * @return {@link String } 88 | */ 89 | public static int getEpisode(String fileName) { 90 | String regex = "[Ss](\\d{2})[Ee](\\d+)"; 91 | Pattern pattern = Pattern.compile(regex); 92 | Matcher matcher = pattern.matcher(fileName); 93 | if (matcher.find()) { 94 | return Integer.parseInt(matcher.group(2)); 95 | } 96 | return -1; 97 | } 98 | 99 | /** 100 | * 获取季数 101 | * 102 | * @param fileName 文件名 103 | * @return {@link String } 104 | */ 105 | public static String getSeason(String fileName) { 106 | String regex = "[Ss](\\d{2})[Ee](\\d+)"; 107 | Pattern pattern = Pattern.compile(regex); 108 | Matcher matcher = pattern.matcher(fileName); 109 | if (matcher.find()) { 110 | return matcher.group(1); 111 | } 112 | return null; 113 | } 114 | 115 | /** 116 | * 格式化视频大小 117 | * 118 | * @param size 视频大小(比特) 119 | * @return 格式化后的视频大小字符串 120 | */ 121 | public static String formatSize(double size) { 122 | final double gb = 1024 * 1024 * 1024; 123 | final double mb = 1024 * 1024; 124 | 125 | if (size >= gb) { 126 | return String.format("%.2f GB", size / gb); 127 | } else { 128 | return String.format("%.2f MB", size / mb); 129 | } 130 | } 131 | 132 | /** 133 | * 格式化视频码率 134 | * 135 | * @param bitrate 视频码率(比特/秒) 136 | * @return 格式化后的视频码率字符串 137 | */ 138 | public static String formatBitrate(int bitrate) { 139 | final double MBPS = 1024 * 1024; 140 | double mbps = bitrate / MBPS; 141 | 142 | // 检查是否为整数 143 | if (mbps == Math.floor(mbps)) { 144 | return String.format("%.0f", mbps); 145 | } else { 146 | return String.format("%.1f", mbps); 147 | } 148 | } 149 | 150 | /** 151 | * 检查是否匹配到季数 152 | * 153 | * @param input 输入字符串,例如 "第 1 季","第1季" 154 | * @return 是否成功匹配到季数 155 | */ 156 | public static boolean isSeasonNumberMatched(String input) { 157 | String regex = "第\\s*.*?\\s*季"; 158 | Pattern pattern = Pattern.compile(regex); 159 | Matcher matcher = pattern.matcher(input); 160 | 161 | return matcher.find(); 162 | } 163 | 164 | /** 165 | * 检查是否匹配到集数 166 | * 167 | * @param input 输入字符串,例如 "第 1 集","第一集" 168 | * @return 是否成功匹配到集数 169 | */ 170 | public static boolean isEpisodeNumberMatched(String input) { 171 | String regex = "第\\s*.*?\\s*集"; 172 | Pattern pattern = Pattern.compile(regex); 173 | Matcher matcher = pattern.matcher(input); 174 | 175 | return matcher.find(); 176 | } 177 | 178 | /** 179 | * 是视频文件 180 | * 181 | * @param file 文件 182 | * @return boolean 183 | */ 184 | public static boolean isVideoFile(Path file) { 185 | String fileName = file.getFileName().toString().toLowerCase(); 186 | return isVideoFile(fileName); 187 | } 188 | 189 | /** 190 | * 是视频文件 191 | * 192 | * @param fileName 文件名 193 | * @return boolean 194 | */ 195 | public static boolean isVideoFile(String fileName) { 196 | return fileName.endsWith(".mp4") || fileName.endsWith(".avi") || 197 | fileName.endsWith(".mkv") || fileName.endsWith(".mov") || 198 | fileName.endsWith(".wmv") || fileName.endsWith(".flv") || 199 | fileName.endsWith(".rmvb") || fileName.endsWith(".iso") || 200 | fileName.endsWith(".ts") || fileName.endsWith(".bdmv"); 201 | } 202 | 203 | } 204 | -------------------------------------------------------------------------------- /src/main/java/com/zq/media/tools/util/StrmUtil.java: -------------------------------------------------------------------------------- 1 | package com.zq.media.tools.util; 2 | 3 | import com.zq.media.tools.properties.ConfigProperties; 4 | import lombok.SneakyThrows; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.dromara.hutool.core.io.file.FileNameUtil; 7 | import org.dromara.hutool.core.io.file.FileUtil; 8 | import org.dromara.hutool.core.net.url.UrlEncoder; 9 | import org.dromara.hutool.http.client.HttpDownloader; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.nio.charset.StandardCharsets; 14 | import java.nio.file.Path; 15 | import java.nio.file.Paths; 16 | 17 | /** 18 | * @author zhaoqiang 19 | * @version 1.0 20 | * @date 2024-11-15 13:45 21 | */ 22 | @Slf4j 23 | @Component 24 | public class StrmUtil { 25 | 26 | private static ConfigProperties configProperties; 27 | 28 | /** 29 | * 生成 STRM 文件 30 | * 31 | * @param filePath 文件路径 32 | * @return {@link String } 33 | */ 34 | public static String generateStrmFiles(Path filePath) { 35 | Path strmPath = Paths.get(configProperties.getServer().getBasePath(), filePath.getParent().toString(), FileNameUtil.mainName(filePath.getFileName().toString()) + ".strm"); 36 | String videoRelativeUrl = configProperties.getAlist().getMediaUrl() + filePath.toString().replace("\\", "/").replace("//", "/"); 37 | FileUtil.writeUtf8String(configProperties.getEncodeStrmPath() ? UrlEncoder.encodeQuery(videoRelativeUrl, StandardCharsets.UTF_8) : videoRelativeUrl, strmPath.toString()); 38 | return strmPath.toString(); 39 | } 40 | 41 | /** 42 | * 重写生成 STRM 文件内容 43 | * 44 | * @param strmFilePath strm文件路径 45 | * @return {@link String } 46 | */ 47 | public static void writeStrmFiles(Path strmFilePath, String path) { 48 | String videoRelativeUrl = configProperties.getAlist().getMediaUrl() + path.replace("\\", "/").replace("//", "/"); 49 | FileUtil.writeUtf8String(configProperties.getEncodeStrmPath() ? UrlEncoder.encodeQuery(videoRelativeUrl, StandardCharsets.UTF_8) : videoRelativeUrl, strmFilePath.toString()); 50 | } 51 | 52 | /** 53 | * 从指定 URL 下载文件,并通过 Cookie 进行身份验证 54 | * 55 | * @param url 网络地址 56 | * @param cookie 需要传入的 Cookie 信息 57 | * @param savePath 保存路径 58 | */ 59 | @SneakyThrows 60 | public static void downloadFile(String url, String cookie, Path savePath) { 61 | HttpDownloader.of(url) 62 | .header("Cookie", cookie) 63 | .header("User-Agent", configProperties.getDriver115().getUserAgent()) 64 | .setTimeout(10000) 65 | .downloadFile(savePath.toFile()); 66 | } 67 | 68 | @Autowired 69 | public void setConfigProperties(ConfigProperties configProperties) { 70 | StrmUtil.configProperties = configProperties; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/resources/Dockerfile: -------------------------------------------------------------------------------- 1 | #基础镜像 2 | FROM sgys.zq.virtual.com.cn:8082/jdk17-python3.12:latest 3 | 4 | #工作目录 5 | WORKDIR /app 6 | 7 | # 安装 Python clouddrive依赖 8 | RUN pip install --no-cache-dir -U clouddrive 9 | 10 | #添加jar包 11 | COPY sgys-media-tools.jar app.jar 12 | #添加logback配置文件 13 | COPY config/ config/ 14 | # 添加lib依赖 15 | COPY lib/ lib/ 16 | 17 | # 将 Python 脚本复制到容器中(如果有多个脚本,可以调整路径) 18 | COPY python/ python/ 19 | 20 | #提示映射端口 21 | EXPOSE 8080 22 | ENV APP_PORT=8080 23 | ENV JAVA_OPTS="-Dloader.path=lib -Dfile.encoding=utf-8 -Xms256m -Xmx256m -Xss256k -XX:MetaspaceSize=128m -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:+AlwaysPreTouch -XX:-OmitStackTraceInFastThrow" 24 | #容器内启动命令 25 | CMD java $JAVA_OPTS -jar /app/app.jar ${SPRINGBOOT_OPTS} -------------------------------------------------------------------------------- /src/main/resources/application-media.yml: -------------------------------------------------------------------------------- 1 | app: 2 | apiRateLimit: 1 # API 速率限制 3 | downloadMediaFile: true # 是否下载媒体文件 4 | encodeStrmPath: true # 是否编码strm路径 5 | # alist配置 6 | alist: 7 | token: alist-39c51876b-956d-4f2c-8243-e280439243YcXc2424ivJ6J3oHzgXVItn245222222cG7goPpUIX42HP2T44ClK06rhbcsb8AlTkT # alist token 8 | url: http://127.0.0.1:5244 # alist地址 9 | mediaUrl: http://127.0.0.1:5244/d # alist媒体地址 10 | driver115Path: /115网盘 # alist115网盘路径 11 | # 待处理媒体路径 12 | # mediaPath: 13 | # - /115网盘/电视剧 14 | # - /115网盘/电影 15 | # - /115网盘/综艺 16 | # - /115网盘/纪录片 17 | # - /115网盘/节目晚会 18 | # 刮削路径 19 | scrapPath: 20 | "[斗罗大陆2绝世唐门]": /115网盘/未刮削/连载动漫/斗罗大陆Ⅱ绝世唐门 (2023)/Season 1 21 | "[斗破苍穹]": /115网盘/未刮削/连载动漫/斗破苍穹 (2017)/Season 5 22 | "[吞噬星空]" : /115网盘/未刮削/连载动漫/吞噬星空 (2020)/Season 1 23 | "[完美世界]": /115网盘/未刮削/连载动漫/完美世界 (2021)/Season 1 24 | "[遮天]": /115网盘/未刮削/连载动漫/遮天 (2023)/Season 1 25 | "[凡人修仙传]": /115网盘/未刮削/连载动漫/凡人修仙传 (2020)/Season 7 26 | "[棋士]": /天翼云盘/未刮削/连载电视/棋士 (2025)/Season 1 27 | # 文件最终保存路径 28 | serializedTvShow: 29 | "[斗罗大陆Ⅱ绝世唐门 (2023)]": /115网盘/动漫/国漫/斗罗大陆Ⅱ绝世唐门 (2023)/Season 1 30 | "[斗破苍穹 (2017)]": /115网盘/动漫/国漫/斗破苍穹 (2017)/Season 5 31 | "[吞噬星空 (2020)]": /115网盘/动漫/国漫/吞噬星空 (2020)/Season 1 32 | "[完美世界 (2021)]": /115网盘/动漫/国漫/完美世界 (2021)/Season 1 33 | "[棋士 (2025)]": /天翼云盘/电视剧/棋士 (2025)/Season 1 34 | # 服务器配置 35 | server: 36 | basePath: /mnt/strm # 媒体文件本地保存路径 37 | driver115Path: /mnt/strm/115网盘 # 115网盘本地保存路径 38 | # 115网盘配置 39 | driver115: 40 | enabled: false # 是否开启115网盘生活监听 41 | intervalMinutes: 30 # 监听间隔 42 | cookie: UID=341890318_D1_1740142749;CID=cae3a0b208kd675e45f2sdc2245dda4d9;SEID=7405c2ad1b10c90646c1342378b8e403ab543ce17b243ca99b0cf5b568ed8dd213132c1d62309e76729bbad2ec;KID=7297fc8a238a0db9ff6b9b8916dff66d 43 | # 忽略文件夹 44 | ignoreFolders: 45 | - /未刮削 46 | - /音乐 47 | - /云下载 48 | - /我的接收 49 | - /游戏 50 | driverQuark: 51 | handleFolders: 52 | - /接收连载电视 53 | - /接收连载动漫 54 | driverCloud189: 55 | handleFolders: 56 | - /接收连载电视 57 | - /接收连载动漫 58 | # tinymediamanger配置 59 | ttm: 60 | enabled: false # 是否开启ttm刮削 61 | url: http://127.0.0.1:7878/api/tvshows 62 | apiKey: 36841ce7c-8917-4a75-af06-6b3211bafbe5 63 | scrapTime: 60 # 刮削时间 64 | # cd2配置 65 | cloud-drive: 66 | enabled: false # 是否开启cd2刮削,不开启则默认刮削为alist挂载到本地的路径 67 | url: http://127.0.0.1:19798 68 | username: 1144804894@qq.com 69 | password: 123456 70 | emby: 71 | url: http://127.0.0.1:8096 72 | apiKey: 213214adsagfgasdgasga 73 | user-id: 123kskjjfafasf 74 | episodeGroup: 75 | - 凡人修仙传|S01E135-S01E152 -> S07E11-S07E28 -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: media 4 | main: 5 | allow-bean-definition-overriding: true 6 | datasource: 7 | dynamic: 8 | primary: master 9 | datasource: 10 | master: 11 | # driver-class-name: com.mysql.cj.jdbc.Driver 12 | # url: jdbc:mysql://127.0.0.1:3306/media?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai 13 | url: jdbc:h2:file:./db/media;AUTO_RECONNECT=TRUE 14 | username: sa 15 | password: 16 | driver-class-name: org.h2.Driver 17 | sql: 18 | init: 19 | schema-locations: classpath:config/init.sql 20 | mode: never 21 | h2: 22 | console: 23 | enabled: true 24 | settings: 25 | web-allow-others: true 26 | 27 | mybatis-plus: 28 | global-config: 29 | banner: false 30 | 31 | logging: 32 | level: 33 | com.zq.media.tools.feign: debug 34 | com.zq.core.biz.rpc.interceptor.HttpLoggingInterceptor: debug 35 | com.zq.core.web.interceptor.ApiAccessLogInterceptor: debug 36 | 37 | --- 38 | # telegram机器人配置 39 | telegrambots: 40 | enabled: false 41 | token: 81997859:AASSEm9iV_8Q23216xc-dsadBpn2Xwdaysj415aZzc 42 | chat-id: 56136141 43 | proxy: 44 | hostname: 192.168.2.3 45 | port: 7890 46 | 47 | --- 48 | app: 49 | web: 50 | admin-api: 51 | prefix: / 52 | controller: "**.controller.admin.**" 53 | -------------------------------------------------------------------------------- /src/main/resources/config/init.sql: -------------------------------------------------------------------------------- 1 | -- 创建 media_115 表 2 | CREATE TABLE IF NOT EXISTS MEDIA_115 3 | ( 4 | id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', 5 | file_id VARCHAR(64) COMMENT '文件ID', 6 | parent_id VARCHAR(64) COMMENT '父文件ID', 7 | path VARCHAR(2000) NOT NULL COMMENT '路径', 8 | file_name VARCHAR(500) NOT NULL COMMENT '文件名', 9 | pick_code VARCHAR(64) COMMENT '选取代码', 10 | sha1 CHAR(40) COMMENT 'sha1', 11 | ext VARCHAR(32) COMMENT '文件扩展名', 12 | create_time TIMESTAMP NOT NULL COMMENT '创建时间', 13 | update_time TIMESTAMP COMMENT '更新时间' 14 | ); 15 | 16 | -- 为表添加注释 17 | COMMENT ON TABLE MEDIA_115 IS '媒体文件表'; 18 | -------------------------------------------------------------------------------- /src/main/resources/config/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 9 | 10 | 11 | 12 | ${PATTERN_DEFAULT} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ${PATTERN_FILE} 21 | 22 | 23 | ${LOG_FILE} 24 | 25 | 26 | ${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz} 27 | 28 | 29 | ${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false} 30 | 31 | ${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB} 32 | 33 | ${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0} 34 | 35 | ${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30} 36 | 37 | 38 | 39 | 40 | 41 | 0 42 | 43 | 256 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/main/resources/static/pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShangGuanYunShun/sgys-media-tools/c265f47093706232dd26a61e959b637f3b5cc232/src/main/resources/static/pic.jpg --------------------------------------------------------------------------------