Merge branch 'jxxghp:main' into feature_dev

This commit is contained in:
InfinityPacer
2024-04-04 14:34:29 +08:00
committed by GitHub
4 changed files with 315 additions and 60 deletions

View File

@@ -186,7 +186,7 @@
"CrossSeed": {
"name": "青蛙辅种助手",
"description": "参考ReseedPuppy和IYUU辅种插件实现自动辅种支持站点青蛙、AGSVPT、麒麟、UBits、聆音、憨憨等。",
"version": "2.0",
"version": "2.1",
"icon": "qingwa.png",
"author": "233@qingwa",
"level": 2
@@ -362,7 +362,7 @@
"RemoveLink": {
"name": "清理硬链接",
"description": "监控目录内文件被删除时,同步删除监控目录内所有和它硬链接的文件",
"version": "1.6",
"version": "1.8",
"icon": "Ombi_A.png",
"author": "DzAvril",
"level": 1
@@ -418,7 +418,7 @@
"QbCommand": {
"name": "QB远程操作",
"description": "通过定时任务或交互命令远程操作QB暂停/开始/限速等。",
"version": "1.2",
"version": "1.3",
"icon": "Qbittorrent_A.png",
"author": "DzAvril",
"level": 1

View File

@@ -56,7 +56,7 @@ class CSSiteConfig(object):
def get_api_url(self):
if self.name == "憨憨":
return f"{self.url}npapi/pieces-hash"
return f"{self.url}nexusapi/pieces-hash"
return f"{self.url}api/pieces-hash"
def get_torrent_url(self, torrent_id: str):
@@ -177,7 +177,7 @@ class CrossSeed(_PluginBase):
# 插件图标
plugin_icon = "qingwa.png"
# 插件版本
plugin_version = "2.0"
plugin_version = "2.1"
# 插件作者
plugin_author = "233@qingwa"
# 作者主页

View File

@@ -1,6 +1,5 @@
import math
from typing import List, Tuple, Dict, Any
from enum import Enum
from app.log import logger
from app.modules.qbittorrent import Qbittorrent
from app.plugins import _PluginBase
@@ -19,7 +18,7 @@ class QbCommand(_PluginBase):
# 插件图标
plugin_icon = "Qbittorrent_A.png"
# 插件版本
plugin_version = "1.2"
plugin_version = "1.3"
# 插件作者
plugin_author = "DzAvril"
# 作者主页
@@ -39,6 +38,9 @@ class QbCommand(_PluginBase):
_resume_cron = None
_only_pause_once = False
_only_resume_once = False
_only_pause_upload = False
_only_pause_download = False
_only_pause_checking = False
_upload_limit = 0
_enable_upload_limit = False
_download_limit = 0
@@ -55,6 +57,9 @@ class QbCommand(_PluginBase):
self._resume_cron = config.get("resume_cron")
self._only_pause_once = config.get("onlypauseonce")
self._only_resume_once = config.get("onlyresumeonce")
self._only_pause_upload = config.get("onlypauseupload")
self._only_pause_download = config.get("onlypausedownload")
self._only_pause_checking = config.get("onlypausechecking")
self._download_limit = config.get("download_limit")
self._upload_limit = config.get("upload_limit")
self._enable_download_limit = config.get("enable_download_limit")
@@ -82,11 +87,40 @@ class QbCommand(_PluginBase):
}
)
if self._only_pause_upload or self._only_pause_download or self._only_pause_checking:
if self._only_pause_upload:
self.pause_torrent(self.TorrentType.UPLOADING)
if self._only_pause_download:
self.pause_torrent(self.TorrentType.DOWNLOADING)
if self._only_pause_checking:
self.pause_torrent(self.TorrentType.CHECKING)
self._only_pause_upload = False
self._only_pause_download = False
self._only_pause_checking = False
self.update_config(
{
"onlypauseupload": False,
"onlypausedownload": False,
"onlypausechecking": False,
"enabled": self._enabled,
"notify": self._notify,
"pause_cron": self._pause_cron,
"resume_cron": self._resume_cron,
}
)
self.set_limit(self._upload_limit, self._download_limit)
def get_state(self) -> bool:
return self._enabled
class TorrentType(Enum):
ALL = 1
DOWNLOADING = 2
UPLOADING = 3
CHECKING = 4
@staticmethod
def get_command() -> List[Dict[str, Any]]:
"""
@@ -97,17 +131,45 @@ class QbCommand(_PluginBase):
{
"cmd": "/pause_torrents",
"event": EventType.PluginAction,
"desc": "暂停QB种子",
"desc": "暂停QB所有任务",
"category": "QB",
"data": {"action": "pause_torrents"},
},
{
"cmd": "/pause_upload_torrents",
"event": EventType.PluginAction,
"desc": "暂停QB上传任务",
"category": "QB",
"data": {"action": "pause_upload_torrents"},
},
{
"cmd": "/pause_download_torrents",
"event": EventType.PluginAction,
"desc": "暂停QB下载任务",
"category": "QB",
"data": {"action": "pause_download_torrents"},
},
{
"cmd": "/pause_checking_torrents",
"event": EventType.PluginAction,
"desc": "暂停QB检查任务",
"category": "QB",
"data": {"action": "pause_checking_torrents"},
},
{
"cmd": "/resume_torrents",
"event": EventType.PluginAction,
"desc": "开始QB种子",
"desc": "开始QB所有任务",
"category": "QB",
"data": {"action": "resume_torrents"},
},
{
"cmd": "/qb_status",
"event": EventType.PluginAction,
"desc": "QB当前任务状态",
"category": "QB",
"data": {"action": "qb_status"},
},
{
"cmd": "/toggle_upload_limit",
"event": EventType.PluginAction,
@@ -209,7 +271,11 @@ class QbCommand(_PluginBase):
for torrent in torrents:
if torrent.state_enum.is_uploading and not torrent.state_enum.is_paused:
uploading_torrents.append(torrent.get("hash"))
elif torrent.state_enum.is_downloading and not torrent.state_enum.is_paused:
elif (
torrent.state_enum.is_downloading
and not torrent.state_enum.is_paused
and not torrent.state_enum.is_checking
):
downloading_torrents.append(torrent.get("hash"))
elif torrent.state_enum.is_checking:
checking_torrents.append(torrent.get("hash"))
@@ -236,7 +302,37 @@ class QbCommand(_PluginBase):
return
self.pause_torrent()
def pause_torrent(self):
@eventmanager.register(EventType.PluginAction)
def handle_pause_upload_torrent(self, event: Event):
if not self._enabled:
return
if event:
event_data = event.event_data
if not event_data or event_data.get("action") != "pause_upload_torrents":
return
self.pause_torrent(self.TorrentType.UPLOADING)
@eventmanager.register(EventType.PluginAction)
def handle_pause_download_torrent(self, event: Event):
if not self._enabled:
return
if event:
event_data = event.event_data
if not event_data or event_data.get("action") != "pause_download_torrents":
return
self.pause_torrent(self.TorrentType.DOWNLOADING)
@eventmanager.register(EventType.PluginAction)
def handle_pause_checking_torrent(self, event: Event):
if not self._enabled:
return
if event:
event_data = event.event_data
if not event_data or event_data.get("action") != "pause_checking_torrents":
return
self.pause_torrent(self.TorrentType.CHECKING)
def pause_torrent(self, type: TorrentType = TorrentType.ALL):
if not self._enabled:
return
@@ -244,7 +340,15 @@ class QbCommand(_PluginBase):
hash_downloading, hash_uploading, hash_paused, hash_checking, hash_error = (
self.get_torrents_status(all_torrents)
)
to_be_paused = hash_downloading + hash_uploading + hash_checking
if type == self.TorrentType.DOWNLOADING:
to_be_paused = hash_downloading
elif type == self.TorrentType.UPLOADING:
to_be_paused = hash_uploading
elif type == self.TorrentType.CHECKING:
to_be_paused = hash_checking
else:
to_be_paused = hash_downloading + hash_uploading + hash_checking
logger.info(
f"暂定任务启动 \n"
f"种子总数: {len(all_torrents)} \n"
@@ -260,12 +364,12 @@ class QbCommand(_PluginBase):
mtype=NotificationType.SiteMessage,
title=f"【QB暂停任务启动】",
text=f"种子总数: {len(all_torrents)} \n"
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n"
f"暂停操作中请稍等...\n",
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n"
f"暂停操作中请稍等...\n",
)
if len(to_be_paused) > 0:
if self._qb.stop_torrents(ids=(to_be_paused)):
@@ -300,11 +404,11 @@ class QbCommand(_PluginBase):
mtype=NotificationType.SiteMessage,
title=f"【QB暂停任务完成】",
text=f"种子总数: {len(all_torrents)} \n"
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n",
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n",
)
@eventmanager.register(EventType.PluginAction)
@@ -340,12 +444,12 @@ class QbCommand(_PluginBase):
mtype=NotificationType.SiteMessage,
title=f"【QB开始任务启动】",
text=f"种子总数: {len(all_torrents)} \n"
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n"
f"开始操作中请稍等...\n",
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n"
f"开始操作中请稍等...\n",
)
if not self._qb.start_torrents(ids=hash_paused):
logger.error(f"开始种子失败")
@@ -377,11 +481,52 @@ class QbCommand(_PluginBase):
mtype=NotificationType.SiteMessage,
title=f"【QB开始任务完成】",
text=f"种子总数: {len(all_torrents)} \n"
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n",
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n",
)
@eventmanager.register(EventType.PluginAction)
def handle_qb_status(self, event: Event):
if not self._enabled:
return
if event:
event_data = event.event_data
if not event_data or event_data.get("action") != "qb_status":
return
self.qb_status()
def qb_status(self):
if not self._enabled:
return
all_torrents = self.get_all_torrents()
hash_downloading, hash_uploading, hash_paused, hash_checking, hash_error = (
self.get_torrents_status(all_torrents)
)
logger.info(
f"QB开始任务启动 \n"
f"种子总数: {len(all_torrents)} \n"
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n"
f"开始操作中请稍等...\n",
)
if self._notify:
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【QB开始任务启动】",
text=f"种子总数: {len(all_torrents)} \n"
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n"
f"开始操作中请稍等...\n",
)
@eventmanager.register(EventType.PluginAction)
@@ -408,7 +553,12 @@ class QbCommand(_PluginBase):
if not self._enable_upload_limit or not self._enable_upload_limit:
return True
if not upload_limit or not upload_limit.isdigit() or not download_limit or not download_limit.isdigit():
if (
not upload_limit
or not upload_limit.isdigit()
or not download_limit
or not download_limit.isdigit()
):
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【QB远程操作】",
@@ -434,7 +584,8 @@ class QbCommand(_PluginBase):
download_limit_current_val, _ = self._qb.get_speed_limit()
return self._qb.set_speed_limit(
download_limit=int(download_limit_current_val), upload_limit=int(upload_limit)
download_limit=int(download_limit_current_val),
upload_limit=int(upload_limit),
)
def set_download_limit(self, download_limit):
@@ -451,7 +602,8 @@ class QbCommand(_PluginBase):
_, upload_limit_current_val = self._qb.get_speed_limit()
return self._qb.set_speed_limit(
download_limit=int(download_limit), upload_limit=int(upload_limit_current_val)
download_limit=int(download_limit),
upload_limit=int(upload_limit_current_val),
)
def set_limit(self, upload_limit, download_limit):
@@ -470,7 +622,6 @@ class QbCommand(_PluginBase):
elif flag is None and self._enabled and self._enable_upload_limit:
flag = self.set_upload_limit(upload_limit)
if flag:
logger.info(f"设置QB限速成功")
if self._notify:
@@ -654,6 +805,50 @@ class QbCommand(_PluginBase):
},
],
},
{
"component": "VRow",
"content": [
{
"component": "VCol",
"props": {"cols": 12, "md": 4},
"content": [
{
"component": "VSwitch",
"props": {
"model": "onlypauseupload",
"label": "暂停上传任务",
},
}
],
},
{
"component": "VCol",
"props": {"cols": 12, "md": 4},
"content": [
{
"component": "VSwitch",
"props": {
"model": "onlypausedownload",
"label": "暂停下载任务",
},
}
],
},
{
"component": "VCol",
"props": {"cols": 12, "md": 4},
"content": [
{
"component": "VSwitch",
"props": {
"model": "onlypausechecking",
"label": "暂停检查任务",
},
}
],
},
],
},
{
"component": "VRow",
"content": [
@@ -668,7 +863,7 @@ class QbCommand(_PluginBase):
"props": {
"type": "info",
"variant": "tonal",
"text": "开始周期和暂停周期使用Cron表达式0 0 0 * *",
"text": "开始周期和暂停周期使用Cron表达式0 0 0 * *,仅针对开始/暂定全部任务",
},
}
],
@@ -704,7 +899,7 @@ class QbCommand(_PluginBase):
},
}
],
}
},
],
},
],
@@ -714,6 +909,9 @@ class QbCommand(_PluginBase):
"notify": True,
"onlypauseonce": False,
"onlyresumeonce": False,
"onlypauseupload": False,
"onlypausedownload": False,
"onlypausechecking": False,
"upload_limit": 0,
"download_limit": 0,
"enable_upload_limit": False,

View File

@@ -11,6 +11,7 @@ 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()
@@ -90,7 +91,7 @@ class RemoveLink(_PluginBase):
# 插件图标
plugin_icon = "Ombi_A.png"
# 插件版本
plugin_version = "1.6"
plugin_version = "1.8"
# 插件作者
plugin_author = "DzAvril"
# 作者主页
@@ -98,7 +99,7 @@ class RemoveLink(_PluginBase):
# 插件配置项ID前缀
plugin_config_prefix = "linkdeleted_"
# 加载顺序
plugin_order = 27
plugin_order = 0
# 可使用的用户级别
auth_level = 1
@@ -174,6 +175,40 @@ 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 [
{
@@ -273,26 +308,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": "监控目录如有多个需换行,源目录和硬链接目录都需要添加到监控目录中;如需实现删除硬链接时不删除源文件,可把源文件目录配置到不删除目录中。",
},
}
]
],
}
]
}
],
},
],
}
], {
@@ -329,6 +364,25 @@ 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):
"""
处理删除事件
@@ -358,7 +412,10 @@ 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())
)