mirror of
https://github.com/d0zingcat/MoviePilot-Plugins.git
synced 2026-05-13 23:16:47 +00:00
Merge remote-tracking branch 'origin/main'
# Conflicts: # package.json # plugins/brushflow/__init__.py
This commit is contained in:
@@ -17,7 +17,7 @@ MoviePilot官方插件市场:https://github.com/jxxghp/MoviePilot-Plugins
|
||||
- 插件命名请勿与官方库插件中的插件冲突,否则会在MoviePilot版本升级时被官方插件覆盖。
|
||||
|
||||
### 4. 依赖
|
||||
- 可在插件目录中放置`requirement.txt`文件,用于指定插件依赖的第三方库,MoviePilot会在插件安装时自动安装依赖库。
|
||||
- 可在插件目录中放置`requirements.txt`文件,用于指定插件依赖的第三方库,MoviePilot会在插件安装时自动安装依赖库。
|
||||
|
||||
### 5. 界面开发
|
||||
- 插件支持`插件配置`及`详情展示`两个展示页面,通过配置化的方式组装,使用 [Vuetify](https://vuetifyjs.com/) 组件库,所有该组件库有的组件都可以通过Json配置使用。
|
||||
|
||||
BIN
icons/PluginAutoUpgrade.png
Normal file
BIN
icons/PluginAutoUpgrade.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
25
package.json
25
package.json
@@ -198,10 +198,13 @@
|
||||
"CrossSeed": {
|
||||
"name": "青蛙辅种助手",
|
||||
"description": "参考ReseedPuppy和IYUU辅种插件实现自动辅种,支持站点:青蛙、AGSVPT、麒麟、UBits、聆音、憨憨等。",
|
||||
"version": "2.1",
|
||||
"version": "2.2",
|
||||
"icon": "qingwa.png",
|
||||
"author": "233@qingwa",
|
||||
"level": 2
|
||||
"level": 2,
|
||||
"history": {
|
||||
"v2.2": "站点停用后会同步暂停对该站点的辅种"
|
||||
}
|
||||
},
|
||||
"VCBAnimeMonitor": {
|
||||
"name": "整理VCB动漫压制组作品",
|
||||
@@ -238,12 +241,13 @@
|
||||
"BrushFlow": {
|
||||
"name": "站点刷流",
|
||||
"description": "自动托管刷流,将会提高对应站点的访问频率。",
|
||||
"version": "2.7",
|
||||
"version": "2.8",
|
||||
"icon": "brush.jpg",
|
||||
"author": "jxxghp,InfinityPacer",
|
||||
"level": 2,
|
||||
"history": {
|
||||
"v2.7": "优化UI显示以及提升性能",
|
||||
"v2.8": "优化UI显示以及提升性能",
|
||||
"v2.7": "动态删除种子规则调整(请注意查阅插件文档),站点独立配置样式优化、日志优化,修复部分配置项无法配置小数的问题,修复部分场景可能导致重复下载的问题",
|
||||
"v2.6": "修复排除订阅功能",
|
||||
"v2.5": "增加H&R做种时间、下载器监控配置项,刷流前置条件逻辑调整,代理下载种子默认为关闭"
|
||||
}
|
||||
@@ -382,13 +386,12 @@
|
||||
"RemoveLink": {
|
||||
"name": "清理硬链接",
|
||||
"description": "监控目录内文件被删除时,同步删除监控目录内所有和它硬链接的文件",
|
||||
"version": "1.8",
|
||||
"version": "1.6",
|
||||
"icon": "Ombi_A.png",
|
||||
"author": "DzAvril",
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v1.8": "修复空目录删除逻辑",
|
||||
"v1.7": "增加定时清理空目录功能"
|
||||
"v1.6": "提升插件性能"
|
||||
}
|
||||
},
|
||||
"LinkMonitor": {
|
||||
@@ -513,5 +516,13 @@
|
||||
"icon": "Ntfy_A.png",
|
||||
"author": "lethargicScribe",
|
||||
"level": 1
|
||||
},
|
||||
"PluginAutoUpgrade": {
|
||||
"name": "插件自动升级",
|
||||
"description": "定时检测、升级插件。",
|
||||
"version": "1.0",
|
||||
"icon": "PluginAutoUpgrade.png",
|
||||
"author": "hotlcc",
|
||||
"level": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,11 @@ class BrushConfig:
|
||||
|
||||
def __initialize_site_config(self):
|
||||
if not self.site_config:
|
||||
logger.error(f"没有设置站点配置,已关闭站点独立配置并恢复默认配置示例,请检查配置项")
|
||||
self.site_config = self.__get_demo_site_config()
|
||||
self.group_site_configs = {}
|
||||
self.enable_site_config = False
|
||||
return
|
||||
|
||||
# 定义允许覆盖的字段列表
|
||||
allowed_fields = {
|
||||
@@ -132,6 +136,47 @@ class BrushConfig:
|
||||
self.enable_site_config = False
|
||||
self.enabled = False
|
||||
|
||||
@staticmethod
|
||||
def __get_demo_site_config() -> str:
|
||||
desc = ("//以下为配置示例,请参考 "
|
||||
"https://github.com/InfinityPacer/MoviePilot-Plugins/blob/main/README.md "
|
||||
"进行配置,请注意,只需要保留实际配置内容(删除这段)\n")
|
||||
config = """[{
|
||||
"sitename": "站点1",
|
||||
"seed_time": 96,
|
||||
"hr_seed_time": 144
|
||||
}, {
|
||||
"sitename": "站点2",
|
||||
"hr": "yes",
|
||||
"size": "10-500",
|
||||
"seeder": "5-10",
|
||||
"pubtime": "5-120",
|
||||
"seed_time": 96,
|
||||
"save_path": "/downloads/site2",
|
||||
"proxy_download": true,
|
||||
"hr_seed_time": 144
|
||||
}, {
|
||||
"sitename": "站点3",
|
||||
"freeleech": "free",
|
||||
"hr": "yes",
|
||||
"include": "",
|
||||
"exclude": "",
|
||||
"size": "10-500",
|
||||
"seeder": "1",
|
||||
"pubtime": "5-120",
|
||||
"seed_time": 120,
|
||||
"hr_seed_time": 144,
|
||||
"seed_ratio": "",
|
||||
"seed_size": "",
|
||||
"download_time": "",
|
||||
"seed_avgspeed": "",
|
||||
"seed_inactivetime": "",
|
||||
"save_path": "/downloads/site1",
|
||||
"proxy_download": false,
|
||||
"proxy_delete": false
|
||||
}]"""
|
||||
return desc + config
|
||||
|
||||
def get_site_config(self, sitename):
|
||||
"""
|
||||
根据站点名称获取特定的BrushConfig实例。如果没有找到站点特定的配置,则返回全局的BrushConfig实例。
|
||||
@@ -194,7 +239,7 @@ class BrushFlow(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "brush.jpg"
|
||||
# 插件版本
|
||||
plugin_version = "2.7"
|
||||
plugin_version = "2.8"
|
||||
# 插件作者
|
||||
plugin_author = "jxxghp,InfinityPacer"
|
||||
# 作者主页
|
||||
@@ -241,10 +286,6 @@ class BrushFlow(_PluginBase):
|
||||
logger.info("站点刷流任务出错,无法获取插件配置")
|
||||
return False
|
||||
|
||||
# 如果没有站点配置时,增加默认的配置项
|
||||
if not config.get("site_config"):
|
||||
config["site_config"] = self.__get_demo_site_config()
|
||||
|
||||
# 如果配置校验没有通过,那么这里修改配置文件后退出
|
||||
if not self.__validate_and_fix_config(config=config):
|
||||
self._brush_config = BrushConfig(config=config)
|
||||
@@ -956,12 +997,6 @@ class BrushFlow(_PluginBase):
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
@@ -1163,7 +1198,7 @@ class BrushFlow(_PluginBase):
|
||||
"component": "VDialog",
|
||||
"props": {
|
||||
"model": "dialog_closed",
|
||||
"max-width": "50rem",
|
||||
"max-width": "65rem",
|
||||
"overlay-class": "v-dialog--scrollable v-overlay--scroll-blocked",
|
||||
"content-class": "v-card v-card--density-default v-card--variant-elevated rounded-t"
|
||||
},
|
||||
@@ -1684,12 +1719,14 @@ class BrushFlow(_PluginBase):
|
||||
size_condition_passed, reason = self.__evaluate_size_condition_for_brush(torrents_size=torrents_size)
|
||||
self.__log_brush_conditions(passed=size_condition_passed, reason=reason)
|
||||
if not size_condition_passed:
|
||||
logger.info(f"刷流任务执行完成")
|
||||
return
|
||||
|
||||
# 判断能否通过刷流前置条件
|
||||
pre_condition_passed, reason = self.__evaluate_pre_conditions_for_brush()
|
||||
self.__log_brush_conditions(passed=pre_condition_passed, reason=reason)
|
||||
if not pre_condition_passed:
|
||||
logger.info(f"刷流任务执行完成")
|
||||
return
|
||||
|
||||
statistic_info = self.__get_statistic_info()
|
||||
@@ -1760,7 +1797,7 @@ class BrushFlow(_PluginBase):
|
||||
for torrent in torrents:
|
||||
# 判断能否通过刷流前置条件
|
||||
pre_condition_passed, reason = self.__evaluate_pre_conditions_for_brush(include_network_conditions=False)
|
||||
self.__log_brush_conditions(passed=pre_condition_passed, reason=reason, torrent=torrent)
|
||||
self.__log_brush_conditions(passed=pre_condition_passed, reason=reason)
|
||||
if not pre_condition_passed:
|
||||
return False
|
||||
|
||||
@@ -1912,10 +1949,19 @@ class BrushFlow(_PluginBase):
|
||||
"""
|
||||
brush_config = self.__get_brush_config(torrent.site_name)
|
||||
|
||||
# 排除重复种子
|
||||
# 默认根据标题和站点名称进行排除
|
||||
task_key = f"{torrent.site_name}{torrent.title}"
|
||||
if any(task_key == f"{task.get('site_name')}{task.get('title')}" for task in torrent_tasks.values()):
|
||||
return False, "重复种子"
|
||||
|
||||
# 部分站点标题会上新时携带后缀,这里进一步根据种子详情地址进行排除
|
||||
if torrent.page_url:
|
||||
task_page_url = f"{torrent.site_name}{torrent.page_url}"
|
||||
if any(task_page_url == f"{task.get('site_name')}{task.get('page_url')}" for task in
|
||||
torrent_tasks.values()):
|
||||
return False, "重复种子"
|
||||
|
||||
# 促销条件
|
||||
if brush_config.freeleech and torrent.downloadvolumefactor != 0:
|
||||
return False, "非免费种子"
|
||||
@@ -1948,7 +1994,7 @@ class BrushFlow(_PluginBase):
|
||||
|
||||
# 做种人数
|
||||
if brush_config.seeder:
|
||||
seeders_range = [int(n) for n in brush_config.seeder.split("-")]
|
||||
seeders_range = [float(n) for n in brush_config.seeder.split("-")]
|
||||
# 检查是否仅指定了一个数字,即做种人数需要小于等于该数字
|
||||
if len(seeders_range) == 1:
|
||||
# 当做种人数大于该数字时,不符合条件
|
||||
@@ -1964,7 +2010,7 @@ class BrushFlow(_PluginBase):
|
||||
pubdate_minutes = self.__get_pubminutes(torrent.pubdate)
|
||||
pubdate_minutes = self.__adjust_site_pubminutes(pubdate_minutes, torrent)
|
||||
if brush_config.pubtime:
|
||||
pubtimes = [int(n) for n in brush_config.pubtime.split("-")]
|
||||
pubtimes = [float(n) for n in brush_config.pubtime.split("-")]
|
||||
if len(pubtimes) == 1:
|
||||
# 单个值:选择发布时间小于等于该值的种子
|
||||
if pubdate_minutes > pubtimes[0]:
|
||||
@@ -1982,7 +2028,7 @@ class BrushFlow(_PluginBase):
|
||||
"""
|
||||
if not passed:
|
||||
if not torrent:
|
||||
logger.warn(f"种子没有通过前置刷流条件校验,原因:{reason}")
|
||||
logger.warn(f"没有通过前置刷流条件校验,原因:{reason}")
|
||||
else:
|
||||
brush_config = self.__get_brush_config()
|
||||
if brush_config.log_more:
|
||||
@@ -2049,7 +2095,7 @@ class BrushFlow(_PluginBase):
|
||||
|
||||
need_delete_hashes = []
|
||||
|
||||
# 如果配置了删种阈值,则根据动态删种进行分组处理
|
||||
# 如果配置了动态删除以及删种阈值,则根据动态删种进行分组处理
|
||||
if brush_config.proxy_delete and brush_config.delete_size_range:
|
||||
logger.info("已开启动态删种,按系统默认动态删种条件开始检查任务")
|
||||
proxy_delete_hashs = self.__delete_torrent_for_proxy(torrents=check_torrents,
|
||||
@@ -2237,6 +2283,22 @@ class BrushFlow(_PluginBase):
|
||||
|
||||
return True, reason if not hit_and_run else "H&R种子(未设置H&R条件)," + reason
|
||||
|
||||
def __evaluate_proxy_pre_conditions_for_delete(self, site_name: str, torrent_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
评估动态删除前置条件并返回是否应删除种子及其原因
|
||||
"""
|
||||
brush_config = self.__get_brush_config(sitename=site_name)
|
||||
|
||||
reason = "未能满足动态删除设置的前置删除条件"
|
||||
|
||||
if brush_config.download_time and torrent_info.get("downloaded") < torrent_info.get(
|
||||
"total_size") and torrent_info.get("dltime") >= float(brush_config.download_time) * 3600:
|
||||
reason = f"下载耗时 {torrent_info.get('dltime') / 3600:.1f} 小时,大于 {brush_config.download_time} 小时"
|
||||
else:
|
||||
return False, reason
|
||||
|
||||
return True, reason
|
||||
|
||||
def __delete_torrent_for_evaluate_conditions(self, torrents: List[Any], torrent_tasks: Dict[str, dict],
|
||||
proxy_delete: bool = False) -> List:
|
||||
"""
|
||||
@@ -2273,11 +2335,54 @@ class BrushFlow(_PluginBase):
|
||||
|
||||
return delete_hashs
|
||||
|
||||
def __delete_torrent_for_evaluate_proxy_pre_conditions(self, torrents: List[Any],
|
||||
torrent_tasks: Dict[str, dict]) -> List:
|
||||
"""
|
||||
根据动态删除前置条件排除H&R种子后删除种子并获取已删除列表
|
||||
"""
|
||||
brush_config = self.__get_brush_config()
|
||||
delete_hashs = []
|
||||
|
||||
for torrent in torrents:
|
||||
torrent_hash = self.__get_hash(torrent)
|
||||
torrent_task = torrent_tasks.get(torrent_hash, None)
|
||||
# 如果找不到种子任务,说明不在管理的种子范围内,直接跳过
|
||||
if not torrent_task:
|
||||
continue
|
||||
|
||||
# 如果是H&R种子,前置条件中不进行处理
|
||||
if torrent_task.get('hit_and_run', False):
|
||||
continue
|
||||
|
||||
site_name = torrent_task.get("site_name", "")
|
||||
torrent_title = torrent_task.get("title", "")
|
||||
torrent_desc = torrent_task.get("description", "")
|
||||
|
||||
torrent_info = self.__get_torrent_info(torrent)
|
||||
|
||||
# 删除种子的具体实现可能会根据实际情况略有不同
|
||||
should_delete, reason = self.__evaluate_proxy_pre_conditions_for_delete(site_name=site_name,
|
||||
torrent_info=torrent_info)
|
||||
if should_delete:
|
||||
delete_hashs.append(torrent_hash)
|
||||
self.__send_delete_message(site_name=site_name, torrent_title=torrent_title, torrent_desc=torrent_desc,
|
||||
reason=reason)
|
||||
logger.info(f"站点:{site_name},{reason},删除种子:{torrent_title}|{torrent_desc}")
|
||||
else:
|
||||
if brush_config.log_more:
|
||||
logger.info(f"站点:{site_name},{reason},不删除种子:{torrent_title}|{torrent_desc}")
|
||||
|
||||
return delete_hashs
|
||||
|
||||
def __delete_torrent_for_proxy(self, torrents: List[Any], torrent_tasks: Dict[str, dict]) -> List:
|
||||
"""
|
||||
支持动态删除种子,当设置了动态删种(全局)和删除阈值时,当做种体积达到删除阈值时,优先按设置规则进行删除,若还没有达到阈值,则排除HR种子后按加入时间倒序进行删除
|
||||
删除阈值:100,当做种体积 > 100G 时,则开始删除种子,直至降低至 100G
|
||||
删除阈值:50-100,当做种体积 > 100G 时,则开始删除种子,直至降至为 50G
|
||||
动态删除种子,删除规则如下;
|
||||
- 不管做种体积是否超过设定的动态删除阈值,默认优先执行排除H&R种子后满足「下载超时时间」的种子
|
||||
- 上述规则执行完成后,当做种体积依旧超过设定的动态删除阈值时,继续执行下述种子删除规则
|
||||
- 优先删除满足用户设置删除规则的全部种子,即便在删除过程中已经低于了阈值下限,也会继续删除
|
||||
- 若删除后还没有达到阈值,则在已完成种子中排除H&R种子后按做种时间倒序进行删除
|
||||
- 动态删除阈值:100,当做种体积 > 100G 时,则开始删除种子,直至降低至 100G
|
||||
- 动态删除阈值:50-100,当做种体积 > 100G 时,则开始删除种子,直至降至为 50G
|
||||
"""
|
||||
brush_config = self.__get_brush_config()
|
||||
|
||||
@@ -2285,29 +2390,49 @@ class BrushFlow(_PluginBase):
|
||||
if not (brush_config.proxy_delete and brush_config.delete_size_range):
|
||||
return []
|
||||
|
||||
# 获取种子信息Map
|
||||
torrent_info_map = {self.__get_hash(torrent): self.__get_torrent_info(torrent=torrent) for torrent in torrents}
|
||||
|
||||
# 计算当前总做种体积
|
||||
total_torrent_size = self.__calculate_seeding_torrents_size(torrent_tasks=torrent_tasks)
|
||||
|
||||
logger.info(
|
||||
f"当前做种体积 {self.__bytes_to_gb(total_torrent_size):.1f} GB,正在准备计算满足动态前置删除条件的种子")
|
||||
|
||||
# 执行排除H&R种子后满足前置删除条件的种子
|
||||
pre_delete_hashes = self.__delete_torrent_for_evaluate_proxy_pre_conditions(torrents=torrents,
|
||||
torrent_tasks=torrent_tasks) or []
|
||||
|
||||
# 如果存在前置删除种子,这里进行额外判断,总做种体积排除前置删除种子的体积
|
||||
if pre_delete_hashes:
|
||||
pre_delete_total_size = sum(torrent_info_map[self.__get_hash(torrent)].get("total_size", 0)
|
||||
for torrent in torrents if self.__get_hash(torrent) in pre_delete_hashes)
|
||||
total_torrent_size = total_torrent_size - pre_delete_total_size
|
||||
torrents = [torrent for torrent in torrents if self.__get_hash(torrent) not in pre_delete_hashes]
|
||||
logger.info(
|
||||
f"满足动态删除前置条件的种子共 {len(pre_delete_hashes)} 个,体积 {self.__bytes_to_gb(pre_delete_total_size):.1f} GB,"
|
||||
f"删除种子后,当前做种体积 {self.__bytes_to_gb(total_torrent_size):.1f} GB")
|
||||
else:
|
||||
logger.info(f"没有找到任何满足动态删除前置条件的种子")
|
||||
|
||||
# 解析删除阈值范围
|
||||
sizes = [float(size) * 1024 ** 3 for size in brush_config.delete_size_range.split("-")]
|
||||
min_size = sizes[0] # 至少需要达到的做种体积
|
||||
max_size = sizes[1] if len(sizes) > 1 else sizes[0] # 触发删除操作的做种体积上限
|
||||
|
||||
torrent_info_map = {self.__get_hash(torrent): self.__get_torrent_info(torrent=torrent) for torrent in torrents}
|
||||
|
||||
# 计算当前总做种体积
|
||||
# total_torrent_size = sum(info.get("total_size", 0) for info in torrent_info_map.values())
|
||||
total_torrent_size = self.__calculate_seeding_torrents_size(torrent_tasks=torrent_tasks)
|
||||
|
||||
# 当总体积未超过最大阈值时,不需要执行删除操作
|
||||
if total_torrent_size < max_size:
|
||||
logger.info(
|
||||
f"当前做种体积 {self.__bytes_to_gb(total_torrent_size):.1f} GB,上限 {self.__bytes_to_gb(max_size):.1f} GB,"
|
||||
f"下限 {self.__bytes_to_gb(min_size):.1f} GB,未触发动态删除")
|
||||
return []
|
||||
f"下限 {self.__bytes_to_gb(min_size):.1f} GB,未进一步触发动态删除")
|
||||
return pre_delete_hashes or []
|
||||
else:
|
||||
logger.info(
|
||||
f"当前做种体积 {self.__bytes_to_gb(total_torrent_size):.1f} GB,上限 {self.__bytes_to_gb(max_size):.1f} GB,"
|
||||
f"下限 {self.__bytes_to_gb(min_size):.1f} GB,触发动态删除")
|
||||
f"下限 {self.__bytes_to_gb(min_size):.1f} GB,进一步触发动态删除")
|
||||
|
||||
need_delete_hashes = []
|
||||
need_delete_hashes.extend(pre_delete_hashes)
|
||||
|
||||
# 即使开了动态删除,但是也有可能部分站点单独设置了关闭,这里根据种子托管进行分组,先处理不需要托管的种子,按设置的规则进行删除
|
||||
proxy_delete_torrents, not_proxy_delete_torrents = self.__group_torrents_by_proxy_delete(torrents=torrents,
|
||||
@@ -2332,7 +2457,6 @@ class BrushFlow(_PluginBase):
|
||||
self.__get_hash(torrent) in proxy_delete_hashes)
|
||||
|
||||
# 在完成初始删除步骤后,如果总体积仍然超过最小阈值,则进一步找到已完成种子并排除HR种子后按做种时间正序进行删除
|
||||
sites_names = set()
|
||||
if total_torrent_size > min_size:
|
||||
# 重新计算当前的种子列表,排除已删除的种子
|
||||
remaining_hashes = list(
|
||||
@@ -2365,14 +2489,15 @@ class BrushFlow(_PluginBase):
|
||||
torrent_desc = torrent_task.get("description", "")
|
||||
seeding_time = torrent_task.get("seeding_time", 0)
|
||||
if seeding_time:
|
||||
sites_names.add(site_name)
|
||||
reason = f"触发动态删除,做种时间 {seeding_time / 3600:.1f} 小时,系统自动删除"
|
||||
self.__send_delete_message(site_name=site_name, torrent_title=torrent_title,
|
||||
torrent_desc=torrent_desc,
|
||||
reason=reason)
|
||||
logger.info(f"站点:{site_name},{reason},删除种子:{torrent_title}|{torrent_desc}")
|
||||
|
||||
msg = (f"站点:{','.join(sites_names)}\n内容:已完成 {len(need_delete_hashes)} 个种子删除,"
|
||||
delete_sites = {torrent_tasks[hash_key].get('site_name', '') for hash_key in need_delete_hashes if
|
||||
hash_key in torrent_tasks}
|
||||
msg = (f"站点:{','.join(delete_sites)}\n内容:已完成 {len(need_delete_hashes)} 个种子删除,"
|
||||
f"当前做种体积 {self.__bytes_to_gb(total_torrent_size):.1f} GB\n原因:触发动态删除")
|
||||
logger.info(msg)
|
||||
self.__send_message(title="【刷流任务状态更新】", text=msg)
|
||||
@@ -2754,7 +2879,7 @@ class BrushFlow(_PluginBase):
|
||||
if response and response.ok:
|
||||
torrent_content = response.content
|
||||
else:
|
||||
logger.error('代理下载种子失败,继续尝试传递种子地址到下载器进行下载')
|
||||
logger.error('尝试通过MP下载种子失败,继续尝试传递种子地址到下载器进行下载')
|
||||
if torrent_content:
|
||||
state = self.qb.add_torrent(content=torrent_content,
|
||||
download_dir=download_dir,
|
||||
@@ -2769,7 +2894,7 @@ class BrushFlow(_PluginBase):
|
||||
torrent_hash = self.qb.get_torrent_id_by_tag(tags=tag)
|
||||
if not torrent_hash:
|
||||
logger.error(f"{brush_config.downloader} 获取种子Hash失败"
|
||||
f"{',请尝试开启代理下载种子' if not brush_config.proxy_download else ''}")
|
||||
f"{',请尝试启用「代理下载种子」配置项' if not brush_config.proxy_download else ''}")
|
||||
return None
|
||||
return torrent_hash
|
||||
return None
|
||||
@@ -2785,7 +2910,7 @@ class BrushFlow(_PluginBase):
|
||||
if response and response.ok:
|
||||
torrent_content = response.content
|
||||
else:
|
||||
logger.error('代理下载种子失败,继续尝试传递种子地址到下载器进行下载')
|
||||
logger.error('尝试通过MP下载种子失败,继续尝试传递种子地址到下载器进行下载')
|
||||
if torrent_content:
|
||||
torrent = self.tr.add_torrent(content=torrent_content,
|
||||
download_dir=download_dir,
|
||||
@@ -3301,9 +3426,9 @@ class BrushFlow(_PluginBase):
|
||||
@staticmethod
|
||||
def __is_number_or_range(value):
|
||||
"""
|
||||
检查字符串是否表示单个数字或数字范围(如'5'或'5-10')
|
||||
检查字符串是否表示单个数字或数字范围(如'5', '5.5', '5-10' 或 '5.5-10.2')
|
||||
"""
|
||||
return bool(re.match(r"^\d+(-\d+)?$", value))
|
||||
return bool(re.match(r"^\d+(\.\d+)?(-\d+(\.\d+)?)?$", value))
|
||||
|
||||
@staticmethod
|
||||
def __is_number(value):
|
||||
@@ -3379,46 +3504,6 @@ class BrushFlow(_PluginBase):
|
||||
}
|
||||
return statistic_info
|
||||
|
||||
@staticmethod
|
||||
def __get_demo_site_config() -> str:
|
||||
desc = ("// 以下为配置示例,请参考:https://github.com/InfinityPacer/MoviePilot-Plugins/blob/main/README.md 进行配置\n"
|
||||
"// 无关内容请使用 // 进行注释\n")
|
||||
config = """[{
|
||||
"sitename": "站点1",
|
||||
"seed_time": 96,
|
||||
"hr_seed_time": 144
|
||||
}, {
|
||||
"sitename": "站点2",
|
||||
"hr": "yes",
|
||||
"size": "10-500",
|
||||
"seeder": "5-10",
|
||||
"pubtime": "5-120",
|
||||
"seed_time": 96,
|
||||
"save_path": "/downloads/site2",
|
||||
"proxy_download": true,
|
||||
"hr_seed_time": 144
|
||||
}, {
|
||||
"sitename": "站点3",
|
||||
"freeleech": "free",
|
||||
"hr": "yes",
|
||||
"include": "",
|
||||
"exclude": "",
|
||||
"size": "10-500",
|
||||
"seeder": "1",
|
||||
"pubtime": "5-120",
|
||||
"seed_time": 120,
|
||||
"hr_seed_time": 144,
|
||||
"seed_ratio": "",
|
||||
"seed_size": "",
|
||||
"download_time": "",
|
||||
"seed_avgspeed": "",
|
||||
"seed_inactivetime": "",
|
||||
"save_path": "/downloads/site1",
|
||||
"proxy_download": false,
|
||||
"proxy_delete": false,
|
||||
}]"""
|
||||
return desc + config
|
||||
|
||||
@staticmethod
|
||||
def __is_valid_time_range(time_range: str) -> bool:
|
||||
"""检查时间范围字符串是否有效:格式为"HH:MM-HH:MM",且时间有效"""
|
||||
|
||||
@@ -177,11 +177,11 @@ class CrossSeed(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "qingwa.png"
|
||||
# 插件版本
|
||||
plugin_version = "2.1"
|
||||
plugin_version = "2.2"
|
||||
# 插件作者
|
||||
plugin_author = "233@qingwa"
|
||||
# 作者主页
|
||||
author_url = "https://new.qingwa.pro/"
|
||||
author_url = "https://qingwapt.com/"
|
||||
# 插件配置项ID前缀
|
||||
plugin_config_prefix = "cross_seed_"
|
||||
# 加载顺序
|
||||
@@ -955,6 +955,11 @@ class CrossSeed(_PluginBase):
|
||||
# 逐个站点查询可辅种数据
|
||||
chunk_size = 100
|
||||
for site_config in self._site_cs_infos:
|
||||
# 检查站点是否已经停用
|
||||
db_site = self.siteoper.get(site_config.id)
|
||||
if db_site and not db_site.is_active:
|
||||
logger.info(f"站点{site_config.name}已停用,跳过辅种")
|
||||
continue
|
||||
remote_tors: List[TorInfo] = []
|
||||
total_size = len(pieces_hashes)
|
||||
for i in range(0, len(pieces_hashes), chunk_size):
|
||||
|
||||
469
plugins/pluginautoupgrade/__init__.py
Normal file
469
plugins/pluginautoupgrade/__init__.py
Normal file
@@ -0,0 +1,469 @@
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from datetime import datetime, timedelta
|
||||
from threading import Event as ThreadEvent, RLock
|
||||
from typing import Any, List, Dict, Tuple, Optional
|
||||
import pytz
|
||||
from app import schemas
|
||||
from app.api.endpoints.plugin import install
|
||||
from app.core.config import settings
|
||||
from app.core.plugin import PluginManager
|
||||
from app.log import logger
|
||||
from app.plugins import _PluginBase
|
||||
|
||||
|
||||
class PluginAutoUpgrade(_PluginBase):
|
||||
# 插件名称
|
||||
plugin_name = "插件自动升级"
|
||||
# 插件描述
|
||||
plugin_desc = "定时检测、升级插件。"
|
||||
# 插件图标
|
||||
plugin_icon = "PluginAutoUpgrade.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.0"
|
||||
# 插件作者
|
||||
plugin_author = "hotlcc"
|
||||
# 作者主页
|
||||
author_url = "https://github.com/hotlcc"
|
||||
# 插件配置项ID前缀
|
||||
plugin_config_prefix = "com.hotlcc.pluginautoupgrade."
|
||||
# 加载顺序
|
||||
plugin_order = 66
|
||||
# 可使用的用户级别
|
||||
auth_level = 1
|
||||
|
||||
# 私有属性
|
||||
# 调度器
|
||||
__scheduler: Optional[BackgroundScheduler] = None
|
||||
# 退出事件
|
||||
__exit_event: ThreadEvent = ThreadEvent()
|
||||
# 任务锁
|
||||
__task_lock: RLock = RLock()
|
||||
|
||||
# 依赖组件
|
||||
# 插件管理器
|
||||
__plugin_manager: PluginManager = PluginManager()
|
||||
|
||||
# 配置相关
|
||||
# 插件缺省配置
|
||||
__config_default: Dict[str, Any] = {
|
||||
'cron': '* 0/4 * * *'
|
||||
}
|
||||
# 插件用户配置
|
||||
__config: Dict[str, Any] = {}
|
||||
|
||||
def init_plugin(self, config: dict = None):
|
||||
"""
|
||||
初始化插件
|
||||
"""
|
||||
# 加载插件配置
|
||||
self.__config = config
|
||||
# 停止现有服务
|
||||
self.stop_service()
|
||||
# 如果需要立即运行一次
|
||||
if self.__get_config_item(config_key='run_once'):
|
||||
if (self.__start_scheduler()):
|
||||
self.__scheduler.add_job(func=self.__try_run,
|
||||
trigger='date',
|
||||
run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3),
|
||||
name=f'{self.plugin_name}-立即运行一次')
|
||||
logger.info(f"立即运行一次成功")
|
||||
# 关闭一次性开关
|
||||
self.__config['run_once'] = False
|
||||
self.update_config(self.__config)
|
||||
|
||||
def get_state(self) -> bool:
|
||||
"""
|
||||
获取插件状态
|
||||
"""
|
||||
state = True if self.__get_config_item(config_key='enable') \
|
||||
and self.__get_config_item(config_key='cron') \
|
||||
else False
|
||||
return state
|
||||
|
||||
@staticmethod
|
||||
def get_command() -> List[Dict[str, Any]]:
|
||||
"""
|
||||
定义远程控制命令
|
||||
:return: 命令关键字、事件、描述、附带数据
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_api(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
获取插件API
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_service(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
注册插件公共服务
|
||||
"""
|
||||
try:
|
||||
if self.get_state():
|
||||
cron = self.__get_config_item(config_key='cron')
|
||||
return [{
|
||||
"id": "PluginAutoUpgradeTimerService",
|
||||
"name": f"{self.plugin_name}定时服务",
|
||||
"trigger": CronTrigger.from_crontab(cron),
|
||||
"func": self.__try_run,
|
||||
"kwargs": {}
|
||||
}]
|
||||
else:
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"注册插件公共服务异常: {str(e)}", exc_info=True)
|
||||
|
||||
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
|
||||
"""
|
||||
拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构
|
||||
"""
|
||||
# 建议的配置
|
||||
config_suggest = {}
|
||||
# 合并默认配置
|
||||
config_suggest.update(self.__config_default)
|
||||
# 定时周期
|
||||
cron = self.__config_default.get('cron')
|
||||
# 已安装的在线插件下拉框数据
|
||||
installed_online_plugin_options = self.__get_installed_online_plugin_options()
|
||||
form = [{
|
||||
'component': 'VForm',
|
||||
'content': [{ # 业务无关总控
|
||||
'component': 'VRow',
|
||||
'content': [{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'xxl': 4, 'xl': 4, 'lg': 4, 'md': 4, 'sm': 6, 'xs': 12
|
||||
},
|
||||
'content': [{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'enable',
|
||||
'label': '启用插件',
|
||||
'hint': '插件总开关'
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'xxl': 4, 'xl': 4, 'lg': 4, 'md': 4, 'sm': 6, 'xs': 12
|
||||
},
|
||||
'content': [{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'enable_notify',
|
||||
'label': '发送通知',
|
||||
'hint': '执行插件任务后是否发送通知'
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'xxl': 4, 'xl': 4, 'lg': 4, 'md': 4, 'sm': 6, 'xs': 12
|
||||
},
|
||||
'content': [{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'run_once',
|
||||
'label': '立即运行一次',
|
||||
'hint': '保存插件配置后是否立即触发一次插件任务运行'
|
||||
}
|
||||
}]
|
||||
}]
|
||||
}, {
|
||||
'component': 'VRow',
|
||||
'content': [{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'xxl': 4, 'xl': 4, 'lg': 4, 'md': 4, 'sm': 6, 'xs': 12
|
||||
},
|
||||
'content': [{
|
||||
'component': 'VTextField',
|
||||
'props': {
|
||||
'model': 'cron',
|
||||
'label': '定时执行周期',
|
||||
'placeholder': cron,
|
||||
'hint': f'设置插件任务执行周期。支持5位cron表达式,应避免任务执行过于频繁,缺省时为:【{cron}】'
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'xxl': 4, 'xl': 4, 'lg': 4, 'md': 4, 'sm': 6, 'xs': 12
|
||||
},
|
||||
'content': [{
|
||||
'component': 'VSelect',
|
||||
'props': {
|
||||
'model': 'include_plugins',
|
||||
'label': '包含的插件',
|
||||
'multiple': True,
|
||||
'chips': True,
|
||||
'items': installed_online_plugin_options,
|
||||
'hint': '选择哪些插件需要自动升级,不选时默认全部已安装插件。'
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'xxl': 4, 'xl': 4, 'lg': 4, 'md': 4, 'sm': 6, 'xs': 12
|
||||
},
|
||||
'content': [{
|
||||
'component': 'VSelect',
|
||||
'props': {
|
||||
'model': 'exclude_plugins',
|
||||
'label': '排除的插件',
|
||||
'multiple': True,
|
||||
'chips': True,
|
||||
'items': installed_online_plugin_options,
|
||||
'hint': '选择哪些插件需要排除升级(在【包含的插件】的基础上排除),不选时默认不排除。'
|
||||
}
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
return form, config_suggest
|
||||
|
||||
def get_page(self) -> List[dict]:
|
||||
pass
|
||||
|
||||
def stop_service(self):
|
||||
"""
|
||||
退出插件
|
||||
"""
|
||||
try:
|
||||
logger.info('尝试停止插件服务...')
|
||||
self.__exit_event.set()
|
||||
self.__stop_scheduler()
|
||||
logger.info('插件服务停止成功')
|
||||
except Exception as e:
|
||||
logger.error(f"插件服务停止异常: {str(e)}", exc_info=True)
|
||||
finally:
|
||||
self.__exit_event.clear()
|
||||
|
||||
def __get_config_item(self, config_key: str, use_default: bool = True) -> Any:
|
||||
"""
|
||||
获取插件配置项
|
||||
:param config_key: 配置键
|
||||
:param use_default: 是否使用缺省值
|
||||
:return: 配置值
|
||||
"""
|
||||
if not config_key:
|
||||
return None
|
||||
config = self.__config if self.__config else {}
|
||||
config_value = config.get(config_key)
|
||||
if config_value is None and use_default:
|
||||
config_default = self.__config_default if self.__config_default else {}
|
||||
config_value = config_default.get(config_key)
|
||||
return config_value
|
||||
|
||||
@classmethod
|
||||
def __get_local_plugins(cls) -> List[schemas.Plugin]:
|
||||
"""
|
||||
获取所有本地插件信息
|
||||
"""
|
||||
local_plugins = cls.__plugin_manager.get_local_plugins()
|
||||
return local_plugins
|
||||
|
||||
@classmethod
|
||||
def __get_installed_local_plugins(cls) -> List[schemas.Plugin]:
|
||||
"""
|
||||
获取所有已安装的本地插件信息
|
||||
"""
|
||||
local_plugins = cls.__get_local_plugins()
|
||||
installed_local_plugins = [local_plugin for local_plugin in local_plugins if local_plugin and local_plugin.installed]
|
||||
return installed_local_plugins
|
||||
|
||||
@classmethod
|
||||
def __get_installed_local_plugin(cls, plugin_id: str) -> List[schemas.Plugin]:
|
||||
"""
|
||||
获取指定的已安装的本地插件信息
|
||||
"""
|
||||
if not plugin_id:
|
||||
return None
|
||||
# 已安装的本地插件
|
||||
installed_plugins = cls.__get_installed_local_plugins()
|
||||
for installed_plugin in installed_plugins:
|
||||
if installed_plugin and installed_plugin.id and installed_plugin.id == plugin_id:
|
||||
return installed_plugin
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def __get_online_plugins(cls) -> List[schemas.Plugin]:
|
||||
"""
|
||||
获取所有在线插件
|
||||
"""
|
||||
online_plugins = cls.__plugin_manager.get_online_plugins()
|
||||
return online_plugins
|
||||
|
||||
@classmethod
|
||||
def __get_installed_online_plugins(cls) -> List[schemas.Plugin]:
|
||||
"""
|
||||
获取所有已安装的在线插件
|
||||
"""
|
||||
online_plugins = cls.__get_online_plugins()
|
||||
installed_online_plugins = [online_plugin for online_plugin in online_plugins if online_plugin and online_plugin.installed]
|
||||
return installed_online_plugins
|
||||
|
||||
@classmethod
|
||||
def __get_installed_online_plugin_options(cls) -> Dict[str, Any]:
|
||||
"""
|
||||
获取所有已安装的在线插件的选项数据
|
||||
"""
|
||||
installed_online_plugin_options = []
|
||||
installed_online_plugins = cls.__get_installed_online_plugins()
|
||||
for installed_online_plugin in installed_online_plugins:
|
||||
if not installed_online_plugin:
|
||||
continue
|
||||
installed_online_plugin_options.append({
|
||||
'value': installed_online_plugin.id,
|
||||
'title': installed_online_plugin.plugin_name
|
||||
})
|
||||
return installed_online_plugin_options
|
||||
|
||||
@classmethod
|
||||
def __get_has_update_online_plugins(cls) -> List[schemas.Plugin]:
|
||||
"""
|
||||
获取所有可升级的在线插件
|
||||
"""
|
||||
installed_online_plugins = cls.__get_installed_online_plugins()
|
||||
if not installed_online_plugins:
|
||||
return None
|
||||
has_update_online_plugins = [installed_online_plugin for installed_online_plugin in installed_online_plugins if installed_online_plugin and installed_online_plugin.has_update]
|
||||
return has_update_online_plugins
|
||||
|
||||
def __start_scheduler(self, timezone=None) -> bool:
|
||||
"""
|
||||
启动调度器
|
||||
:param timezone: 时区
|
||||
"""
|
||||
try:
|
||||
if not self.__scheduler:
|
||||
if not timezone:
|
||||
timezone = settings.TZ
|
||||
self.__scheduler = BackgroundScheduler(timezone=timezone)
|
||||
logger.debug(f"插件服务调度器初始化完成: timezone = {str(timezone)}")
|
||||
if not self.__scheduler.running:
|
||||
self.__scheduler.start()
|
||||
logger.debug(f"插件服务调度器启动成功")
|
||||
self.__scheduler.print_jobs()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"插件服务调度器启动异常: {str(e)}", exc_info=True)
|
||||
return False
|
||||
|
||||
def __stop_scheduler(self):
|
||||
"""
|
||||
停止调度器
|
||||
"""
|
||||
try:
|
||||
logger.info('尝试停止插件服务调度器...')
|
||||
if self.__scheduler:
|
||||
self.__scheduler.remove_all_jobs()
|
||||
if self.__scheduler.running:
|
||||
self.__scheduler.shutdown()
|
||||
self.__scheduler = None
|
||||
logger.info('插件服务调度器停止成功')
|
||||
else:
|
||||
logger.info('插件未启用服务调度器,无须停止')
|
||||
except Exception as e:
|
||||
logger.error(f"插件服务调度器停止异常: {str(e)}", exc_info=True)
|
||||
|
||||
def __check_allow_upgrade(self, plugin_id: str) -> bool:
|
||||
"""
|
||||
判断插件是否允许升级:包含、排除
|
||||
"""
|
||||
if not plugin_id:
|
||||
return False
|
||||
exclude_plugins = self.__get_config_item('exclude_plugins')
|
||||
if exclude_plugins and plugin_id in exclude_plugins:
|
||||
return False
|
||||
include_plugins = self.__get_config_item('include_plugins')
|
||||
if not include_plugins or plugin_id in include_plugins:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def __try_run(self):
|
||||
"""
|
||||
尝试运行插件任务
|
||||
"""
|
||||
if not self.__task_lock.acquire(blocking=False):
|
||||
logger.info('已有进行中的任务,本次不执行')
|
||||
return
|
||||
try:
|
||||
self.__run()
|
||||
finally:
|
||||
self.__task_lock.release()
|
||||
|
||||
def __run(self):
|
||||
""""
|
||||
运行插件任务
|
||||
"""
|
||||
self.__upgrade_batch()
|
||||
|
||||
def __upgrade_batch(self):
|
||||
"""
|
||||
批量升级
|
||||
"""
|
||||
has_update_online_plugins = self.__get_has_update_online_plugins()
|
||||
upgrade_results = []
|
||||
for has_update_online_plugin in has_update_online_plugins:
|
||||
upgrade_result = self.__upgrade_single(has_update_online_plugin)
|
||||
if upgrade_result:
|
||||
upgrade_results.append(upgrade_result)
|
||||
self.__send_notify(results=upgrade_results)
|
||||
|
||||
def __upgrade_single(self, online_plugin: schemas.Plugin) -> Dict[str, Any]:
|
||||
"""
|
||||
单个升级
|
||||
"""
|
||||
if not online_plugin or not online_plugin.has_update or not online_plugin.id or not online_plugin.repo_url or not self.__check_allow_upgrade(plugin_id=online_plugin.id):
|
||||
return None
|
||||
installed_local_plugin = self.__get_installed_local_plugin(plugin_id=online_plugin.id)
|
||||
if not installed_local_plugin:
|
||||
return None
|
||||
response = install(plugin_id=online_plugin.id, repo_url=online_plugin.repo_url, force=True)
|
||||
logger.info(f"插件升级结果: plugin_name = {online_plugin.plugin_name}, plugin_version = v{installed_local_plugin.plugin_version} -> v{online_plugin.plugin_version}, success = {response.success}, message = {response.message}")
|
||||
return {
|
||||
'success': response.success,
|
||||
'message': response.message,
|
||||
'plugin_id': online_plugin.id,
|
||||
'plugin_name': online_plugin.plugin_name,
|
||||
'new_plugin_version': online_plugin.plugin_version,
|
||||
'old_plugin_version': installed_local_plugin.plugin_version
|
||||
}
|
||||
|
||||
def __send_notify(self, results: List[Dict[str, Any]]):
|
||||
"""
|
||||
发送通知
|
||||
:param results: 插件升级结果
|
||||
"""
|
||||
if not results or not self.__get_config_item('enable_notify'):
|
||||
return
|
||||
text = self.__build_notify_message(results=results)
|
||||
if not text:
|
||||
return
|
||||
self.post_message(title=f'{self.plugin_name}任务执行结果', text=text)
|
||||
|
||||
@staticmethod
|
||||
def __build_notify_message(results: List[Dict[str, Any]]) -> str:
|
||||
"""
|
||||
构建通知消息内容
|
||||
"""
|
||||
text = ''
|
||||
if not results:
|
||||
return text
|
||||
for result in results:
|
||||
if not result:
|
||||
continue
|
||||
if result.get('success'):
|
||||
text += f"{result.get('plugin_name')}升级[v{result.get('old_plugin_version')} -> v{result.get('new_plugin_version')}]成功\n"
|
||||
else:
|
||||
text += f"{result.get('plugin_name')}升级[v{result.get('old_plugin_version')} -> v{result.get('new_plugin_version')}]失败:{result.get('message')}\n"
|
||||
return text
|
||||
@@ -11,7 +11,6 @@ from watchdog.observers import Observer
|
||||
from app.log import logger
|
||||
from app.plugins import _PluginBase
|
||||
from app.schemas import Notification, NotificationType
|
||||
from app.utils.timer import TimerUtils
|
||||
|
||||
state_lock = threading.Lock()
|
||||
|
||||
@@ -91,7 +90,7 @@ class RemoveLink(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "Ombi_A.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.8"
|
||||
plugin_version = "1.6"
|
||||
# 插件作者
|
||||
plugin_author = "DzAvril"
|
||||
# 作者主页
|
||||
@@ -175,40 +174,6 @@ class RemoveLink(_PluginBase):
|
||||
def get_api(self) -> List[Dict[str, Any]]:
|
||||
pass
|
||||
|
||||
def get_service(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
注册插件公共服务
|
||||
[{
|
||||
"id": "服务ID",
|
||||
"name": "服务名称",
|
||||
"trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()",
|
||||
"func": self.xxx,
|
||||
"kwargs": {} # 定时器参数
|
||||
}]
|
||||
"""
|
||||
if self._enabled:
|
||||
# 随机时间
|
||||
triggers = TimerUtils.random_scheduler(
|
||||
num_executions=1,
|
||||
begin_hour=0,
|
||||
end_hour=1,
|
||||
min_interval=1,
|
||||
max_interval=60,
|
||||
)
|
||||
ret_jobs = []
|
||||
for trigger in triggers:
|
||||
ret_jobs.append(
|
||||
{
|
||||
"id": f"RemoveLink|{trigger.hour}:{trigger.minute}",
|
||||
"name": "清理空文件夹",
|
||||
"trigger": "cron",
|
||||
"func": self.delete_empty_folders,
|
||||
"kwargs": {"hour": trigger.hour, "minute": trigger.minute},
|
||||
}
|
||||
)
|
||||
return ret_jobs
|
||||
return []
|
||||
|
||||
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
@@ -308,26 +273,26 @@ class RemoveLink(_PluginBase):
|
||||
],
|
||||
},
|
||||
{
|
||||
"component": "VRow",
|
||||
"content": [
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
"component": "VCol",
|
||||
"props": {
|
||||
"cols": 12,
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
},
|
||||
"content": [
|
||||
'content': [
|
||||
{
|
||||
"component": "VAlert",
|
||||
"props": {
|
||||
"type": "info",
|
||||
"variant": "tonal",
|
||||
"text": "监控目录如有多个需换行,源目录和硬链接目录都需要添加到监控目录中;如需实现删除硬链接时不删除源文件,可把源文件目录配置到不删除目录中。",
|
||||
},
|
||||
'component': 'VAlert',
|
||||
'props': {
|
||||
'type': 'info',
|
||||
'variant': 'tonal',
|
||||
'text': '监控目录如有多个需换行,源目录和硬链接目录都需要添加到监控目录中;如需实现删除硬链接时不删除源文件,可把源文件目录配置到不删除目录中。'
|
||||
}
|
||||
}
|
||||
],
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
}
|
||||
], {
|
||||
@@ -364,25 +329,6 @@ class RemoveLink(_PluginBase):
|
||||
return True
|
||||
return False
|
||||
|
||||
def delete_empty_folders(self):
|
||||
"""
|
||||
删除空目录
|
||||
"""
|
||||
for mon_path in self.monitor_dirs.split("\n"):
|
||||
for subdir, dirs, files in os.walk(mon_path, topdown=False):
|
||||
for dir in dirs:
|
||||
dir_path = os.path.join(subdir, dir)
|
||||
# 检查当前目录是否为空
|
||||
if not os.listdir(dir_path) and not self.__is_excluded(dir_path):
|
||||
os.rmdir(dir_path)
|
||||
logger.info(f"删除空目录:{dir_path}")
|
||||
if self._notify:
|
||||
self.post_message(
|
||||
mtype=NotificationType.SiteMessage,
|
||||
title=f"【清理硬链接】",
|
||||
text=f"清理空文件夹:[{dir_path}]\n",
|
||||
)
|
||||
|
||||
def handle_deleted(self, file_path: Path):
|
||||
"""
|
||||
处理删除事件
|
||||
@@ -412,10 +358,7 @@ class RemoveLink(_PluginBase):
|
||||
mtype=NotificationType.SiteMessage,
|
||||
title=f"【清理硬链接】",
|
||||
text=f"监控到删除源文件:[{file_path}]\n"
|
||||
f"同步删除硬链接文件:[{path}]",
|
||||
f"同步删除硬链接文件:[{path}]",
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"删除硬链接文件发生错误:%s - %s" % (str(e), traceback.format_exc())
|
||||
)
|
||||
logger.error("删除硬链接文件发生错误:%s - %s" % (str(e), traceback.format_exc()))
|
||||
|
||||
Reference in New Issue
Block a user