767 lines
33 KiB
Python
767 lines
33 KiB
Python
import re
|
||
import threading
|
||
import time
|
||
from datetime import datetime, timedelta
|
||
from typing import List, Tuple, Dict, Any, Optional
|
||
|
||
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.schemas import NotificationType
|
||
from app.utils.string import StringUtils
|
||
|
||
lock = threading.Lock()
|
||
|
||
|
||
class TorrentRemover(_PluginBase):
|
||
# 插件名称
|
||
plugin_name = "自动删种"
|
||
# 插件描述
|
||
plugin_desc = "自动删除下载器中的下载任务。"
|
||
# 插件图标
|
||
plugin_icon = "torrent.png"
|
||
# 主题色
|
||
plugin_color = "#02853F"
|
||
# 插件版本
|
||
plugin_version = "1.0"
|
||
# 插件作者
|
||
plugin_author = "jxxghp"
|
||
# 作者主页
|
||
author_url = "https://github.com/jxxghp"
|
||
# 插件配置项ID前缀
|
||
plugin_config_prefix = "torrentremover_"
|
||
# 加载顺序
|
||
plugin_order = 8
|
||
# 可使用的用户级别
|
||
auth_level = 2
|
||
|
||
# 私有属性
|
||
qb = None
|
||
tr = None
|
||
_event = threading.Event()
|
||
_scheduler = None
|
||
_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):
|
||
if config:
|
||
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(func=self.delete_torrents,
|
||
trigger=CronTrigger.from_crontab(self._cron),
|
||
name="自动删种服务")
|
||
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(func=self.delete_torrents, trigger='date',
|
||
run_date=datetime.now(
|
||
tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3)
|
||
)
|
||
# 关闭一次性开关
|
||
self._onlyonce = False
|
||
# 保存设置
|
||
self.update_config({
|
||
"enabled": self._enabled,
|
||
"notify": self._notify,
|
||
"onlyonce": self._onlyonce,
|
||
"action": self._action,
|
||
"cron": self._cron,
|
||
"downloaders": self._downloaders,
|
||
"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
|
||
|
||
})
|
||
if self._scheduler.get_jobs():
|
||
# 启动服务
|
||
self._scheduler.print_jobs()
|
||
self._scheduler.start()
|
||
|
||
def get_state(self) -> bool:
|
||
return True if self._enabled and self._cron and self._downloaders else False
|
||
|
||
@staticmethod
|
||
def get_command() -> List[Dict[str, Any]]:
|
||
pass
|
||
|
||
def get_api(self) -> List[Dict[str, Any]]:
|
||
pass
|
||
|
||
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
|
||
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 */12 * * *'
|
||
}
|
||
}
|
||
]
|
||
},
|
||
{
|
||
'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': 'errorkeywords',
|
||
'label': '错误信息关键词(TR)',
|
||
'placeholder': '支持正式表达式,仅适用于TR'
|
||
}
|
||
}
|
||
]
|
||
},
|
||
{
|
||
'component': 'VCol',
|
||
'props': {
|
||
'cols': 6
|
||
},
|
||
'content': [
|
||
{
|
||
'component': 'VTextField',
|
||
'props': {
|
||
'model': 'torrentstates',
|
||
'label': '任务状态(QB)',
|
||
'placeholder': '用,分隔多个状态,仅适用于QB'
|
||
}
|
||
}
|
||
]
|
||
},
|
||
{
|
||
'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': '立即运行一次',
|
||
}
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
'component': 'VRow',
|
||
'content': [
|
||
{
|
||
'component': 'VCol',
|
||
'props': {
|
||
'cols': 12,
|
||
},
|
||
'content': [
|
||
{
|
||
'component': 'VAlert',
|
||
'props': {
|
||
'type': 'info',
|
||
'variant': 'tonal',
|
||
'text': '自动删种存在风险,如设置不当可能导致数据丢失!建议动作先选择暂停,确定条件正确后再改成删除。'
|
||
}
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
], {
|
||
"enabled": False,
|
||
"notify": False,
|
||
"onlyonce": False,
|
||
"action": 'pause',
|
||
'downloaders': [],
|
||
"cron": '0 */12 * * *',
|
||
"samedata": False,
|
||
"mponly": False,
|
||
"size": "",
|
||
"ratio": "",
|
||
"time": "",
|
||
"upspeed": "",
|
||
"labels": "",
|
||
"pathkeywords": "",
|
||
"trackerkeywords": "",
|
||
"errorkeywords": "",
|
||
"torrentstates": "",
|
||
"torrentcategorys": ""
|
||
}
|
||
|
||
def get_page(self) -> List[dict]:
|
||
pass
|
||
|
||
def stop_service(self):
|
||
"""
|
||
退出插件
|
||
"""
|
||
try:
|
||
if self._scheduler:
|
||
self._scheduler.remove_all_jobs()
|
||
if self._scheduler.running:
|
||
self._event.set()
|
||
self._scheduler.shutdown()
|
||
self._event.clear()
|
||
self._scheduler = None
|
||
except Exception as e:
|
||
print(str(e))
|
||
|
||
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'))}"
|
||
# 暂停种子
|
||
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'))}"
|
||
# 删除种子
|
||
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'))}"
|
||
# 删除种子
|
||
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 >= int(maxsize) or torrent.size <= int(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 (not torrent.category or 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 >= int(maxsize) or torrent.total_size <= int(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_ids = [t.get("id") for t in 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 downloader == "qbittorrent":
|
||
plus_id = torrent.hash
|
||
plus_name = torrent.name
|
||
plus_size = torrent.size
|
||
plus_site = StringUtils.get_url_sld(torrent.tracker)
|
||
else:
|
||
plus_id = torrent.hashString
|
||
plus_name = torrent.name
|
||
plus_size = torrent.total_size
|
||
plus_site = torrent.trackers[0].get("sitename") if torrent.trackers else ""
|
||
# 比对名称和大小
|
||
if plus_name == name \
|
||
and plus_size == size \
|
||
and plus_id not in remove_ids:
|
||
remove_torrents_plus.append(
|
||
{
|
||
"id": plus_id,
|
||
"name": plus_name,
|
||
"site": plus_site,
|
||
"size": plus_size
|
||
}
|
||
)
|
||
if remove_torrents_plus:
|
||
remove_torrents.extend(remove_torrents_plus)
|
||
return remove_torrents
|