Merge branch 'feature_dev' of https://github.com/InfinityPacer/MoviePilot-Plugins into feature_dev

This commit is contained in:
InfinityPacer
2024-04-24 21:12:24 +08:00
49 changed files with 926 additions and 562 deletions

View File

@@ -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]:
"""

View File

@@ -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"
# 作者主页

View File

@@ -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 [
{

File diff suppressed because it is too large Load Diff

View File

@@ -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())
)

View File

@@ -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,
})

View File

@@ -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}")

View File

@@ -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,

View File

@@ -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