Files
MoviePilot-Plugins/plugins/trackereditor/__init__.py
honue b91f106de6 fix
2025-04-18 19:12:49 +08:00

447 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from typing import List, Tuple, Dict, Any, Union, Optional
from apscheduler.triggers.cron import CronTrigger
from app.log import logger
from app.modules.qbittorrent import Qbittorrent
from qbittorrentapi.torrents import TorrentInfoList
from app.modules.transmission import Transmission
from transmission_rpc.torrent import Torrent
from app.plugins import _PluginBase
from app.schemas import NotificationType
class TrackerEditor(_PluginBase):
# 插件名称
plugin_name = "Tracker替换"
# 插件描述
plugin_desc = "批量替换修改种子tracker"
# 插件图标
plugin_icon = "trackereditor_A.png"
# 插件版本
plugin_version = "1.8"
# 插件作者
plugin_author = "honue"
# 作者主页
author_url = "https://github.com/honue"
# 插件配置项ID前缀
plugin_config_prefix = "trackereditor_"
# 加载顺序
plugin_order = 30
# 可使用的用户级别
auth_level = 1
_downloader_type: str = None
_username: str = None
_password: str = None
_host: str = None
_port: int = None
_tracker_config: str = None
_onlyonce: bool = False
_downloader: Union[Qbittorrent, Transmission] = None
_run_con_enable: bool = False
_run_con: Optional[str] = None
_notify: bool = False
def init_plugin(self, config: dict = None):
if config:
self._onlyonce = config.get("onlyonce")
self._downloader_type = config.get("downloader_type")
self._host = config.get("host")
self._port = config.get("port")
self._username = config.get("username")
self._password = config.get("password")
self._tracker_config = config.get("tracker_config")
self._run_con_enable = config.get("run_con_enable")
self._run_con = config.get("run_con")
self._notify = config.get("notify")
if self._onlyonce:
# 执行替换
self.task()
self._onlyonce = False
# 更新onlyonce属性
self.__update_config()
def task(self):
tracker_configs: List[str] = self._tracker_config.split("\n")
tracker_dict = {}
for tracker_config in tracker_configs:
if tracker_config.count('|') == 1:
tracker_dict[tracker_config.split('|')[0]] = tracker_config.split('|')[1]
else:
logger.error(f"配置行错误: {tracker_config}")
logger.info(f"【TrackerEditor】: 开始执行Tracker替换")
torrent_total_cnt: int = 0
torrent_update_cnt: int = 0
if self._downloader_type == "qbittorrent":
self._downloader = Qbittorrent(self._host, self._port, self._username, self._password)
torrent_info_list: TorrentInfoList
torrent_info_list, error = self._downloader.get_torrents()
torrent_total_cnt = len(torrent_info_list)
if error:
return
for torrent in torrent_info_list:
for tracker in torrent.trackers:
for target_domain in tracker_dict.keys():
if target_domain in tracker.url:
original_url = tracker.url
new_url = tracker.url.replace(target_domain, tracker_dict[target_domain])
logger.info(f"{original_url[:30]}... 替换为 {new_url[:30]}...")
torrent.edit_tracker(orig_url=original_url, new_url=new_url)
torrent_update_cnt += 1
elif self._downloader_type == "transmission":
self._downloader = Transmission(self._host, self._port, self._username, self._password)
tr_version = self._downloader.get_session().get('version')
# "4.0.3 (6b0e49bbb2)" "3.00 (bb6b5a062e)"
torrent_list: List[Torrent]
torrent_list, error = self._downloader.get_torrents()
torrent_total_cnt = len(torrent_list)
if error:
return
for torrent in torrent_list:
new_tracker_list = []
for tracker in torrent.tracker_list:
tracker_replaced = False
for target_domain in tracker_dict.keys():
if target_domain in tracker:
new_url = tracker.replace(target_domain, tracker_dict[target_domain])
new_tracker_list.append(new_url)
logger.info(f"{tracker[:30]}... 替换为 {new_url[:30]}...")
torrent_update_cnt += 1
tracker_replaced = True
break
if not tracker_replaced:
new_tracker_list.append(tracker)
if int(tr_version[0]) >= 4:
# 版本大于等于4.x
__tracker_list = [new_tracker_list]
else:
__tracker_list = new_tracker_list
if torrent_update_cnt > 0:
update_result = self._downloader.update_tracker(hash_string=torrent.hashString, tracker_list=__tracker_list)
if not update_result:
logger.error(f"执行tracker修改出错中止本次执行")
torrent_update_cnt = 0
break
if torrent_update_cnt == 0:
logger.info(f"tracker修改条数为0")
logger.info(f"【TrackerEditor】: Tracker替换完成")
if (self._run_con_enable and self._notify) or (self._onlyonce and self._notify):
title = '【Tracker替换】'
msg = f'''扫描下载器{self._downloader_type}\n总的种子数: {torrent_total_cnt}\n已修改种子数: {torrent_update_cnt}'''
self.send_site_message(title, msg)
def __update_config(self):
self.update_config({
"onlyonce": self._onlyonce,
"downloader_type": self._downloader_type,
"username": self._username,
"password": self._password,
"host": self._host,
"port": self._port,
"tracker_config": self._tracker_config,
"run_cron_enable": self._run_con_enable,
"run_cron": self._run_con,
"notify": self._notify
})
@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': 'run_con_enable',
'label': '定时执行',
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'onlyonce',
'label': '立即运行一次',
}
}
]
}]
}, {
'component': 'VRow',
'content': [
{
'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': 'run_con',
'label': 'cron表达式',
'placeholder': '* * * * *'
}
}
]
}, {
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VSelect',
'props': {
'model': 'downloader_type',
'label': '下载器类型',
'items': [
{'title': 'Qbittorrent', 'value': 'qbittorrent'},
{'title': 'Transmission', 'value': 'transmission'}
]
}
}
]
}]
}, {
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'host',
'label': 'host主机ip',
'placeholder': '192.168.2.100'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'port',
'label': 'qb/tr端口',
'placeholder': '8989'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'username',
'label': '用户名',
'placeholder': 'username'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'password',
'label': '密码',
'placeholder': 'password'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
},
'content': [
{
'component': 'VTextarea',
'props': {
'model': 'tracker_config',
'label': 'tracker替换配置',
'rows': 6,
'placeholder': '每一行一个配置,中间以|分隔\n'
'待替换文本|替换的文本',
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
},
'content': [
{
'component': 'VAlert',
'props': {
'type': 'info',
'variant': 'tonal',
'text': '对下载器中所有符合代替换文本的tacker进行字符串replace替换' + '\n' +
'现有tracker: https://baidu.com/announce.php?passkey=xxxx' + '\n' +
'待替换 baidu.com 或 https://baidu.com' + '\n' +
'用于替换的文本 qq.com 或 https://qq.com' + '\n' +
'结果为 https://qq.com/announce.php?passkey=xxxx',
'style': 'white-space: pre-line;'
}
},
{
'component': 'VAlert',
'props': {
'type': 'info',
'variant': 'tonal',
'text': '强烈建议自己先添加一个tracker测试替换是否符合预期程序是否正常运行',
'style': 'white-space: pre-line;'
}
},
{
'component': 'VAlert',
'props': {
'type': 'info',
'variant': 'tonal',
'text': '支持qbtr仅支持4.0以上版本' + '\n',
'style': 'white-space: pre-line;'
}
}
]
}
]
}
]
}
], {
"onlyonce": False,
"downloader_type": "qbittorrent",
"host": "192.168.2.100",
"port": 8989,
"username": "username",
"password": "password",
"tracker_config":"",
"run_con_enable": False,
"run_con": "",
"notify": True
}
def get_page(self) -> List[dict]:
pass
def get_state(self) -> bool:
return True
def stop_service(self):
pass
def get_service(self) -> List[Dict[str, Any]]:
if self._run_con_enable and self._run_con:
logger.info(f"【TrackerEditor】: 注册定时任务")
return [
{
"id": "TrackerChangeRun",
"name": "定时Tracker替换",
"trigger": CronTrigger.from_crontab(self._run_con),
"func": self.task,
"kwargs": {}
}]
return []
def send_site_message(self, title, message):
self.post_message(
mtype=NotificationType.SiteMessage,
title=title,
text=message
)