Merge branch 'feature_dev' of https://github.com/InfinityPacer/MoviePilot-Plugins into feature_dev
BIN
icons/Actual_A.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
icons/Apache_webdav_A.png
Normal file
|
After Width: | Height: | Size: 287 KiB |
BIN
icons/Chandao_A.png
Normal file
|
After Width: | Height: | Size: 128 KiB |
BIN
icons/Chandao_B.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
icons/Cloudflare_B.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
icons/Dell_A.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
icons/Docker.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
icons/FileBrowser.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
icons/Flood_A.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
icons/FreshRSS.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
icons/Guacamole_A.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
icons/Homebridge_A.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
icons/Homer_A.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
icons/Ipw.cn_A.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
icons/Itdog.cn_A.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
icons/Macos_A.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
icons/Macos_Bigsur.png
Normal file
|
After Width: | Height: | Size: 456 KiB |
BIN
icons/Macos_Catalina.png
Normal file
|
After Width: | Height: | Size: 276 KiB |
BIN
icons/Macos_EI_Capitan.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
icons/Macos_High_Sierra.png
Normal file
|
After Width: | Height: | Size: 429 KiB |
BIN
icons/Macos_Mojave.png
Normal file
|
After Width: | Height: | Size: 376 KiB |
BIN
icons/Macos_Monterey.png
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
icons/Macos_Sierra.png
Normal file
|
After Width: | Height: | Size: 460 KiB |
BIN
icons/Ombi.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
icons/Omnivore_A.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
icons/PVE_B.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
icons/Photoprism_A.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
icons/Photoprism_C.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
icons/Photoprism_D.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
icons/Photoprism_E.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
icons/Photoprism_F.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
icons/Photostructure_A.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
icons/Qbittorrent_B.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
icons/Readarr.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
icons/Rocky_A.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
icons/Shiori_A.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
icons/UnlockMusic.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
icons/Verysync.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
icons/Youtube-dl_C.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
27
package.json
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"AutoSignIn": {
|
||||
"name": "站点自动签到",
|
||||
"description": "自动模拟登录站点、签到。",
|
||||
"version": "1.9",
|
||||
"description": "自动模拟登录、签到站点。",
|
||||
"version": "2.0",
|
||||
"icon": "signin.png",
|
||||
"author": "thsrite",
|
||||
"level": 2,
|
||||
"history": {
|
||||
"v2.0": "站点签到时更新站点使用统计信息,需要主程序升级至v1.8.3+版本",
|
||||
"v1.9": "支持馒头新架构自动签到"
|
||||
}
|
||||
},
|
||||
@@ -21,11 +22,13 @@
|
||||
"SiteStatistic": {
|
||||
"name": "站点数据统计",
|
||||
"description": "自动统计和展示站点数据。",
|
||||
"version": "2.6",
|
||||
"version": "2.8",
|
||||
"icon": "statistic.png",
|
||||
"author": "lightolly",
|
||||
"level": 2,
|
||||
"history": {
|
||||
"v2.8": "修复馒头未读消息统计",
|
||||
"v2.7": "修复憨憨种子信息只统计第一页的问题,增加移除失效统计选项",
|
||||
"v2.6": "支持馒头新架构数据统计"
|
||||
}
|
||||
},
|
||||
@@ -198,12 +201,13 @@
|
||||
"CrossSeed": {
|
||||
"name": "青蛙辅种助手",
|
||||
"description": "参考ReseedPuppy和IYUU辅种插件实现自动辅种,支持站点:青蛙、AGSVPT、麒麟、UBits、聆音、憨憨等。",
|
||||
"version": "2.2",
|
||||
"version": "2.3",
|
||||
"icon": "qingwa.png",
|
||||
"author": "233@qingwa",
|
||||
"level": 2,
|
||||
"history": {
|
||||
"v2.2": "站点停用后会同步暂停对该站点的辅种"
|
||||
"v2.2": "站点停用后会同步暂停对该站点的辅种",
|
||||
"v2.3": "站点辅种支持代理"
|
||||
}
|
||||
},
|
||||
"VCBAnimeMonitor": {
|
||||
@@ -387,11 +391,13 @@
|
||||
"RemoveLink": {
|
||||
"name": "清理硬链接",
|
||||
"description": "监控目录内文件被删除时,同步删除监控目录内所有和它硬链接的文件",
|
||||
"version": "1.7",
|
||||
"version": "1.9",
|
||||
"icon": "Ombi_A.png",
|
||||
"author": "DzAvril",
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v1.9": "增加清理刮削文件功能(beta)",
|
||||
"v1.8": "增加清理空目录功能(beta)",
|
||||
"v1.7": "修复因未监测重命名事件导致的清理硬链接失败的问题",
|
||||
"v1.6": "提升插件性能"
|
||||
}
|
||||
@@ -506,10 +512,13 @@
|
||||
"IyuuAuth": {
|
||||
"name": "IYUU站点绑定",
|
||||
"description": "为IYUU账号绑定认证站点,以便用于用户认证和辅种。",
|
||||
"version": "1.0",
|
||||
"version": "1.1",
|
||||
"icon": "Iyuu_A.png",
|
||||
"author": "jxxghp",
|
||||
"level": 1
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v1.1": "修复IYUU站点绑定失败问题"
|
||||
}
|
||||
},
|
||||
"NtfyMsg": {
|
||||
"name": "ntfy消息推送",
|
||||
@@ -522,7 +531,7 @@
|
||||
"PluginAutoUpgrade": {
|
||||
"name": "插件自动升级",
|
||||
"description": "定时检测、升级插件。",
|
||||
"version": "1.0",
|
||||
"version": "1.4",
|
||||
"icon": "PluginAutoUpgrade.png",
|
||||
"author": "hotlcc",
|
||||
"level": 1
|
||||
|
||||
@@ -16,6 +16,7 @@ from app.chain.site import SiteChain
|
||||
from app.core.config import settings
|
||||
from app.core.event import EventManager, eventmanager, Event
|
||||
from app.db.site_oper import SiteOper
|
||||
from app.db.sitestatistic_oper import SiteStatisticOper
|
||||
from app.helper.browser import PlaywrightHelper
|
||||
from app.helper.cloudflare import under_challenge
|
||||
from app.helper.module import ModuleHelper
|
||||
@@ -33,11 +34,11 @@ class AutoSignIn(_PluginBase):
|
||||
# 插件名称
|
||||
plugin_name = "站点自动签到"
|
||||
# 插件描述
|
||||
plugin_desc = "自动模拟登录站点、签到。"
|
||||
plugin_desc = "自动模拟登录、签到站点。"
|
||||
# 插件图标
|
||||
plugin_icon = "signin.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.9"
|
||||
plugin_version = "2.0"
|
||||
# 插件作者
|
||||
plugin_author = "thsrite"
|
||||
# 作者主页
|
||||
@@ -53,6 +54,7 @@ class AutoSignIn(_PluginBase):
|
||||
sites: SitesHelper = None
|
||||
siteoper: SiteOper = None
|
||||
sitechain: SiteChain = None
|
||||
sitestatistic: SiteStatisticOper = None
|
||||
# 事件管理器
|
||||
event: EventManager = None
|
||||
# 定时器
|
||||
@@ -87,6 +89,7 @@ class AutoSignIn(_PluginBase):
|
||||
self.siteoper = SiteOper()
|
||||
self.event = EventManager()
|
||||
self.sitechain = SiteChain()
|
||||
self.sitestatistic = SiteStatisticOper()
|
||||
|
||||
# 停止现有任务
|
||||
self.stop_service()
|
||||
@@ -871,26 +874,34 @@ class AutoSignIn(_PluginBase):
|
||||
签到一个站点
|
||||
"""
|
||||
site_module = self.__build_class(site_info.get("url"))
|
||||
# 开始记时
|
||||
start_time = datetime.now()
|
||||
if site_module and hasattr(site_module, "signin"):
|
||||
try:
|
||||
_, msg = site_module().signin(site_info)
|
||||
# 特殊站点直接返回签到信息,防止仿真签到、模拟登录有歧义
|
||||
return site_info.get("name"), msg or ""
|
||||
state, message = site_module().signin(site_info)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
return site_info.get("name"), f"签到失败:{str(e)}"
|
||||
state, message = False, f"签到失败:{str(e)}"
|
||||
else:
|
||||
return site_info.get("name"), self.__signin_base(site_info)
|
||||
state, message = self.__signin_base(site_info)
|
||||
# 统计
|
||||
seconds = (datetime.now() - start_time).seconds
|
||||
domain = StringUtils.get_url_domain(site_info.get('url'))
|
||||
if state:
|
||||
self.sitestatistic.success(domain=domain, seconds=seconds)
|
||||
else:
|
||||
self.sitestatistic.fail(domain)
|
||||
return site_info.get("name"), message
|
||||
|
||||
@staticmethod
|
||||
def __signin_base(site_info: CommentedMap) -> str:
|
||||
def __signin_base(site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
"""
|
||||
通用签到处理
|
||||
:param site_info: 站点信息
|
||||
:return: 签到结果信息
|
||||
"""
|
||||
if not site_info:
|
||||
return ""
|
||||
return False, ""
|
||||
site = site_info.get("name")
|
||||
site_url = site_info.get("url")
|
||||
site_cookie = site_info.get("cookie")
|
||||
@@ -900,7 +911,7 @@ class AutoSignIn(_PluginBase):
|
||||
proxy_server = settings.PROXY_SERVER if site_info.get("proxy") else None
|
||||
if not site_url or not site_cookie:
|
||||
logger.warn(f"未配置 {site} 的站点地址或Cookie,无法签到")
|
||||
return ""
|
||||
return False, ""
|
||||
# 模拟登录
|
||||
try:
|
||||
# 访问链接
|
||||
@@ -916,14 +927,14 @@ class AutoSignIn(_PluginBase):
|
||||
proxies=proxy_server)
|
||||
if not SiteUtils.is_logged_in(page_source):
|
||||
if under_challenge(page_source):
|
||||
return f"无法通过Cloudflare!"
|
||||
return f"仿真登录失败,Cookie已失效!"
|
||||
return False, f"无法通过Cloudflare!"
|
||||
return False, f"仿真登录失败,Cookie已失效!"
|
||||
else:
|
||||
# 判断是否已签到
|
||||
if re.search(r'已签|签到已得', page_source, re.IGNORECASE) \
|
||||
or SiteUtils.is_checkin(page_source):
|
||||
return f"签到成功"
|
||||
return "仿真签到成功"
|
||||
return True, f"签到成功"
|
||||
return True, "仿真签到成功"
|
||||
else:
|
||||
res = RequestUtils(cookies=site_cookie,
|
||||
ua=ua,
|
||||
@@ -945,20 +956,20 @@ class AutoSignIn(_PluginBase):
|
||||
else:
|
||||
msg = f"状态码:{res.status_code}"
|
||||
logger.warn(f"{site} 签到失败,{msg}")
|
||||
return f"签到失败,{msg}!"
|
||||
return False, f"签到失败,{msg}!"
|
||||
else:
|
||||
logger.info(f"{site} 签到成功")
|
||||
return f"签到成功"
|
||||
return True, f"签到成功"
|
||||
elif res is not None:
|
||||
logger.warn(f"{site} 签到失败,状态码:{res.status_code}")
|
||||
return f"签到失败,状态码:{res.status_code}!"
|
||||
return False, f"签到失败,状态码:{res.status_code}!"
|
||||
else:
|
||||
logger.warn(f"{site} 签到失败,无法打开网站")
|
||||
return f"签到失败,无法打开网站!"
|
||||
return False, f"签到失败,无法打开网站!"
|
||||
except Exception as e:
|
||||
logger.warn("%s 签到失败:%s" % (site, str(e)))
|
||||
traceback.print_exc()
|
||||
return f"签到失败:{str(e)}!"
|
||||
return False, f"签到失败:{str(e)}!"
|
||||
|
||||
def login_site(self, site_info: CommentedMap) -> Tuple[str, str]:
|
||||
"""
|
||||
|
||||
@@ -154,7 +154,11 @@ class CrossSeedHelper(object):
|
||||
remote_torrent_infos = []
|
||||
try:
|
||||
response = requests.post(
|
||||
site.get_api_url(), headers=headers, json=data, timeout=10
|
||||
site.get_api_url(),
|
||||
headers=headers,
|
||||
json=data,
|
||||
timeout=10,
|
||||
proxies=settings.PROXY if site.proxy else None,
|
||||
)
|
||||
response.raise_for_status()
|
||||
rsp_body = response.json()
|
||||
@@ -177,7 +181,7 @@ class CrossSeed(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "qingwa.png"
|
||||
# 插件版本
|
||||
plugin_version = "2.2"
|
||||
plugin_version = "2.3"
|
||||
# 插件作者
|
||||
plugin_author = "233@qingwa"
|
||||
# 作者主页
|
||||
|
||||
@@ -14,7 +14,7 @@ class IyuuAuth(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "Iyuu_A.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.0"
|
||||
plugin_version = "1.1"
|
||||
# 插件作者
|
||||
plugin_author = "jxxghp"
|
||||
# 作者主页
|
||||
@@ -85,7 +85,7 @@ class IyuuAuth(_PluginBase):
|
||||
for item in self.iyuu.get_auth_sites() or []:
|
||||
SiteOptions.append({
|
||||
"title": item.get("site"),
|
||||
"value": item.get("id")
|
||||
"value": item.get("site")
|
||||
})
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -40,7 +40,7 @@ class FileMonitorHandler(FileSystemEventHandler):
|
||||
# 新增文件记录
|
||||
with state_lock:
|
||||
self.sync.state_set[str(file_path)] = file_path.stat().st_ino
|
||||
|
||||
|
||||
def on_moved(self, event):
|
||||
if event.is_directory:
|
||||
return
|
||||
@@ -106,7 +106,7 @@ class RemoveLink(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "Ombi_A.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.7"
|
||||
plugin_version = "1.9"
|
||||
# 插件作者
|
||||
plugin_author = "DzAvril"
|
||||
# 作者主页
|
||||
@@ -124,6 +124,7 @@ class RemoveLink(_PluginBase):
|
||||
exclude_keywords = ""
|
||||
_enabled = False
|
||||
_notify = False
|
||||
_delete_scrap_infos = False
|
||||
_observer = []
|
||||
# 监控目录的文件列表
|
||||
state_set: Dict[str, int] = {}
|
||||
@@ -136,6 +137,7 @@ class RemoveLink(_PluginBase):
|
||||
self.monitor_dirs = config.get("monitor_dirs")
|
||||
self.exclude_dirs = config.get("exclude_dirs") or ""
|
||||
self.exclude_keywords = config.get("exclude_keywords") or ""
|
||||
self._delete_scrap_infos = config.get("delete_scrap_infos")
|
||||
|
||||
# 停止现有任务
|
||||
self.stop_service()
|
||||
@@ -224,6 +226,19 @@ class RemoveLink(_PluginBase):
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"component": "VCol",
|
||||
"props": {"cols": 12, "md": 4},
|
||||
"content": [
|
||||
{
|
||||
"component": "VSwitch",
|
||||
"props": {
|
||||
"model": "delete_scrap_infos",
|
||||
"label": "清理刮削文件(beta)",
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -289,32 +304,47 @@ class RemoveLink(_PluginBase):
|
||||
],
|
||||
},
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
"component": "VRow",
|
||||
"content": [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
"component": "VCol",
|
||||
"props": {
|
||||
"cols": 12,
|
||||
},
|
||||
'content': [
|
||||
"content": [
|
||||
{
|
||||
'component': 'VAlert',
|
||||
'props': {
|
||||
'type': 'info',
|
||||
'variant': 'tonal',
|
||||
'text': '监控目录如有多个需换行,源目录和硬链接目录都需要添加到监控目录中;如需实现删除硬链接时不删除源文件,可把源文件目录配置到不删除目录中。'
|
||||
}
|
||||
"component": "VAlert",
|
||||
"props": {
|
||||
"type": "info",
|
||||
"variant": "tonal",
|
||||
"text": "监控目录如有多个需换行,源目录和硬链接目录都需要添加到监控目录中;如需实现删除硬链接时不删除源文件,可把源文件目录配置到不删除目录中。",
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"component": "VCol",
|
||||
"props": {
|
||||
"cols": 12,
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"component": "VAlert",
|
||||
"props": {
|
||||
"type": "info",
|
||||
"variant": "tonal",
|
||||
"text": "清理刮削文件为测试功能,请谨慎开启。",
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
], {
|
||||
"enabled": False,
|
||||
"notify": False,
|
||||
"onlyonce": False,
|
||||
"monitor_dirs": "",
|
||||
"exclude_keywords": "",
|
||||
}
|
||||
@@ -345,12 +375,91 @@ class RemoveLink(_PluginBase):
|
||||
return True
|
||||
return False
|
||||
|
||||
def scrape_files_left(self, path):
|
||||
"""
|
||||
检查path目录是否只包含刮削文件
|
||||
"""
|
||||
# 检查path下是否有目录
|
||||
for dir_path in os.listdir(path):
|
||||
if os.path.isdir(os.path.join(path, dir_path)):
|
||||
return False
|
||||
|
||||
# 检查path下是否有非刮削文件
|
||||
for file in path.iterdir():
|
||||
if not file.suffix.lower() in [
|
||||
".jpg",
|
||||
".nfo",
|
||||
]:
|
||||
return False
|
||||
return True
|
||||
|
||||
def delete_scrap_infos(self, path):
|
||||
"""
|
||||
清理path相关的刮削文件
|
||||
"""
|
||||
if not self._delete_scrap_infos:
|
||||
return
|
||||
# 文件所在目录已被删除则退出
|
||||
if not os.path.exists(path.parent):
|
||||
return
|
||||
logger.info(f"清理刮削文件: {path}")
|
||||
if not path.suffix.lower() in [
|
||||
".jpg",
|
||||
".nfo",
|
||||
]:
|
||||
# 清理与path相关的刮削文件
|
||||
name_prefix = path.stem
|
||||
for file in path.parent.iterdir():
|
||||
if file.name.startswith(name_prefix):
|
||||
file.unlink()
|
||||
logger.info(f'删除刮削文件:{file}')
|
||||
# 清理空目录
|
||||
self.delete_empty_folders(path)
|
||||
def delete_empty_folders(self, path):
|
||||
"""
|
||||
从指定路径开始,逐级向上层目录检测并删除空目录,直到遇到非空目录或到达指定监控目录为止
|
||||
"""
|
||||
logger.info(f"清理空目录: {path}")
|
||||
while True:
|
||||
parent_path = path.parent
|
||||
if self.__is_excluded(parent_path):
|
||||
break
|
||||
# parent_path如已被删除则退出检查
|
||||
if not os.path.exists(parent_path):
|
||||
break
|
||||
# 如果当前路径等于监控目录之一,停止向上检查
|
||||
if parent_path in self.monitor_dirs.split("\n"):
|
||||
break
|
||||
|
||||
# 若目录下只剩刮削文件,则清空文件夹
|
||||
if self.scrape_files_left(parent_path):
|
||||
# 清除目录下所有文件
|
||||
for file in parent_path.iterdir():
|
||||
file.unlink()
|
||||
logger.info(f'删除刮削文件:{file}')
|
||||
|
||||
if not os.listdir(parent_path):
|
||||
os.rmdir(parent_path)
|
||||
logger.info(f"清理空目录:{parent_path}")
|
||||
if self._notify:
|
||||
self.post_message(
|
||||
mtype=NotificationType.SiteMessage,
|
||||
title=f"【清理硬链接】",
|
||||
text=f"清理空文件夹:[{parent_path}]\n",
|
||||
)
|
||||
else:
|
||||
break
|
||||
# 更新路径为父目录,准备下一轮检查
|
||||
path = parent_path
|
||||
|
||||
def handle_deleted(self, file_path: Path):
|
||||
"""
|
||||
处理删除事件
|
||||
"""
|
||||
# 删除的文件对应的监控信息
|
||||
with state_lock:
|
||||
# 清理刮削文件
|
||||
self.delete_scrap_infos(file_path)
|
||||
# 删除的文件inode
|
||||
deleted_inode = self.state_set.get(str(file_path))
|
||||
if not deleted_inode:
|
||||
@@ -369,12 +478,16 @@ class RemoveLink(_PluginBase):
|
||||
# 删除硬链接文件
|
||||
logger.info(f"删除硬链接文件:{path}, inode: {inode}")
|
||||
file.unlink()
|
||||
# 清理刮削文件
|
||||
self.delete_scrap_infos(file_path)
|
||||
if self._notify:
|
||||
self.post_message(
|
||||
mtype=NotificationType.SiteMessage,
|
||||
title=f"【清理硬链接】",
|
||||
text=f"监控到删除源文件:[{file_path}]\n"
|
||||
f"同步删除硬链接文件:[{path}]",
|
||||
f"同步删除硬链接文件:[{path}]",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("删除硬链接文件发生错误:%s - %s" % (str(e), traceback.format_exc()))
|
||||
logger.error(
|
||||
"删除硬链接文件发生错误:%s - %s" % (str(e), traceback.format_exc())
|
||||
)
|
||||
|
||||
@@ -43,7 +43,7 @@ class SiteStatistic(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "statistic.png"
|
||||
# 插件版本
|
||||
plugin_version = "2.6"
|
||||
plugin_version = "2.8"
|
||||
# 插件作者
|
||||
plugin_author = "lightolly"
|
||||
# 作者主页
|
||||
@@ -70,6 +70,7 @@ class SiteStatistic(_PluginBase):
|
||||
_cron: str = ""
|
||||
_notify: bool = False
|
||||
_queue_cnt: int = 5
|
||||
_remove_failed: bool = False
|
||||
_statistic_type: str = None
|
||||
_statistic_sites: list = []
|
||||
|
||||
@@ -87,6 +88,7 @@ class SiteStatistic(_PluginBase):
|
||||
self._notify = config.get("notify")
|
||||
self._sitemsg = config.get("sitemsg")
|
||||
self._queue_cnt = config.get("queue_cnt")
|
||||
self._remove_failed = config.get("remove_failed")
|
||||
self._statistic_type = config.get("statistic_type") or "all"
|
||||
self._statistic_sites = config.get("statistic_sites") or []
|
||||
|
||||
@@ -369,7 +371,23 @@ class SiteStatistic(_PluginBase):
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 4
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'remove_failed',
|
||||
'label': '移除失效站点',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -381,6 +399,7 @@ class SiteStatistic(_PluginBase):
|
||||
"sitemsg": True,
|
||||
"cron": "5 1 * * *",
|
||||
"queue_cnt": 5,
|
||||
"remove_failed": False,
|
||||
"statistic_type": "all",
|
||||
"statistic_sites": []
|
||||
}
|
||||
@@ -1179,7 +1198,8 @@ class SiteStatistic(_PluginBase):
|
||||
"bonus": site_user_info.bonus,
|
||||
"url": site_url,
|
||||
"err_msg": site_user_info.err_msg,
|
||||
"message_unread": site_user_info.message_unread
|
||||
"message_unread": site_user_info.message_unread,
|
||||
"updated_at": datetime.now().strftime('%Y-%m-%d')
|
||||
}
|
||||
})
|
||||
return site_user_info
|
||||
@@ -1248,62 +1268,67 @@ class SiteStatistic(_PluginBase):
|
||||
if not refresh_sites:
|
||||
return
|
||||
|
||||
# 将数据初始化为前一天,筛选站点
|
||||
yesterday_sites_data = {}
|
||||
today_date = datetime.now().strftime('%Y-%m-%d')
|
||||
if self._statistic_type == "add" or not self._remove_failed:
|
||||
if last_update_time := self.get_data("last_update_time"):
|
||||
yesterday_sites_data = self.get_data(last_update_time) or {}
|
||||
|
||||
if not self._remove_failed and yesterday_sites_data:
|
||||
site_names = [site.get("name") for site in refresh_sites]
|
||||
self._sites_data = {k: v for k, v in yesterday_sites_data.items() if k in site_names}
|
||||
|
||||
# 并发刷新
|
||||
with ThreadPool(min(len(refresh_sites), int(self._queue_cnt or 5))) as p:
|
||||
p.map(self.__refresh_site_data, refresh_sites)
|
||||
|
||||
# 通知刷新完成
|
||||
if self._notify:
|
||||
yesterday_sites_data = {}
|
||||
# 增量数据
|
||||
if self._statistic_type == "add":
|
||||
last_update_time = self.get_data("last_update_time")
|
||||
if last_update_time:
|
||||
yesterday_sites_data = self.get_data(last_update_time) or {}
|
||||
|
||||
messages = []
|
||||
# 按照上传降序排序
|
||||
sites = self._sites_data.keys()
|
||||
uploads = [self._sites_data[site].get("upload") or 0 if not yesterday_sites_data.get(site) else
|
||||
int(self._sites_data[site].get("upload") or 0) - int(
|
||||
yesterday_sites_data[site].get("upload") or 0) for site in sites]
|
||||
downloads = [self._sites_data[site].get("download") or 0 if not yesterday_sites_data.get(site) else
|
||||
int(self._sites_data[site].get("download") or 0) - int(
|
||||
yesterday_sites_data[site].get("download") or 0) for site in sites]
|
||||
data_list = sorted(list(zip(sites, uploads, downloads)),
|
||||
key=lambda x: x[1],
|
||||
reverse=True)
|
||||
messages = {}
|
||||
# 总上传
|
||||
incUploads = 0
|
||||
# 总下载
|
||||
incDownloads = 0
|
||||
for data in data_list:
|
||||
site = data[0]
|
||||
upload = int(data[1])
|
||||
download = int(data[2])
|
||||
|
||||
for rand, site in enumerate(self._sites_data.keys()):
|
||||
upload = int(self._sites_data[site].get("upload") or 0)
|
||||
download = int(self._sites_data[site].get("download") or 0)
|
||||
updated_date = self._sites_data[site].get("updated_at")
|
||||
|
||||
if self._statistic_type == "add" and yesterday_sites_data.get(site):
|
||||
upload -= int(yesterday_sites_data[site].get("upload") or 0)
|
||||
download -= int(yesterday_sites_data[site].get("download") or 0)
|
||||
|
||||
if updated_date and updated_date != today_date:
|
||||
updated_date = f"({updated_date})"
|
||||
else:
|
||||
updated_date = ""
|
||||
|
||||
if upload > 0 or download > 0:
|
||||
incUploads += int(upload)
|
||||
incDownloads += int(download)
|
||||
messages.append(f"【{site}】\n"
|
||||
f"上传量:{StringUtils.str_filesize(upload)}\n"
|
||||
f"下载量:{StringUtils.str_filesize(download)}\n"
|
||||
f"————————————")
|
||||
incUploads += upload
|
||||
incDownloads += download
|
||||
messages[upload + (rand / 1000)] = (
|
||||
f"【{site}】{updated_date}\n"
|
||||
+ f"上传量:{StringUtils.str_filesize(upload)}\n"
|
||||
+ f"下载量:{StringUtils.str_filesize(download)}\n"
|
||||
+ "————————————"
|
||||
)
|
||||
|
||||
if incDownloads or incUploads:
|
||||
messages.insert(0, f"【汇总】\n"
|
||||
sorted_messages = [messages[key] for key in sorted(messages.keys(), reverse=True)]
|
||||
sorted_messages.insert(0, f"【汇总】\n"
|
||||
f"总上传:{StringUtils.str_filesize(incUploads)}\n"
|
||||
f"总下载:{StringUtils.str_filesize(incDownloads)}\n"
|
||||
f"————————————")
|
||||
self.post_message(mtype=NotificationType.SiteMessage,
|
||||
title="站点数据统计", text="\n".join(messages))
|
||||
title="站点数据统计", text="\n".join(sorted_messages))
|
||||
|
||||
# 获取今天的日期
|
||||
key = datetime.now().strftime('%Y-%m-%d')
|
||||
# 保存数据
|
||||
self.save_data(key, self._sites_data)
|
||||
self.save_data(today_date, self._sites_data)
|
||||
|
||||
# 更新时间
|
||||
self.save_data("last_update_time", key)
|
||||
self.save_data("last_update_time", today_date)
|
||||
logger.info("站点数据刷新完成")
|
||||
|
||||
def __custom_sites(self) -> List[Any]:
|
||||
@@ -1321,6 +1346,7 @@ class SiteStatistic(_PluginBase):
|
||||
"notify": self._notify,
|
||||
"sitemsg": self._sitemsg,
|
||||
"queue_cnt": self._queue_cnt,
|
||||
"remove_failed": self._remove_failed,
|
||||
"statistic_type": self._statistic_type,
|
||||
"statistic_sites": self._statistic_sites,
|
||||
})
|
||||
|
||||
@@ -217,6 +217,9 @@ class ISiteUserInfo(metaclass=ABCMeta):
|
||||
msg_links
|
||||
)
|
||||
unread_msg_links.extend(msg_links)
|
||||
# 重新更新未读消息数(99999表示有消息但数量未知)
|
||||
if self.message_unread == 99999:
|
||||
self.message_unread = len(unread_msg_links)
|
||||
# 解析未读消息内容
|
||||
for msg_link in unread_msg_links:
|
||||
logger.debug(f"{self.site_name} 信息链接 {msg_link}")
|
||||
|
||||
@@ -102,7 +102,8 @@ class MTorrentSiteUserInfo(ISiteUserInfo):
|
||||
self.download = int(user_info.get("memberCount", {}).get("downloaded") or '0')
|
||||
self.ratio = user_info.get("memberCount", {}).get("shareRate") or 0
|
||||
self.bonus = user_info.get("memberCount", {}).get("bonus") or 0
|
||||
self.message_unread = 1
|
||||
# 需要解析消息,但不确定消息条数
|
||||
self.message_unread = 99999
|
||||
|
||||
self._torrent_seeding_params = {
|
||||
"pageNumber": 1,
|
||||
|
||||
@@ -217,7 +217,7 @@ class NexusPhpSiteUserInfo(ISiteUserInfo):
|
||||
|
||||
# 是否存在下页数据
|
||||
next_page = None
|
||||
next_page_text = html.xpath('//a[contains(.//text(), "下一页") or contains(.//text(), "下一頁")]/@href')
|
||||
next_page_text = html.xpath('//a[contains(.//text(), "下一页") or contains(.//text(), "下一頁") or contains(.//text(), ">")]/@href')
|
||||
if next_page_text:
|
||||
next_page = next_page_text[-1].strip()
|
||||
# fix up page url
|
||||
|
||||