fix TorrentRemover

This commit is contained in:
jxxghp 2023-08-08 17:26:26 +08:00
parent 78fd659e35
commit c51728dea1
3 changed files with 661 additions and 19 deletions

View File

@ -16,6 +16,7 @@ from app.modules.qbittorrent import Qbittorrent
from app.modules.transmission import Transmission from app.modules.transmission import Transmission
from app.plugins import _PluginBase from app.plugins import _PluginBase
from app.plugins.iyuuautoseed.iyuu_helper import IyuuHelper from app.plugins.iyuuautoseed.iyuu_helper import IyuuHelper
from app.schemas import NotificationType
from app.utils.http import RequestUtils from app.utils.http import RequestUtils
from app.utils.string import StringUtils from app.utils.string import StringUtils
@ -462,6 +463,7 @@ class IYUUAutoSeed(_PluginBase):
if self._notify: if self._notify:
if self.success or self.fail: if self.success or self.fail:
self.post_message( self.post_message(
mtype=NotificationType.SiteMessage,
title="【IYUU自动辅种任务完成】", title="【IYUU自动辅种任务完成】",
text=f"服务器返回可辅种总数:{self.total}\n" text=f"服务器返回可辅种总数:{self.total}\n"
f"实际可辅种数:{self.realtotal}\n" f"实际可辅种数:{self.realtotal}\n"

View File

@ -1,8 +1,22 @@
from typing import List, Tuple, Dict, Any import re
import threading
import time
from datetime import datetime, timedelta
from typing import List, Tuple, Dict, Any, Optional
from app.core.event import eventmanager import pytz
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from app.core.config import settings
from app.log import logger
from app.modules.qbittorrent import Qbittorrent
from app.modules.transmission import Transmission
from app.plugins import _PluginBase from app.plugins import _PluginBase
from app.schemas.types import EventType from app.schemas import NotificationType
from app.utils.string import StringUtils
lock = threading.Lock()
class TorrentRemover(_PluginBase): class TorrentRemover(_PluginBase):
@ -28,15 +42,97 @@ class TorrentRemover(_PluginBase):
auth_level = 2 auth_level = 2
# 私有属性 # 私有属性
downloader = None qb = None
tr = None
_event = threading.Event()
_scheduler = None
_enabled = False _enabled = False
_onlyonce = False
_notify = False
# pause/delete
_downloaders = []
_action = "pause"
_cron = None
_samedata = False
_mponly = False
_size = None
_ratio = None
_time = None
_upspeed = None
_labels = None
_pathkeywords = None
_trackerkeywords = None
_errorkeywords = None
_torrentstates = None
_torrentcategorys = None
def init_plugin(self, config: dict = None): def init_plugin(self, config: dict = None):
if config: if config:
self._enabled = config.get("enabled") self._enabled = config.get("enabled")
self._onlyonce = config.get("onlyonce")
self._notify = config.get("notify")
self._downloaders = config.get("downloaders") or []
self._action = config.get("action")
self._cron = config.get("cron")
self._samedata = config.get("samedata")
self._mponly = config.get("mponly")
self._size = config.get("size") or ""
self._ratio = config.get("ratio")
self._time = config.get("time")
self._upspeed = config.get("upspeed")
self._labels = config.get("labels") or ""
self._pathkeywords = config.get("pathkeywords") or ""
self._trackerkeywords = config.get("trackerkeywords") or ""
self._errorkeywords = config.get("errorkeywords") or ""
self._torrentstates = config.get("torrentstates") or ""
self._torrentcategorys = config.get("torrentcategorys") or ""
self.stop_service()
if self.get_state() or self._onlyonce:
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
self.qb = Qbittorrent()
self.tr = Transmission()
if self._cron:
try:
self._scheduler.add_job(self.delete_torrents,
CronTrigger.from_crontab(self._cron))
logger.info(f"自动删种服务启动,周期:{self._cron}")
except Exception as err:
logger.error(f"自动删种服务启动失败:{str(err)}")
self.systemmessage.put(f"自动删种服务启动失败:{str(err)}")
if self._onlyonce:
logger.info(f"自动删种服务启动,立即运行一次")
self._scheduler.add_job(self.delete_torrents, 'date',
run_date=datetime.now(
tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3)
)
# 关闭一次性开关
self._onlyonce = False
# 保存设置
self.update_config({
"enable": self._enabled,
"notify": self._notify,
"onlyonce": self._onlyonce,
"action": self._action,
"cron": self._cron,
"samedata": self._samedata,
"mponly": self._mponly,
"size": self._size,
"ratio": self._ratio,
"time": self._time,
"upspeed": self._upspeed,
"labels": self._labels,
"pathkeywords": self._pathkeywords,
"trackerkeywords": self._trackerkeywords,
"errorkeywords": self._errorkeywords,
"torrentstates": self._torrentstates,
"torrentcategorys": self._torrentcategorys
})
def get_state(self) -> bool: def get_state(self) -> bool:
return self._enabled return self._enabled and self._cron and self._downloaders
@staticmethod @staticmethod
def get_command() -> List[Dict[str, Any]]: def get_command() -> List[Dict[str, Any]]:
@ -46,23 +142,566 @@ class TorrentRemover(_PluginBase):
pass pass
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
pass return [
{
'component': 'VForm',
'content': [
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'enabled',
'label': '启用插件',
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'notify',
'label': '发送通知',
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'cron',
'label': '执行周期',
'placeholder': '0 0 0 ? *'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VSelect',
'props': {
'model': 'action',
'label': '动作',
'items': [
{'title': '暂停', 'value': 'pause'},
{'title': '删除种子', 'value': 'delete'},
{'title': '删除种子和文件', 'value': 'deletefile'}
]
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12
},
'content': [
{
'component': 'VSelect',
'props': {
'chips': True,
'multiple': True,
'model': 'downloaders',
'label': '下载器',
'items': [
{'title': 'Qbittorrent', 'value': 'qbittorrent'},
{'title': 'Transmission', 'value': 'transmission'}
]
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'size',
'label': '种子大小GB',
'placeholder': '例如1-10'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'ratio',
'label': '分享率',
'placeholder': ''
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'time',
'label': '做种时间(小时)',
'placeholder': ''
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'upspeed',
'label': '平均上传速度',
'placeholder': ''
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'labels',
'label': '标签',
'placeholder': '用,分隔多个标签'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'pathkeywords',
'label': '保存路径关键词',
'placeholder': '支持正式表达式'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'trackerkeywords',
'label': 'Tracker关键词',
'placeholder': '支持正式表达式'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'torrentstates',
'label': '任务状态',
'placeholder': '用,分隔多个状态'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'torrentcategorys',
'label': '任务分类',
'placeholder': '用,分隔多个分类'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'samedata',
'label': '处理辅种',
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'mponly',
'label': '仅MoviePilot任务',
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'onlyonce',
'label': '立即运行一次',
}
}
]
}
]
}
]
}
], {
"enable": False,
"notify": False,
"onlyonce": False,
"action": 'pause',
"cron": '0 0 0 ? *',
"samedata": False,
"mponly": False,
"size": "",
"ratio": "",
"time": "",
"upspeed": "",
"labels": "",
"pathkeywords": "",
"trackerkeywords": "",
"errorkeywords": "",
"torrentstates": "",
"torrentcategorys": ""
}
def get_page(self) -> List[dict]: def get_page(self) -> List[dict]:
pass pass
def stop_service(self): def stop_service(self):
pass
@eventmanager.register(EventType.HistoryDeleted)
def deletetorrent(self, event):
""" """
联动删除下载器中的下载任务 退出插件
""" """
if not self._enabled: try:
return if self._scheduler:
event_info = event.event_data self._scheduler.remove_all_jobs()
if not event_info: if self._scheduler.running:
return self._event.set()
self._scheduler.shutdown()
self._event.clear()
self._scheduler = None
except Exception as e:
print(str(e))
# TODO 删除所有下载任务 def __get_downloader(self, dtype: str):
"""
根据类型返回下载器实例
"""
if dtype == "qbittorrent":
return self.qb
elif dtype == "transmission":
return self.tr
else:
return None
def delete_torrents(self):
"""
定时删除下载器中的下载任务
"""
for downloader in self._downloaders:
try:
with lock:
# 获取需删除种子列表
torrents = self.get_remove_torrents(downloader)
logger.info(f"自动删种任务 获取符合处理条件种子数 {len(torrents)}")
# 下载器
downlader_obj = self.__get_downloader(downloader)
if self._action == "pause":
message_text = f"{downloader.title()} 共暂停{len(torrents)}个种子"
for torrent in torrents:
if self._event.is_set():
logger.info(f"自动删种服务停止")
return
text_item = f"{torrent.get('name')} " \
f"来自站点:{torrent.get('site')} " \
f"大小:{StringUtils.str_filesize(torrent.get('size'))} GB"
# 暂停种子
downlader_obj.stop_torrents(ids=[torrent.get("id")])
logger.info(f"自动删种任务 暂停种子:{text_item}")
message_text = f"{message_text}\n{text_item}"
elif self._action == "delete":
message_text = f"{downloader.title()} 共删除{len(torrents)}个种子"
for torrent in torrents:
if self._event.is_set():
logger.info(f"自动删种服务停止")
return
text_item = f"{torrent.get('name')} " \
f"来自站点:{torrent.get('site')} " \
f"大小:{StringUtils.str_filesize(torrent.get('size'))} GB"
# 删除种子
downlader_obj.delete_torrents(delete_file=False,
ids=[torrent.get("id")])
logger.info(f"自动删种任务 删除种子:{text_item}")
message_text = f"{message_text}\n{text_item}"
elif self._action == "deletefile":
message_text = f"{downloader.title()} 共删除{len(torrents)}个种子及文件"
for torrent in torrents:
if self._event.is_set():
logger.info(f"自动删种服务停止")
return
text_item = f"{torrent.get('name')} " \
f"来自站点:{torrent.get('site')} " \
f"大小:{StringUtils.str_filesize(torrent.get('size'))} GB"
# 删除种子
downlader_obj.delete_torrents(delete_file=True,
ids=[torrent.get("id")])
logger.info(f"自动删种任务 删除种子及文件:{text_item}")
message_text = f"{message_text}\n{text_item}"
else:
continue
if torrents and message_text and self._notify:
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【自动删种任务执行完成】",
text=message_text
)
except Exception as e:
logger.error(f"自动删种任务异常:{str(e)}")
def __get_qb_torrent(self, torrent: Any) -> Optional[dict]:
"""
检查QB下载任务是否符合条件
"""
# 完成时间
date_done = torrent.completion_on if torrent.completion_on > 0 else torrent.added_on
# 现在时间
date_now = int(time.mktime(datetime.now().timetuple()))
# 做种时间
torrent_seeding_time = date_now - date_done if date_done else 0
# 平均上传速度
torrent_upload_avs = torrent.uploaded / torrent_seeding_time if torrent_seeding_time else 0
# 大小 单位GB
sizes = self._size.split(',') if self._size else []
minsize = sizes[0] * 1024 * 1024 * 1024 if sizes else 0
maxsize = sizes[-1] * 1024 * 1024 * 1024 if sizes else 0
# 分享率
if self._ratio and torrent.ratio <= float(self._ratio):
return None
# 做种时间 单位:小时
if self._time and torrent_seeding_time <= float(self._time) * 3600:
return None
# 文件大小
if self._size and (torrent.size >= maxsize or torrent.size <= minsize):
return None
if self._upspeed and torrent_upload_avs >= float(self._upspeed) * 1024:
return None
if self._pathkeywords and not re.findall(self._pathkeywords, torrent.save_path, re.I):
return None
if self._trackerkeywords and not re.findall(self._trackerkeywords, torrent.tracker, re.I):
return None
if self._torrentstates and torrent.state not in self._torrentstates:
return None
if self._torrentcategorys and torrent.category not in self._torrentcategorys:
return None
return {
"id": torrent.hash,
"name": torrent.name,
"site": StringUtils.get_url_sld(torrent.tracker),
"size": torrent.size
}
def __get_tr_torrent(self, torrent: Any) -> Optional[dict]:
"""
检查TR下载任务是否符合条件
"""
# 完成时间
date_done = torrent.date_done or torrent.date_added
# 现在时间
date_now = int(time.mktime(datetime.now().timetuple()))
# 做种时间
torrent_seeding_time = date_now - int(time.mktime(date_done.timetuple())) if date_done else 0
# 上传量
torrent_uploaded = torrent.ratio * torrent.total_size
# 平均上传速茺
torrent_upload_avs = torrent_uploaded / torrent_seeding_time if torrent_seeding_time else 0
# 大小 单位GB
sizes = self._size.split(',') if self._size else []
minsize = sizes[0] * 1024 * 1024 * 1024 if sizes else 0
maxsize = sizes[-1] * 1024 * 1024 * 1024 if sizes else 0
# 分享率
if self._ratio and torrent.ratio <= float(self._ratio):
return None
if self._time and torrent_seeding_time <= float(self._time) * 3600:
return None
if self._size and (torrent.total_size >= maxsize or torrent.total_size <= minsize):
return None
if self._upspeed and torrent_upload_avs >= float(self._upspeed) * 1024:
return None
if self._pathkeywords and not re.findall(self._pathkeywords, torrent.download_dir, re.I):
return None
if self._trackerkeywords:
if not torrent.trackers:
return None
else:
tacker_key_flag = False
for tracker in torrent.trackers:
if re.findall(self._trackerkeywords, tracker.get("announce", ""), re.I):
tacker_key_flag = True
break
if not tacker_key_flag:
return None
if self._errorkeywords and not re.findall(self._errorkeywords, torrent.error_string, re.I):
return None
return {
"id": torrent.hashString,
"name": torrent.name,
"site": torrent.trackers[0].get("sitename") if torrent.trackers else "",
"size": torrent.total_size
}
def get_remove_torrents(self, downloader: str):
"""
获取自动删种任务种子
"""
remove_torrents = []
# 下载器对象
downloader_obj = self.__get_downloader(downloader)
# 标题
if self._labels:
tags = self._labels.split(',')
else:
tags = []
if self._mponly:
tags.extend(settings.TORRENT_TAG)
# 查询种子
torrents, error_flag = downloader_obj.get_torrents(tags=tags or None)
if error_flag:
return []
# 处理种子
for torrent in torrents:
if downloader == "qbittorrent":
item = self.__get_qb_torrent(torrent)
else:
item = self.__get_tr_torrent(torrent)
if not item:
continue
remove_torrents.append(item)
# 处理辅种
if self._samedata and remove_torrents:
remove_torrents_plus = []
for remove_torrent in remove_torrents:
name = remove_torrent.get("name")
size = remove_torrent.get("size")
for torrent in torrents:
if torrent.name == name \
and torrent.size == size \
and torrent.hash not in [t.get("id") for t in remove_torrents]:
remove_torrents_plus.append({
"id": torrent.hash,
"name": torrent.name,
"site": StringUtils.get_url_sld(torrent.tracker),
"size": torrent.size
})
remove_torrents.extend(remove_torrents_plus)
return remove_torrents

View File

@ -16,6 +16,7 @@ from app.log import logger
from app.modules.qbittorrent import Qbittorrent from app.modules.qbittorrent import Qbittorrent
from app.modules.transmission import Transmission from app.modules.transmission import Transmission
from app.plugins import _PluginBase from app.plugins import _PluginBase
from app.schemas import NotificationType
from app.utils.string import StringUtils from app.utils.string import StringUtils
@ -324,8 +325,7 @@ class TorrentTransfer(_PluginBase):
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12
'md': 12
}, },
'content': [ 'content': [
{ {
@ -644,6 +644,7 @@ class TorrentTransfer(_PluginBase):
# 发送通知 # 发送通知
if self._notify: if self._notify:
self.post_message( self.post_message(
mtype=NotificationType.SiteMessage,
title="【移转做种任务执行完成】", title="【移转做种任务执行完成】",
text=f"总数:{total},成功:{success},失败:{fail}" text=f"总数:{total},成功:{success},失败:{fail}"
) )