Merge remote-tracking branch 'origin/main'

This commit is contained in:
jxxghp
2024-03-23 13:34:49 +08:00
11 changed files with 1035 additions and 22 deletions

BIN
icons/ipAddress.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
icons/trackereditor_A.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -34,7 +34,7 @@
"DoubanSync": {
"name": "豆瓣想看",
"description": "同步豆瓣想看数据,自动添加订阅。",
"version": "1.3",
"version": "1.4",
"icon": "douban.png",
"author": "jxxghp",
"level": 2
@@ -58,7 +58,7 @@
"DoubanRank": {
"name": "豆瓣榜单订阅",
"description": "监控豆瓣热门榜单,自动添加订阅。",
"version": "1.6",
"version": "1.7",
"icon": "movie.jpg",
"author": "jxxghp",
"level": 2
@@ -338,7 +338,7 @@
"DownloadSiteTag": {
"name": "下载任务分类与标签",
"description": "自动给下载任务分类与打站点标签、剧集名称标签",
"version": "1.9",
"version": "2.0",
"icon": "Youtube-dl_B.png",
"author": "叮叮当",
"level": 1
@@ -395,7 +395,7 @@
"name": "诊断参数调整",
"description": "Emby专用插件|暂时性解决emby字幕偏移问题需要emby安装Diagnostics插件。",
"version": "1.3",
"icon": "Themeengine_A.png",
"icon": "Gatus_A.png",
"author": "jeblove",
"level": 1
},
@@ -407,12 +407,28 @@
"author": "DzAvril",
"level": 1
},
"TrCommond": {
"TrCommand": {
"name": "TR远程操作",
"description": "通过定时任务或交互命令远程操作TR暂停/开始/限速等。",
"version": "1.1",
"icon": "Transmission_A.png",
"author": "Hoey",
"level": 1
},
"IpDetect": {
"name": "本地IP检测",
"description": "如果QB、TR等服务在本地部署当本地IP改变时自动修改其server IP",
"version": "1.0",
"icon": "ipAddress.png",
"author": "DzAvril",
"level": 1
},
"TrackerEditor": {
"name": "Tracker替换",
"description": "批量替换种子tracker支持周期性巡检如为TR仅支持4.0以上版本)",
"version": "1.4",
"icon": "trackereditor_A.png",
"author": "honue",
"level": 1
}
}

View File

@@ -21,7 +21,7 @@ class DiagParamAdjust(_PluginBase):
# 插件描述
plugin_desc = "Emby专用插件|暂时性解决emby字幕偏移问题需要emby安装Diagnostics插件。"
# 插件图标
plugin_icon = "Themeengine_A.png"
plugin_icon = "Gatus_A.png"
# 插件版本
plugin_version = "1.3"
# 插件作者

View File

@@ -9,6 +9,7 @@ from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from app.chain.download import DownloadChain
from app.chain.media import MediaChain
from app.chain.subscribe import SubscribeChain
from app.core.config import settings
from app.core.context import MediaInfo
@@ -27,7 +28,7 @@ class DoubanRank(_PluginBase):
# 插件图标
plugin_icon = "movie.jpg"
# 插件版本
plugin_version = "1.6"
plugin_version = "1.7"
# 插件作者
plugin_author = "jxxghp"
# 作者主页
@@ -44,6 +45,7 @@ class DoubanRank(_PluginBase):
# 私有属性
downloadchain: DownloadChain = None
subscribechain: SubscribeChain = None
mediachain: MediaChain = None
_scheduler = None
_douban_address = {
'movie-ustop': 'https://rsshub.app/douban/movie/ustop',
@@ -68,6 +70,7 @@ class DoubanRank(_PluginBase):
def init_plugin(self, config: dict = None):
self.downloadchain = DownloadChain()
self.subscribechain = SubscribeChain()
self.mediachain = MediaChain()
if config:
self._enabled = config.get("enabled")
@@ -514,11 +517,20 @@ class DoubanRank(_PluginBase):
# 识别媒体信息
if douban_id:
# 识别豆瓣信息
mediainfo = self.chain.recognize_media(meta=meta, doubanid=douban_id)
if not mediainfo:
logger.warn(f'未识别到媒体信息,标题:{title}豆瓣ID{douban_id}')
continue
if settings.RECOGNIZE_SOURCE == "themoviedb":
tmdbinfo = self.mediachain.get_tmdbinfo_by_doubanid(doubanid=douban_id, mtype=meta.type)
if not tmdbinfo:
logger.warn(f'未能通过豆瓣ID {douban_id} 获取到TMDB信息标题{title}豆瓣ID{douban_id}')
continue
mediainfo = self.chain.recognize_media(meta=meta, tmdbid=tmdbinfo.get("id"))
if not mediainfo:
logger.warn(f'TMDBID {tmdbinfo.get("id")} 未识别到媒体信息')
continue
else:
mediainfo = self.chain.recognize_media(meta=meta, doubanid=douban_id)
if not mediainfo:
logger.warn(f'豆瓣ID {douban_id} 未识别到媒体信息')
continue
else:
# 匹配媒体信息
mediainfo: MediaInfo = self.chain.recognize_media(meta=meta)

View File

@@ -6,6 +6,8 @@ from typing import Optional, Any, List, Dict, Tuple
import pytz
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from app.chain.media import MediaChain
from app.schemas.types import MediaType
from app.chain.download import DownloadChain
@@ -31,7 +33,7 @@ class DoubanSync(_PluginBase):
# 插件图标
plugin_icon = "douban.png"
# 插件版本
plugin_version = "1.3"
plugin_version = "1.4"
# 插件作者
plugin_author = "jxxghp"
# 作者主页
@@ -51,6 +53,7 @@ class DoubanSync(_PluginBase):
downloadchain = None
searchchain = None
subscribechain = None
mediachain = None
# 配置属性
_enabled: bool = False
@@ -67,6 +70,7 @@ class DoubanSync(_PluginBase):
self.downloadchain = DownloadChain()
self.searchchain = SearchChain()
self.subscribechain = SubscribeChain()
self.mediachain = MediaChain()
# 停止现有任务
self.stop_service()
@@ -497,12 +501,21 @@ class DoubanSync(_PluginBase):
# 识别媒体信息
meta = MetaInfo(title=title)
douban_info = self.chain.douban_info(doubanid=douban_id)
meta.year = douban_info.get("year")
meta.type = MediaType.MOVIE if douban_info.get("type") == "movie" else MediaType.TV
mediainfo = self.chain.recognize_media(meta=meta, doubanid=douban_id)
if not mediainfo:
logger.warn(f'未识别到媒体信息,标题:{title}豆瓣ID{douban_id}')
continue
if settings.RECOGNIZE_SOURCE == "themoviedb":
tmdbinfo = self.mediachain.get_tmdbinfo_by_doubanid(doubanid=douban_id, mtype=meta.type)
if not tmdbinfo:
logger.warn(f'未能通过豆瓣ID {douban_id} 获取到TMDB信息标题{title}豆瓣ID{douban_id}')
continue
mediainfo = self.chain.recognize_media(meta=meta, tmdbid=tmdbinfo.get("id"))
if not mediainfo:
logger.warn(f'TMDBID {tmdbinfo.get("id")} 未识别到媒体信息')
continue
else:
mediainfo = self.chain.recognize_media(meta=meta, doubanid=douban_id)
if not mediainfo:
logger.warn(f'豆瓣ID {douban_id} 未识别到媒体信息')
continue
# 查询缺失的媒体信息
exist_flag, no_exists = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo)
if exist_flag:

View File

@@ -28,7 +28,7 @@ class DownloadSiteTag(_PluginBase):
# 插件图标
plugin_icon = "Youtube-dl_B.png"
# 插件版本
plugin_version = "1.9"
plugin_version = "2.0"
# 插件作者
plugin_author = "叮叮当"
# 作者主页
@@ -190,6 +190,11 @@ class DownloadSiteTag(_PluginBase):
# JackettIndexers索引器支持多个站点, 如果不存在历史记录, 则通过tracker会再次附加其他站点名称
indexers.append("JackettIndexers")
indexers = set(indexers)
tracker_mappings = {
"chdbits.xyz": "ptchdbits.co",
"agsvpt.trackers.work": "agsvpt.com",
"tracker.cinefiles.info": "audiences.me",
}
for DOWNLOADER in ["qbittorrent", "transmission"]:
logger.info(f"{self.LOG_TAG}开始扫描下载器 {DOWNLOADER} ...")
# 获取下载器中的种子
@@ -241,7 +246,13 @@ class DownloadSiteTag(_PluginBase):
elif not history.torrent_site:
trackers = self._get_trackers(torrent=torrent, dl_type=DOWNLOADER)
for tracker in trackers:
domain = StringUtils.get_url_domain(tracker)
# 检查tracker是否包含特定的关键字并进行相应的映射
for key, mapped_domain in tracker_mappings.items():
if key in tracker:
domain = mapped_domain
break
else:
domain = StringUtils.get_url_domain(tracker)
site_info = self.sites_helper.get_indexer(domain)
if site_info:
history.torrent_site = site_info.get("name")

View File

@@ -0,0 +1,520 @@
import socket
from typing import List, Tuple, Dict, Any
import re
from app.log import logger
from app.plugins import _PluginBase
from app.schemas import Notification, NotificationType
from app.core.config import settings
from dotenv import set_key
from app.core.module import ModuleManager
from app.scheduler import Scheduler
from apscheduler.triggers.cron import CronTrigger
class IpDetect(_PluginBase):
# 插件名称
plugin_name = "本地IP检测"
# 插件描述
plugin_desc = "如果QB、TR等服务在本地部署当本地IP改变时自动修改其server IP"
# 插件图标
plugin_icon = "ipAddress.png"
# 插件版本
plugin_version = "1.0"
# 插件作者
plugin_author = "DzAvril"
# 作者主页
author_url = "https://github.com/DzAvril"
# 插件配置项ID前缀
plugin_config_prefix = "ipdetect_"
# 加载顺序
plugin_order = 0
# 可使用的用户级别
auth_level = 1
# preivate property
_enabled = False
_notify = False
_enable_qb = False
_enable_tr = False
_enable_emby = False
_enable_emby_play = False
_enable_jellyfin = False
_enable_jellyfin_play = False
_enable_plex = False
_enable_plex_play = False
_onlyonce = False
_cron = ""
_setting_keys = []
def init_plugin(self, config: dict = None):
logger.info(f"Hello IpDetect, config {config}")
if config:
self._enabled = config.get("enabled")
self._onlyonce = config.get("onlyonce")
self._cron = config.get("cron")
self._notify = config.get("notify")
self._enable_qb = config.get("enable_qb")
self._enable_tr = config.get("enable_tr")
self._enable_emby = config.get("enable_emby")
self._enable_emby_play = config.get("enable_emby_play")
self._enable_jellyfin = config.get("enable_jellyfin")
self._enable_jellyfin_play = config.get("enable_jellyfin_play")
self._enable_plex = config.get("enable_plex")
self._enable_plex_play = config.get("enable_plex_play")
if not self._enabled:
return
self._setting_keys = []
if self._enable_qb:
if settings.QB_HOST is not None:
self._setting_keys.append("QB_HOST")
else:
logger.warn("QB服务地址未设置请检查配置")
if self._enable_tr:
if settings.TR_HOST is not None:
self._setting_keys.append("TR_HOST")
else:
self._enable_tr = False
logger.warn("TR服务地址未设置请检查配置")
if self._enable_emby:
if settings.EMBY_HOST is not None:
self._setting_keys.append("EMBY_HOST")
else:
self._enable_emby = False
logger.warn("Emby服务地址未设置请检查配置")
if self._enable_emby_play:
if settings.EMBY_PLAY_HOST is not None:
self._setting_keys.append("EMBY_PLAY_HOST")
else:
self._enable_emby_play = False
logger.warn("Emby外网播放地址未设置请检查配置")
if self._enable_jellyfin:
if settings.JELLYFIN_HOST is not None:
self._setting_keys.append("JELLYFIN_HOST")
else:
self._enable_jellyfin = False
logger.warn("Jellyfin服务地址未设置请检查配置")
if self._enable_jellyfin_play:
if settings.JELLYFIN_PLAY_HOST is not None:
self._setting_keys.append("JELLYFIN_PLAY_HOST")
else:
self._enable_jellyfin_play = False
logger.warn("Jellyfin外网播放地址未设置请检查配置")
if self._enable_plex:
if settings.PLEX_HOST is not None:
self._setting_keys.append("PLEX_HOST")
else:
self._enable_plex = False
logger.warn("Plex服务地址未设置请检查配置")
if self._enable_plex_play:
if settings.PLEX_PLAY_HOST is not None:
self._setting_keys.append("PLEX_PLAY_HOST")
else:
self._enable_plex_play = False
logger.warn("Plex外网播放地址未设置请检查配置")
# 更新配置
self.__update_config()
logger.info(f"_setting_keys: {self._setting_keys}")
if self._onlyonce:
self._onlyonce = False
self.__update_config()
self.detect_ip()
def __update_config(self):
self.update_config({
"enabled": self._enabled,
"onlyonce": self._onlyonce,
"cron": self._cron,
"notify": self._notify,
"enable_qb": self._enable_qb,
"enable_tr": self._enable_tr,
"enable_emby": self._enable_emby,
"enable_emby_play": self._enable_emby_play,
"enable_jellyfin": self._enable_jellyfin,
"enable_jellyfin_play": self._enable_jellyfin_play,
"enable_plex": self._enable_plex,
"enable_plex_play": self._enable_plex_play,
})
def get_state(self) -> bool:
return self._enabled
def detect_ip(self):
if len(self._setting_keys) == 0:
return
local_ip = self.get_local_ip()
current_ip = self.parse_ip(self.get_value(self._setting_keys[0]))
logger.info(f"current_ip: {current_ip}")
if local_ip == current_ip:
logger.info(f"当前IP地址为{local_ip},没有变化!")
if self._notify:
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【本地IP检测】",
text=f"未检测到IP地址变化",
)
return
for key in self._setting_keys:
prefix = (
True
if key == "EMBY_PLAY_HOST"
or key == "PLEX_PLAY_HOST"
or key == "JELLYFIN_PLAY_HOST"
else False
)
self.update_key_value(key, local_ip, prefix)
# 重新加载模块
logger.info("重新加载模块")
ModuleManager().reload()
Scheduler().init()
if self._notify:
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【本地IP检测】",
text=f"检测到本地IP变为{local_ip},已更新服务地址!",
)
def update_key_value(self, k, v, prefix):
old_value = self.get_value(k)
if prefix: # http(s)://ip:port
ip_pattern = r"https?://(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)"
v = re.sub(
ip_pattern,
lambda m: "{}://{}:{}".format(m.group(0).split(":")[0], v, m.group(2)),
old_value,
)
else: # ip:port
ip_pattern = r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)'
v = re.sub(ip_pattern, r'{}:\2'.format(v), old_value)
if hasattr(settings, k):
if v == "None":
v = None
setattr(settings, k, v)
if v is None:
v = ""
else:
v = str(v)
set_key(settings.CONFIG_PATH / "app.env", k, v)
logger.info(f"重新设置服务地址{k}成功!")
def get_value(self, key):
if key == "QB_HOST":
return settings.QB_HOST
elif key == "TR_HOST":
return settings.TR_HOST
elif key == "EMBY_HOST":
return settings.EMBY_HOST
elif key == "EMBY_PLAY_HOST":
return settings.EMBY_PLAY_HOST
elif key == "JELLYFIN_HOST":
return settings.JELLYFIN_HOST
elif key == "JELLYFIN_PLAY_HOST":
return settings.JELLYFIN_PLAY_HOST
elif key == "PLEX_HOST":
return settings.PLEX_HOST
elif key == "PLEX_PLAY_HOST":
return settings.PLEX_PLAY_HOST
else:
return None
def parse_ip(self, ip):
ip_pattern = r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
match = re.search(ip_pattern, ip)
if match:
return match.group(1)
else:
return None
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 and self._cron:
return [
{
"id": "IpDetect",
"name": "检测本地IP变化",
"trigger": CronTrigger.from_crontab(self._cron),
"func": self.detect_ip,
"kwargs": {},
}
]
def get_local_ip(self):
try:
# 创建一个 UDP 套接字
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 连接到一个虚拟的目标IP和端口
s.connect(("10.255.255.255", 1))
# 获取本地 IP 地址
local_ip = s.getsockname()[0]
logger.info(f"当前本地IP为{local_ip}")
return local_ip
except socket.error:
return "127.0.0.1" # 如果无法获取到本地IP则返回本地回环地址
@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": 4},
"content": [
{
"component": "VTextField",
"props": {
"model": "cron",
"label": "检测周期",
},
}
],
},
],
},
{
"component": "VRow",
"content": [
{
"component": "VCol",
"props": {"cols": 12, "md": 6},
"content": [
{
"component": "VSwitch",
"props": {
"model": "notify",
"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": "enable_qb",
"label": "QB下载器",
},
}
],
},
{
"component": "VCol",
"props": {"cols": 12, "md": 6},
"content": [
{
"component": "VSwitch",
"props": {
"model": "enable_tr",
"label": "TR下载器",
},
}
],
},
],
},
{
"component": "VRow",
"content": [
{
"component": "VCol",
"props": {"cols": 12, "md": 6},
"content": [
{
"component": "VSwitch",
"props": {
"model": "enable_emby",
"label": "Emby服务",
},
}
],
},
{
"component": "VCol",
"props": {"cols": 12, "md": 6},
"content": [
{
"component": "VSwitch",
"props": {
"model": "enable_emby_play",
"label": "Emby外网播放",
},
}
],
},
],
},
{
"component": "VRow",
"content": [
{
"component": "VCol",
"props": {"cols": 12, "md": 6},
"content": [
{
"component": "VSwitch",
"props": {
"model": "enable_jellyfin",
"label": "Jellyfin服务",
},
}
],
},
{
"component": "VCol",
"props": {"cols": 12, "md": 6},
"content": [
{
"component": "VSwitch",
"props": {
"model": "enable_jellyfin_play",
"label": "Jellyfin外网播放",
},
}
],
},
],
},
{
"component": "VRow",
"content": [
{
"component": "VCol",
"props": {"cols": 12, "md": 6},
"content": [
{
"component": "VSwitch",
"props": {
"model": "enable_plex",
"label": "Plex服务",
},
}
],
},
{
"component": "VCol",
"props": {"cols": 12, "md": 6},
"content": [
{
"component": "VSwitch",
"props": {
"model": "enable_plex_play",
"label": "Plex外网播放",
},
}
],
},
],
},
{
"component": "VRow",
"content": [
{
"component": "VCol",
"props": {
"cols": 12,
},
"content": [
{
"component": "VAlert",
"props": {
"type": "info",
"variant": "tonal",
"text": "本插件针对部署在本地的服务如QB下载器、Emby服务等检测到本地IP变化时同步修改服务地址请勾选部署在本地的服务。",
},
}
]
},
{
"component": "VCol",
"props": {
"cols": 12,
},
"content": [
{
"component": "VAlert",
"props": {
"type": "info",
"variant": "tonal",
"text": "本插件不适用于桥接模式的Docker因为获取不到Host的IP地址",
},
}
],
},
],
},
],
}
], {
"enabled": False,
"notify": False,
"onlyonce": False,
"enable_qb": False,
"enable_tr": False,
"enable_emby": False,
"enable_emby_play": False,
"enable_jellyfin": False,
"enable_jellyfin_play": False,
"enable_plex": False,
"enable_plex_play": False,
}
def get_page(self) -> List[dict]:
pass
def stop_service(self):
pass

View File

@@ -11,7 +11,7 @@ from app.core.event import eventmanager, Event
import time
class QbCommond(_PluginBase):
class QbCommand(_PluginBase):
# 插件名称
plugin_name = "QB远程操作"
# 插件描述

View File

@@ -0,0 +1,441 @@
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支持周期性巡检如为TR仅支持4.0以上版本)"
# 插件图标
plugin_icon = "trackereditor_A.png"
# 插件版本
plugin_version = "1.4"
# 插件作者
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
_target_domain: str = None
_replace_domain: 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._target_domain = config.get("target_domain")
self._replace_domain = config.get("replace_domain")
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):
logger.info(f"{'*' * 30}TrackerEditor: 开始执行Tracker替换{'*' * 30}")
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:
if self._target_domain in tracker.url:
original_url = tracker.url
new_url = tracker.url.replace(self._target_domain, self._replace_domain)
logger.info(f"{original_url} 替换为\n {new_url}")
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)
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:
new_url = None
if self._target_domain in tracker:
new_url = tracker.replace(self._target_domain, self._replace_domain)
new_tracker_list.append(new_url)
else:
new_tracker_list.append(tracker)
logger.info(f"{tracker} 替换为\n {new_url}")
torrent_update_cnt += 1
self._downloader.update_tracker(hash_string=torrent.hashString, tracker_list=new_tracker_list)
logger.info(f"{'*' * 30}TrackerEditor: Tracker替换成功{'*' * 30}")
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,
"target_domain": self._target_domain,
"replace_domain": self._replace_domain,
"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': '启用周期性巡检 (注: 请开启时务必填写cron表达式)',
}
}
]
},
{
'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,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'target_domain',
'label': '待替换文本',
'placeholder': 'target.com'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'replace_domain',
'label': '替换的文本',
'placeholder': 'replace.net'
}
}
]
}
]
}, {
'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': '周期性巡检时指的是允许设置间隔一段进行巡检下载器中的种子Tracker' + '\n'
'当匹配到等待替换的tracker时进行替换其中cron表达式是5位例如:* * * * * 指的是每过一分钟轮训一次',
'style': 'white-space: pre-line;'
}
}
]
}
]
}
]
}
], {
"onlyonce": False,
"downloader_type": "qbittorrent",
"host": "192.168.2.100",
"port": 8989,
"username": "username",
"password": "password",
"target_domain": "",
"replace_domain": "",
"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"{'*' * 30}TrackerEditor: 注册公共调度服务{'*' * 30}")
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
)

View File

@@ -10,7 +10,7 @@ from app.core.event import eventmanager, Event
import time
class TrCommond(_PluginBase):
class TrCommand(_PluginBase):
# 插件名称
plugin_name = "TR远程操作"
# 插件描述