Merge remote-tracking branch 'origin/main'

# Conflicts:
#	package.json
This commit is contained in:
jxxghp
2024-05-08 09:15:10 +08:00
12 changed files with 294 additions and 81 deletions

View File

@@ -27,11 +27,12 @@
"name": "站点数据统计",
"description": "自动统计和展示站点数据。",
"labels": "站点",
"version": "3.0",
"version": "3.1",
"icon": "statistic.png",
"author": "lightolly",
"level": 2,
"history": {
"v3.1": "修复观众无法统计做总数和做种体积的bug",
"v3.0": "适配馒头数据统计需要升级至v1.8.5+版本且在站点信息中维护好API Key",
"v2.9": "增强API安全性",
"v2.8": "修复馒头未读消息统计",
@@ -259,10 +260,13 @@
"name": "整理VCB动漫压制组作品",
"description": "提高部分VCB-Studio作品的识别准确率,将VCB-Studio的作品统一转移到指定目录同时进行刮削整理",
"labels": "文件整理,识别",
"version": "1.7",
"version": "1.7.1",
"icon": "vcbmonitor.png",
"author": "pixel@qingwa",
"level": 2
"level": 2,
"history": {
"v1.7.1": "修复偶尔安装失败问题"
}
},
"TorrentTransfer": {
"name": "自动转移做种",
@@ -298,11 +302,12 @@
"name": "站点刷流",
"description": "自动托管刷流,将会提高对应站点的访问频率。",
"labels": "刷流",
"version": "2.9",
"version": "3.0",
"icon": "brush.jpg",
"author": "jxxghp,InfinityPacer",
"level": 2,
"history": {
"v3.0": "优化不同站点刷流到相同种子的逻辑,修复数据页滚动闪烁,部分日志优化",
"v2.9": "优化动态删除消息推送优化配置页UI显示及部分配置项支持配置种子分类以及开启自动分类管理取消单独适配站点时区逻辑可通过配置项「pubtime」自行适配",
"v2.8": "优化UI显示以及提升性能",
"v2.7": "动态删除种子规则调整(请注意查阅插件文档),站点独立配置样式优化、日志优化,修复部分配置项无法配置小数的问题,修复部分场景可能导致重复下载的问题",
@@ -547,11 +552,12 @@
"name": "QB远程操作",
"description": "通过定时任务或交互命令远程操作QB暂停/开始/限速等。",
"labels": "下载管理,Qbittorrent",
"version": "1.4",
"version": "1.5",
"icon": "Qbittorrent_A.png",
"author": "DzAvril",
"level": 1,
"history": {
"v1.5": "可选特定路径下的做种不会被暂停",
"v1.4": "可选某些站点不再做种(暂停做种后不会被恢复)"
}
},
@@ -586,10 +592,13 @@
"name": "契约检查",
"description": "定时检查保种契约达成情况。",
"labels": "做种",
"version": "1.1",
"version": "1.2",
"icon": "contract.png",
"author": "DzAvril",
"level": 1
"level": 1,
"history": {
"v1.2": "修复契约检查无数据返回的问题"
}
},
"DownloaderHelper": {
"name": "下载器助手",
@@ -646,10 +655,13 @@
"name": "聚合站点开关",
"description": "统一管理所有与站点相关的开关。",
"labels": "系统设置",
"version": "1.0",
"version": "1.1",
"icon": "world.png",
"author": "hotlcc",
"level": 2
"level": 2,
"history": {
"v1.1": "优化插件配置生效;支持青蛙辅种助手。"
}
},
"TmdbWallpaper": {
"name": "登录壁纸本地化",

View File

@@ -10,6 +10,9 @@ from typing import Any, List, Dict, Tuple, Optional, Union, Set
from urllib.parse import urlparse, parse_qs, unquote
import pytz
from app.helper.sites import SitesHelper
from apscheduler.schedulers.background import BackgroundScheduler
from app import schemas
from app.chain.torrents import TorrentsChain
from app.core.config import settings
@@ -17,7 +20,6 @@ from app.core.context import MediaInfo
from app.core.metainfo import MetaInfo
from app.db.site_oper import SiteOper
from app.db.subscribe_oper import SubscribeOper
from app.helper.sites import SitesHelper
from app.log import logger
from app.modules.qbittorrent import Qbittorrent
from app.modules.transmission import Transmission
@@ -25,7 +27,6 @@ from app.plugins import _PluginBase
from app.schemas import NotificationType, TorrentInfo, MediaType
from app.utils.http import RequestUtils
from app.utils.string import StringUtils
from apscheduler.schedulers.background import BackgroundScheduler
lock = threading.Lock()
@@ -146,9 +147,10 @@ class BrushConfig:
@staticmethod
def get_demo_site_config() -> str:
desc = ("// 以下为配置示例请参考https://github.com/InfinityPacer/MoviePilot-Plugins/blob/main/README.md 进行配置\n"
"// 如与全局保持一致的配置项,请勿在站点配置中配置\n"
"// 注意无关内容需使用 // 注释\n")
desc = (
"// 以下为配置示例请参考https://github.com/InfinityPacer/MoviePilot-Plugins/blob/main/plugins/brushflowlowfreq/README.md 进行配置\n"
"// 如与全局保持一致的配置项,请勿在站点配置中配置\n"
"// 注意无关内容需使用 // 注释\n")
config = """[{
"sitename": "站点1",
"seed_time": 96,
@@ -249,7 +251,7 @@ class BrushFlow(_PluginBase):
# 插件图标
plugin_icon = "brush.jpg"
# 插件版本
plugin_version = "2.9"
plugin_version = "3.0"
# 插件作者
plugin_author = "jxxghp,InfinityPacer"
# 作者主页
@@ -1239,7 +1241,7 @@ class BrushFlow(_PluginBase):
"component": "VSwitch",
"props": {
"model": "dialog_closed",
"label": "设置站点"
"label": "打开站点配置窗口"
}
}
]
@@ -1342,15 +1344,20 @@ class BrushFlow(_PluginBase):
'content': [
{
'component': 'span',
'text': '部分配置项以及细节请参考:'
'text': '注意:详细配置说明以及刷流规则请参考:'
},
{
'component': 'a',
'props': {
'href': 'https://github.com/InfinityPacer/MoviePilot-Plugins/blob/main/README.md',
'href': 'https://github.com/InfinityPacer/MoviePilot-Plugins/blob/main/plugins/brushflowlowfreq/README.md',
'target': '_blank'
},
'text': 'https://github.com/InfinityPacer/MoviePilot-Plugins/blob/main/README.md'
'content': [
{
'component': 'u',
'text': 'README'
}
]
}
]
}
@@ -1449,10 +1456,15 @@ class BrushFlow(_PluginBase):
{
'component': 'a',
'props': {
'href': 'https://github.com/InfinityPacer/MoviePilot-Plugins/blob/main/README.md',
'href': 'https://github.com/InfinityPacer/MoviePilot-Plugins/blob/main/plugins/brushflowlowfreq/README.md',
'target': '_blank'
},
'text': 'https://github.com/InfinityPacer/MoviePilot-Plugins/blob/main/README.md'
'content': [
{
'component': 'u',
'text': 'README'
}
]
}
]
}
@@ -1551,6 +1563,11 @@ class BrushFlow(_PluginBase):
return [
{
'component': 'VRow',
'props': {
'style': {
'overflow': 'hidden',
}
},
'content': [
# 总上传量
{
@@ -2149,6 +2166,12 @@ class BrushFlow(_PluginBase):
torrent_tasks.values()):
return False, "重复种子"
# 不同站点如果遇到相同种子,判断前一个种子是否已经在做种,否则排除处理
if torrent.title:
if any(torrent.site_name != f"{task.get('site_name')}" and torrent.title == f"{task.get('title')}"
and not task.get("seed_time") for task in torrent_tasks.values()):
return False, "其他站点存在尚未下载完成的相同种子"
# 促销条件
if brush_config.freeleech and torrent.downloadvolumefactor != 0:
return False, "非免费种子"
@@ -2286,15 +2309,15 @@ class BrushFlow(_PluginBase):
# 如果配置了动态删除以及删种阈值,则根据动态删种进行分组处理
if brush_config.proxy_delete and brush_config.delete_size_range:
logger.info("已开启动态删种,按系统默认动态删种条件开始检查任务")
proxy_delete_hashs = self.__delete_torrent_for_proxy(torrents=check_torrents,
torrent_tasks=torrent_tasks) or []
need_delete_hashes.extend(proxy_delete_hashs)
proxy_delete_hashes = self.__delete_torrent_for_proxy(torrents=check_torrents,
torrent_tasks=torrent_tasks) or []
need_delete_hashes.extend(proxy_delete_hashes)
# 否则均认为是没有开启动态删种
else:
logger.info("没有开启动态删种,按用户设置删种条件开始检查任务")
not_proxy_delete_hashs = self.__delete_torrent_for_evaluate_conditions(torrents=check_torrents,
torrent_tasks=torrent_tasks) or []
need_delete_hashes.extend(not_proxy_delete_hashs)
not_proxy_delete_hashes = self.__delete_torrent_for_evaluate_conditions(torrents=check_torrents,
torrent_tasks=torrent_tasks) or []
need_delete_hashes.extend(not_proxy_delete_hashes)
if need_delete_hashes:
if downloader.delete_torrents(ids=need_delete_hashes, delete_file=True):
@@ -2493,7 +2516,7 @@ class BrushFlow(_PluginBase):
根据条件删除种子并获取已删除列表
"""
brush_config = self.__get_brush_config()
delete_hashs = []
delete_hashes = []
for torrent in torrents:
torrent_hash = self.__get_hash(torrent)
@@ -2512,8 +2535,8 @@ class BrushFlow(_PluginBase):
torrent_info=torrent_info,
torrent_task=torrent_task)
if should_delete:
delete_hashs.append(torrent_hash)
reason = "触发动态删除," + reason if proxy_delete else reason
delete_hashes.append(torrent_hash)
reason = "触发动态删除阈值" + reason if proxy_delete else reason
self.__send_delete_message(site_name=site_name, torrent_title=torrent_title, torrent_desc=torrent_desc,
reason=reason)
logger.info(f"站点:{site_name}{reason},删除种子:{torrent_title}|{torrent_desc}")
@@ -2521,7 +2544,7 @@ class BrushFlow(_PluginBase):
if brush_config.log_more:
logger.info(f"站点:{site_name}{reason},不删除种子:{torrent_title}|{torrent_desc}")
return delete_hashs
return delete_hashes
def __delete_torrent_for_evaluate_proxy_pre_conditions(self, torrents: List[Any],
torrent_tasks: Dict[str, dict]) -> List:
@@ -2529,7 +2552,7 @@ class BrushFlow(_PluginBase):
根据动态删除前置条件排除H&R种子后删除种子并获取已删除列表
"""
brush_config = self.__get_brush_config()
delete_hashs = []
delete_hashes = []
for torrent in torrents:
torrent_hash = self.__get_hash(torrent)
@@ -2552,7 +2575,7 @@ class BrushFlow(_PluginBase):
should_delete, reason = self.__evaluate_proxy_pre_conditions_for_delete(site_name=site_name,
torrent_info=torrent_info)
if should_delete:
delete_hashs.append(torrent_hash)
delete_hashes.append(torrent_hash)
self.__send_delete_message(site_name=site_name, torrent_title=torrent_title, torrent_desc=torrent_desc,
reason=reason)
logger.info(f"站点:{site_name}{reason},删除种子:{torrent_title}|{torrent_desc}")
@@ -2560,7 +2583,7 @@ class BrushFlow(_PluginBase):
if brush_config.log_more:
logger.info(f"站点:{site_name}{reason},不删除种子:{torrent_title}|{torrent_desc}")
return delete_hashs
return delete_hashes
def __delete_torrent_for_proxy(self, torrents: List[Any], torrent_tasks: Dict[str, dict]) -> List:
"""
@@ -3557,7 +3580,7 @@ class BrushFlow(_PluginBase):
now = datetime.now()
return (now - pubdate).total_seconds() // 60
except Exception as e:
print(str(e))
logger.error(f"发布时间 {pubdate} 获取分钟失败,错误详情: {e}")
return 0
@staticmethod

View File

@@ -39,7 +39,7 @@ class ContractCheck(_PluginBase):
# 插件图标
plugin_icon = "contract.png"
# 插件版本
plugin_version = "1.1"
plugin_version = "1.2"
# 插件作者
plugin_author = "DzAvril"
# 作者主页
@@ -713,7 +713,7 @@ class ContractCheck(_PluginBase):
else:
num_gap = contract_info.num - current_seeding_size[0]
is_satisfied = is_size_satisfied and is_num_satisfied
duration = (datetime.now().date() - contract_info.date).days
duration = (datetime.now() - contract_info.date).days
if duration < contract_info.duration:
duration_gap = contract_info.duration - duration
return is_satisfied, size_gap, num_gap, duration_gap

View File

@@ -69,6 +69,7 @@ class ISiteUserInfo(metaclass=ABCMeta):
"UBits": ["UBits"],
"听听歌": ["TTG", "WiKi", "DoA", "NGB", "ARiN"],
"馒头": ["MTeam", "MTeamTV"],
"朋友": ["FRDS"],
}
# 错误信息

View File

@@ -3,11 +3,12 @@ import os
from typing import Any, List, Dict, Tuple
from app.core.event import eventmanager, Event
from app.db.models.site import Site
from app.core.plugin import PluginManager
from app.db.site_oper import SiteOper
from app.db.systemconfig_oper import SystemConfigOper
from app.log import logger
from app.plugins import _PluginBase
from app.scheduler import Scheduler
from app.schemas.types import SystemConfigKey, EventType
@@ -19,7 +20,7 @@ class MergeSiteSwitch(_PluginBase):
# 插件图标
plugin_icon = "world.png"
# 插件版本
plugin_version = "1.0"
plugin_version = "1.1"
# 插件作者
plugin_author = "hotlcc"
# 作者主页
@@ -46,6 +47,8 @@ class MergeSiteSwitch(_PluginBase):
__plugin_id_iyuu_auto_seed: str = 'IYUUAutoSeed'
# 站点刷流
__plugin_id_brush_flow: str = 'BrushFlow'
# 青蛙辅种助手
__plugin_id_cross_seed: str = 'CrossSeed'
# 配置相关
# 插件缺省配置
@@ -369,6 +372,43 @@ class MergeSiteSwitch(_PluginBase):
}]
}]
})
# 青蛙辅种助手
if self.__plugin_id_cross_seed in installed_plugin_ids:
form_content.append({
'component': 'VRow',
'content': [{
'component': 'VCol',
'props': {
'cols': 12,
'xxl': 9, 'xl': 9, 'lg': 9, 'md': 9, 'sm': 8, 'xs': 12
},
'content': [{
'component': 'VSelect',
'props': {
'model': 'cross_seed_sites',
'label': '插件 / 青蛙辅种助手 / 辅种站点',
'multiple': True,
'chips': True,
'items': site_options,
'hint': '只有选中的站点才会在辅种中使用。'
}
}]
}, {
'component': 'VCol',
'props': {
'cols': 12,
'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 4, 'xs': 12
},
'content': [{
'component': 'VSwitch',
'props': {
'model': 'cross_seed_follow_enable_sites',
'label': '跟随启用的站点',
'hint': '与站点的启用状态保持一致,保存时会立即生效,并在后台监听站点状态变化实时生效。'
}
}]
}]
})
# 提示信息
form_content.append({
'component': 'VRow',
@@ -500,6 +540,12 @@ class MergeSiteSwitch(_PluginBase):
"""
return self.__check_follow_enable_sites(config_key='brush_flow_follow_enable_sites', plugin_id=self.__plugin_id_brush_flow, installed_plugin_ids=installed_plugin_ids)
def __check_cross_seed_follow_enable_sites(self, installed_plugin_ids: List[str] = None) -> bool:
"""
判断青蛙辅种站点的跟随按钮是否打开
"""
return self.__check_follow_enable_sites(config_key='cross_seed_follow_enable_sites', plugin_id=self.__plugin_id_cross_seed, installed_plugin_ids=installed_plugin_ids)
def __check_any_follow_enable_sites(self) -> bool:
"""
判断是否开启任意跟随按钮
@@ -564,6 +610,10 @@ class MergeSiteSwitch(_PluginBase):
config.update({
'brush_flow_sites': self.__get_brush_flow_site_ids(),
})
if self.__plugin_id_cross_seed in installed_plugin_ids:
config.update({
'cross_seed_sites': self.__get_cross_seed_site_ids(),
})
self.update_config(config=config)
return config
@@ -588,6 +638,8 @@ class MergeSiteSwitch(_PluginBase):
config.update({"iyuu_seed_sites": enable_sites.copy()})
if config.get('brush_flow_follow_enable_sites'):
config.update({"brush_flow_sites": enable_sites.copy()})
if config.get('cross_seed_follow_enable_sites'):
config.update({"cross_seed_sites": enable_sites.copy()})
return config
def __pre_config(self, config: dict) -> dict:
@@ -621,6 +673,8 @@ class MergeSiteSwitch(_PluginBase):
self.__set_iyuu_seed_site_ids(config.get('iyuu_seed_sites'))
if self.__plugin_id_brush_flow in installed_plugin_ids:
self.__set_brush_flow_site_ids(config.get('brush_flow_sites'))
if self.__plugin_id_cross_seed in installed_plugin_ids:
self.__set_cross_seed_site_ids(config.get('cross_seed_sites'))
return config
def __get_enable_site_ids(self) -> List[int]:
@@ -687,6 +741,19 @@ class MergeSiteSwitch(_PluginBase):
return None
return config.get(config_key)
def __reload_plugin_config(self, plugin_id: str, config: dict = None):
"""
重载插件配置
"""
if not plugin_id:
return
if not config:
config = self.get_config(plugin_id)
# 重新生效插件
PluginManager().init_plugin(plugin_id, config)
# 注册插件服务
Scheduler().update_plugin_job(plugin_id)
def __set_plugin_config_value(self, plugin_id: str, config_key: str, config_value: Any) -> Any:
"""
设置插件配置值
@@ -698,6 +765,7 @@ class MergeSiteSwitch(_PluginBase):
config = {}
config.update({config_key: config_value})
self.update_config(plugin_id=plugin_id, config=config)
self.__reload_plugin_config(plugin_id=plugin_id, config=config)
def __get_signin_site_ids(self) -> List[int]:
"""
@@ -769,6 +837,20 @@ class MergeSiteSwitch(_PluginBase):
self.__set_plugin_config_value(self.__plugin_id_brush_flow, 'brushsites', site_ids)
logger.info("刷流站点配置完成")
def __get_cross_seed_site_ids(self) -> List[int]:
"""
获取青蛙辅种站点IDs
"""
sites = self.__get_plugin_config_value(self.__plugin_id_cross_seed, 'sites')
return sites if sites else []
def __set_cross_seed_site_ids(self, site_ids: List[int]):
"""
设置青蛙辅种站点IDs
"""
self.__set_plugin_config_value(self.__plugin_id_cross_seed, 'sites', site_ids)
logger.info("青蛙辅种站点配置完成")
def __update_search_site_ids_by_site(self, site_id: int, site_status: bool):
if site_id == None:
return
@@ -846,6 +928,17 @@ class MergeSiteSwitch(_PluginBase):
site_ids.remove(site_id)
self.__set_brush_flow_site_ids(site_ids=site_ids)
def __update_cross_seed_site_ids_by_site(self, site_id: int, site_status: bool):
if site_id == None:
return
site_ids = self.__get_cross_seed_site_ids() or []
if site_id not in site_ids and site_status:
site_ids.append(site_id)
self.__set_cross_seed_site_ids(site_ids=site_ids)
elif site_id in site_ids and not site_status:
site_ids.remove(site_id)
self.__set_cross_seed_site_ids(site_ids=site_ids)
def __update_site_ids_for_site_event(self, site_id: int, site_status: bool):
"""
针对站点事件更新各项配置
@@ -868,6 +961,8 @@ class MergeSiteSwitch(_PluginBase):
self.__update_iyuu_seed_site_ids_by_site(site_id=site_id, site_status=site_status)
if self.__check_brush_flow_follow_enable_sites(installed_plugin_ids=installed_plugin_ids):
self.__update_brush_flow_site_ids_by_site(site_id=site_id, site_status=site_status)
if self.__check_cross_seed_follow_enable_sites(installed_plugin_ids=installed_plugin_ids):
self.__update_cross_seed_site_ids_by_site(site_id=site_id, site_status=site_status)
@eventmanager.register(EventType.SiteUpdated)
def listen_site_updated_event(self, event: Event = None):

View File

@@ -27,7 +27,7 @@ class QbCommand(_PluginBase):
# 插件图标
plugin_icon = "Qbittorrent_A.png"
# 插件版本
plugin_version = "1.4"
plugin_version = "1.5"
# 插件作者
plugin_author = "DzAvril"
# 作者主页
@@ -60,7 +60,7 @@ class QbCommand(_PluginBase):
_op_sites = []
_multi_level_root_domain = ["edu.cn", "com.cn", "net.cn", "org.cn"]
_scheduler = None
_exclude_dirs = ""
def init_plugin(self, config: dict = None):
self._sites = SitesHelper()
self._siteoper = SiteOper()
@@ -87,6 +87,7 @@ class QbCommand(_PluginBase):
all_sites = [site for site in self._sites.get_indexers() if not site.get("public")] + self.__custom_sites()
# 过滤掉没有选中的站点
self._op_sites = [site for site in all_sites if site.get("id") in self._op_site_ids]
self._exclude_dirs = config.get("exclude_dirs") or ""
if self._only_pause_once or self._only_resume_once:
if self._only_pause_once and self._only_resume_once:
@@ -121,6 +122,7 @@ class QbCommand(_PluginBase):
"pause_cron": self._pause_cron,
"resume_cron": self._resume_cron,
"op_site_ids": self._op_site_ids,
"exclude_dirs": self._exclude_dirs,
}
)
@@ -430,14 +432,6 @@ class QbCommand(_PluginBase):
hash_downloading, hash_uploading, hash_paused, hash_checking, hash_error = (
self.get_torrents_status(all_torrents)
)
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"
@@ -461,6 +455,19 @@ class QbCommand(_PluginBase):
f"错误数量: {len(hash_error)}\n"
f"暂停操作中请稍等...\n",
)
pause_torrents = self.filter_pause_torrents(all_torrents)
hash_downloading, hash_uploading, hash_paused, hash_checking, hash_error = (
self.get_torrents_status(pause_torrents)
)
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
if len(to_be_paused) > 0:
if self._qb.stop_torrents(ids=to_be_paused):
logger.info(f"暂停了{len(to_be_paused)}个种子")
@@ -501,6 +508,22 @@ class QbCommand(_PluginBase):
f"错误数量: {len(hash_error)}\n",
)
def __is_excluded(self, file_path) -> bool:
"""
是否排除目录
"""
for exclude_dir in self._exclude_dirs.split("\n"):
if exclude_dir and exclude_dir in str(file_path):
return True
return False
def filter_pause_torrents(self, all_torrents):
torrents = []
for torrent in all_torrents:
if self.__is_excluded(torrent.get("content_path")):
continue
torrents.append(torrent)
return torrents
@eventmanager.register(EventType.PluginAction)
def handle_resume_torrent(self, event: Event):
if not self._enabled:
@@ -516,7 +539,6 @@ class QbCommand(_PluginBase):
return
all_torrents = self.get_all_torrents()
all_torrents = self.filter_torrents(all_torrents)
hash_downloading, hash_uploading, hash_paused, hash_checking, hash_error = (
self.get_torrents_status(all_torrents)
)
@@ -542,6 +564,11 @@ class QbCommand(_PluginBase):
f"错误数量: {len(hash_error)}\n"
f"开始操作中请稍等...\n",
)
resume_torrents = self.filter_resume_torrents(all_torrents)
hash_downloading, hash_uploading, hash_paused, hash_checking, hash_error = (
self.get_torrents_status(resume_torrents)
)
if not self._qb.start_torrents(ids=hash_paused):
logger.error(f"开始种子失败")
if self._notify:
@@ -579,7 +606,7 @@ class QbCommand(_PluginBase):
f"错误数量: {len(hash_error)}\n",
)
def filter_torrents(self, all_torrents):
def filter_resume_torrents(self, all_torrents):
"""
过滤掉不参与保种的种子
"""
@@ -644,14 +671,13 @@ class QbCommand(_PluginBase):
if self._notify:
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【QB开始任务启动",
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)
@@ -747,7 +773,7 @@ class QbCommand(_PluginBase):
elif flag is None and self._enabled and self._enable_upload_limit:
flag = self.set_upload_limit(upload_limit)
if flag:
if flag == True:
logger.info(f"设置QB限速成功")
if self._notify:
if upload_limit == 0:
@@ -763,7 +789,7 @@ class QbCommand(_PluginBase):
title=f"【QB远程操作】",
text=text,
)
elif not flag:
elif flag == False:
logger.error(f"QB设置限速失败")
if self._notify:
self.post_message(
@@ -1054,6 +1080,26 @@ class QbCommand(_PluginBase):
}
],
},
{
"component": "VRow",
"content": [
{
"component": "VCol",
"props": {"cols": 12},
"content": [
{
"component": "VTextarea",
"props": {
"model": "exclude_dirs",
"label": "不暂停保种目录",
"rows": 5,
"placeholder": "该目录下的做种不会暂停,一行一个目录",
},
}
],
}
],
},
{
"component": "VRow",
"content": [

View File

@@ -43,7 +43,7 @@ class SiteStatistic(_PluginBase):
# 插件图标
plugin_icon = "statistic.png"
# 插件版本
plugin_version = "3.0"
plugin_version = "3.1"
# 插件作者
plugin_author = "lightolly"
# 作者主页

View File

@@ -26,6 +26,7 @@ class SiteSchema(Enum):
NexusProject = "NexusProject"
NexusRabbit = "NexusRabbit"
NexusHhanclub = "NexusHhanclub"
NexusAudiences = "NexusAudiences"
SmallHorse = "Small Horse"
Unit3d = "Unit3d"
TorrentLeech = "TorrentLeech"

View File

@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from urllib.parse import urljoin
from app.plugins.sitestatistic.siteuserinfo import SITE_BASE_ORDER, SiteSchema
from app.plugins.sitestatistic.siteuserinfo.nexus_php import NexusPhpSiteUserInfo
class NexusAudiencesSiteUserInfo(NexusPhpSiteUserInfo):
schema = SiteSchema.NexusAudiences
order = SITE_BASE_ORDER + 5
@classmethod
def match(cls, html_text: str) -> bool:
return 'audiences.me' in html_text
def _parse_seeding_pages(self):
self._torrent_seeding_headers = {"Referer": urljoin(self._base_url, self._user_detail_page)}
super()._parse_seeding_pages()

View File

@@ -5,8 +5,8 @@ import threading
import time
import traceback
from pathlib import Path
from time import sleep
from typing import List, Tuple, Dict, Any, Optional
import pytz
import qbittorrentapi
from apscheduler.schedulers.background import BackgroundScheduler
@@ -14,12 +14,13 @@ from apscheduler.triggers.cron import CronTrigger
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
from watchdog.observers.polling import PollingObserver
from app import schemas
from app.chain.tmdb import TmdbChain
from app.chain.transfer import TransferChain
from app.core.config import settings
from app.core.context import MediaInfo
from app.core.event import eventmanager, Event
from app.core.metainfo import MetaInfoPath
from app.db.downloadhistory_oper import DownloadHistoryOper
from app.db.transferhistory_oper import TransferHistoryOper
from app.log import logger
@@ -76,7 +77,7 @@ class VCBAnimeMonitor(_PluginBase):
# 插件图标
plugin_icon = "vcbmonitor.png"
# 插件版本
plugin_version = "1.7"
plugin_version = "1.7.1"
# 插件作者
plugin_author = "pixel@qingwa"
# 作者主页
@@ -155,15 +156,20 @@ class VCBAnimeMonitor(_PluginBase):
self._scheduler.add_job(self.send_msg, trigger='interval', seconds=15)
self.qb = Qbittorrent()
# 读取目录配置
monitor_dirs = self._monitor_dirs.split("\n")
if not monitor_dirs:
return
# 启用种子目录监控
if self._torrents_path is not None and Path(self._torrents_path).exists() and self._enabled:
# 只取第一个目录作为新的保存
first_path = monitor_dirs[0]
if SystemUtils.is_windows():
self.new_save_path = self._monitor_dirs.split(':')[0] + ":" + self._monitor_dirs.split(':')[1]
self.new_save_path = first_path.split(':')[0] + ":" + first_path.split(':')[1]
else:
self.new_save_path = self._monitor_dirs.split(':')[0]
print(self.new_save_path)
self.new_save_path = first_path.split(':')[0]
# print(self.new_save_path)
try:
observer = Observer()
self._observer.append(observer)
@@ -177,10 +183,6 @@ class VCBAnimeMonitor(_PluginBase):
else:
logger.info("种子目录为空不转移qb中正在下载的VCB-Studio文件")
# 读取目录配置
monitor_dirs = self._monitor_dirs.split("\n")
if not monitor_dirs:
return
for mon_path in monitor_dirs:
# 格式源目录:目的目录
if not mon_path:
@@ -594,6 +596,7 @@ class VCBAnimeMonitor(_PluginBase):
def torrent_event(self, event, mon_path: str, text: str):
"""
处理种子文件
:param mon_path: 种子目录
"""
evc_path = Path(event.src_path)
if not event.is_directory and (evc_path.suffix == ".torrent" or str(evc_path).split('.')[1] == "torrent"):
@@ -1009,8 +1012,7 @@ class VCBAnimeMonitor(_PluginBase):
'props': {
'type': 'info',
'variant': 'tonal',
'text': '核心用法与目录同步插件相同不同点在于只识别处理VCB-Studio资源,'
'避免与目录同步插件的监控目录相同(否则会同时进行识别)'
'text': '核心用法与目录同步插件相同不同点在于只识别处理VCB-Studio资源,\n'
'不处理SPs目录下的文件,OVA/OAD集数根据入库顺序累加命名,不保证与TMDB集数匹配'
}
}

View File

@@ -2,9 +2,6 @@ import concurrent
import re
from pathlib import Path
from typing import List
import roman
from app.chain.media import MediaChain
from app.chain.tmdb import TmdbChain
from app.core.metainfo import MetaInfoPath
@@ -12,6 +9,26 @@ from app.log import logger
from app.schemas import MediaType
def roman_to_int(s) -> int:
"""
:param s: 罗马数字字符串
罗马数字转整数
"""
roman_dict = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
total = 0
prev_value = 0
for char in reversed(s): # 反向遍历罗马数字字符串
current_value = roman_dict[char]
if current_value >= prev_value:
total += current_value # 如果当前值大于等于前一个值,加上当前值
else:
total -= current_value # 如果当前值小于前一个值,减去当前值
prev_value = current_value
return total
class ReMeta:
# 解析之后的标题:
title: str = None
@@ -41,7 +58,7 @@ class ReMeta:
]
_ova_patterns = [re.compile(r"\[.*?(OVA|OAD).*?]"),
re.compile(r"\[\d+\.5]"),
re.compile(r"\[00]")]
re.compile(r"\[00\]")]
final_season_patterns = [re.compile('final season', re.IGNORECASE),
re.compile('The Final', re.IGNORECASE),
@@ -57,13 +74,14 @@ class ReMeta:
def handel_file(self, file_path: Path):
meta = MetaInfoPath(file_path)
self.title = meta.title
self.title = Path(self.title).stem.strip()
if 'VCB-Studio' not in meta.title:
logger.warn("不属于VCB的作品不处理")
return None
if meta.title.count("[") != 4 and meta.title.count("]") != 4:
# 可能是电影,电影只有三组[],因此去除所有[]后只剩下电影名
logger.warn("不符合VCB-Studio的剧集命名规范跳过剧集模块处理交给默认处理逻辑")
meta.title = re.sub(r'\[.*?]', '', meta.title).strip()
meta.title = re.sub(r'\[.*?\]', '', meta.title).strip()
meta.en_name = meta.title
return meta
split_title: List[str] | None = self.split_season_ep(self.title)
@@ -84,13 +102,12 @@ class ReMeta:
return meta
# 分离季度部分和集数部分
@staticmethod
def split_season_ep(pre_title: str):
def split_season_ep(self, pre_title: str):
split_ep = re.findall(r"(\[.*?])", pre_title)[1]
if not split_ep:
logger.warn("未识别出集数位置信息,结束识别!")
return None
split_title = re.sub(r"\[.*?]", "", pre_title).strip()
split_title = re.sub(r"\[.*?\]", "", pre_title).strip()
logger.info(f"分离出包含季度的部分:{split_title} \n 分离出包含集数的部分: {split_ep}")
return [split_title, split_ep]
@@ -126,7 +143,7 @@ class ReMeta:
match = pattern.search(pre_title)
if match:
if type(group) == str:
title_season["season"] = int(roman.fromRoman(match.group(int(group))))
title_season["season"] = roman_to_int(match.group(int(group)))
title_season["title"] = re.sub(pattern, "", pre_title).strip()
else:
title_season["season"] = int(match.group(group))
@@ -142,8 +159,7 @@ class ReMeta:
return title_season
# 处理存在“Final”字样命名的季度
@staticmethod
def handle_final_season(title: str) -> int | None:
def handle_final_season(self, title: str) -> int | None:
medias = MediaChain().search(title=title)[1]
if not medias:
logger.warn("没有找到对应的媒体信息!")

View File

@@ -1 +0,0 @@
roman~=4.1