mirror of
https://github.com/d0zingcat/MoviePilot-Plugins.git
synced 2026-05-13 15:09:12 +00:00
fix 插件API安全控制
This commit is contained in:
@@ -121,6 +121,7 @@ class EventType(Enum):
|
||||
"description": "刷新对应域名的站点数据", // API描述
|
||||
}]
|
||||
```
|
||||
注意:在插件中暴露API接口时注意安全控制,推荐使用`settings.API_TOKEN`进行身份验证。
|
||||
|
||||
- 在对应的方法中实现API响应方法逻辑,通过 `http://localhost:3001/docs` 查看API文档和调试
|
||||
|
||||
|
||||
36
package.json
36
package.json
@@ -2,11 +2,12 @@
|
||||
"AutoSignIn": {
|
||||
"name": "站点自动签到",
|
||||
"description": "自动模拟登录、签到站点。",
|
||||
"version": "2.0",
|
||||
"version": "2.1",
|
||||
"icon": "signin.png",
|
||||
"author": "thsrite",
|
||||
"level": 2,
|
||||
"history": {
|
||||
"v2.1": "增强API安全性",
|
||||
"v2.0": "站点签到时更新站点使用统计信息,需要主程序升级至v1.8.3+版本",
|
||||
"v1.9": "支持馒头新架构自动签到"
|
||||
}
|
||||
@@ -22,11 +23,12 @@
|
||||
"SiteStatistic": {
|
||||
"name": "站点数据统计",
|
||||
"description": "自动统计和展示站点数据。",
|
||||
"version": "2.8",
|
||||
"version": "2.9",
|
||||
"icon": "statistic.png",
|
||||
"author": "lightolly",
|
||||
"level": 2,
|
||||
"history": {
|
||||
"v2.9": "增强API安全性",
|
||||
"v2.8": "修复馒头未读消息统计",
|
||||
"v2.7": "修复憨憨种子信息只统计第一页的问题,增加移除失效统计选项",
|
||||
"v2.6": "支持馒头新架构数据统计"
|
||||
@@ -43,11 +45,12 @@
|
||||
"DoubanSync": {
|
||||
"name": "豆瓣想看",
|
||||
"description": "同步豆瓣想看数据,自动添加订阅。",
|
||||
"version": "1.6",
|
||||
"version": "1.7",
|
||||
"icon": "douban.png",
|
||||
"author": "jxxghp",
|
||||
"level": 2,
|
||||
"history": {
|
||||
"v1.7": "增强API安全性",
|
||||
"v1.6": "同步历史记录支持手动删除,需要主程序升级至v1.8.4+版本",
|
||||
"v1.5": "豆瓣信息识别后直接添加订阅,不进行搜索下载"
|
||||
}
|
||||
@@ -55,11 +58,12 @@
|
||||
"DirMonitor": {
|
||||
"name": "目录监控",
|
||||
"description": "监控目录文件发生变化时实时整理到媒体库。",
|
||||
"version": "1.9",
|
||||
"version": "2.0",
|
||||
"icon": "directory.png",
|
||||
"author": "jxxghp",
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v2.0": "增强API安全性",
|
||||
"v1.9": "修复目录监控不能正确获取下载历史记录进行识别的问题"
|
||||
}
|
||||
},
|
||||
@@ -74,11 +78,12 @@
|
||||
"DoubanRank": {
|
||||
"name": "豆瓣榜单订阅",
|
||||
"description": "监控豆瓣热门榜单,自动添加订阅。",
|
||||
"version": "1.8",
|
||||
"version": "1.9",
|
||||
"icon": "movie.jpg",
|
||||
"author": "jxxghp",
|
||||
"level": 2,
|
||||
"history": {
|
||||
"v1.9": "增强API安全性",
|
||||
"v1.8": "订阅历史记录支持手动删除,需要主程序升级至v1.8.4+版本"
|
||||
}
|
||||
},
|
||||
@@ -125,10 +130,13 @@
|
||||
"CloudflareSpeedTest": {
|
||||
"name": "Cloudflare IP优选",
|
||||
"description": "🌩 测试 Cloudflare CDN 延迟和速度,自动优选IP。",
|
||||
"version": "1.1",
|
||||
"version": "1.2",
|
||||
"icon": "cloudflare.jpg",
|
||||
"author": "thsrite",
|
||||
"level": 1
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v1.2": "增强API安全性"
|
||||
}
|
||||
},
|
||||
"BestFilmVersion": {
|
||||
"name": "收藏洗版",
|
||||
@@ -189,10 +197,13 @@
|
||||
"AutoBackup": {
|
||||
"name": "自动备份",
|
||||
"description": "自动备份数据和配置文件。",
|
||||
"version": "1.1",
|
||||
"version": "1.2",
|
||||
"icon": "Time_machine_B.png",
|
||||
"author": "thsrite",
|
||||
"level": 1
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v1.2": "增强API安全性"
|
||||
}
|
||||
},
|
||||
"IYUUAutoSeed": {
|
||||
"name": "IYUU自动辅种",
|
||||
@@ -409,10 +420,13 @@
|
||||
"LinkMonitor": {
|
||||
"name": "实时硬链接",
|
||||
"description": "监控目录文件变化,实时硬链接。",
|
||||
"version": "1.5",
|
||||
"version": "1.6",
|
||||
"icon": "Linkace_C.png",
|
||||
"author": "jxxghp",
|
||||
"level": 1
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v1.6": "增强API安全性"
|
||||
}
|
||||
},
|
||||
"CategoryEditor": {
|
||||
"name": "二级分类策略",
|
||||
|
||||
@@ -25,7 +25,7 @@ class AutoBackup(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "Time_machine_B.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.1"
|
||||
plugin_version = "1.2"
|
||||
# 插件作者
|
||||
plugin_author = "thsrite"
|
||||
# 作者主页
|
||||
@@ -81,6 +81,14 @@ class AutoBackup(_PluginBase):
|
||||
self._scheduler.print_jobs()
|
||||
self._scheduler.start()
|
||||
|
||||
def api_backup(self, apikey: str):
|
||||
"""
|
||||
API调用备份
|
||||
"""
|
||||
if apikey != settings.API_TOKEN:
|
||||
return schemas.Response(success=False, message="API密钥错误")
|
||||
return self.__backup()
|
||||
|
||||
def __backup(self):
|
||||
"""
|
||||
自动备份、删除备份
|
||||
@@ -169,7 +177,7 @@ class AutoBackup(_PluginBase):
|
||||
def get_api(self) -> List[Dict[str, Any]]:
|
||||
return [{
|
||||
"path": "/backup",
|
||||
"endpoint": self.__backup,
|
||||
"endpoint": self.api_backup,
|
||||
"methods": ["GET"],
|
||||
"summary": "MoviePilot备份",
|
||||
"description": "MoviePilot备份",
|
||||
|
||||
@@ -38,7 +38,7 @@ class AutoSignIn(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "signin.png"
|
||||
# 插件版本
|
||||
plugin_version = "2.0"
|
||||
plugin_version = "2.1"
|
||||
# 插件作者
|
||||
plugin_author = "thsrite"
|
||||
# 作者主页
|
||||
@@ -852,10 +852,13 @@ class AutoSignIn(_PluginBase):
|
||||
logger.error("站点模块加载失败:%s" % str(e))
|
||||
return None
|
||||
|
||||
def signin_by_domain(self, url: str) -> schemas.Response:
|
||||
def signin_by_domain(self, url: str, apikey: str) -> schemas.Response:
|
||||
"""
|
||||
签到一个站点,可由API调用
|
||||
"""
|
||||
# 校验
|
||||
if apikey != settings.API_TOKEN:
|
||||
return schemas.Response(success=False, message="API密钥错误")
|
||||
domain = StringUtils.get_url_domain(url)
|
||||
site_info = self.sites.get_indexer(domain)
|
||||
if not site_info:
|
||||
|
||||
@@ -32,7 +32,7 @@ class CloudflareSpeedTest(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "cloudflare.jpg"
|
||||
# 插件版本
|
||||
plugin_version = "1.1"
|
||||
plugin_version = "1.2"
|
||||
# 插件作者
|
||||
plugin_author = "thsrite"
|
||||
# 作者主页
|
||||
@@ -750,10 +750,12 @@ class CloudflareSpeedTest(_PluginBase):
|
||||
def get_page(self) -> List[dict]:
|
||||
pass
|
||||
|
||||
def cloudflare_speedtest(self) -> schemas.Response:
|
||||
def cloudflare_speedtest(self, apikey: str) -> schemas.Response:
|
||||
"""
|
||||
API调用CloudflareSpeedTest IP优选
|
||||
"""
|
||||
if apikey != settings.API_TOKEN:
|
||||
return schemas.Response(success=False, message="API密钥错误")
|
||||
self.__cloudflareSpeedTest()
|
||||
return schemas.Response(success=True)
|
||||
|
||||
|
||||
@@ -134,19 +134,19 @@ class ContractCheck(_PluginBase):
|
||||
self._scheduler.start()
|
||||
|
||||
def parse_contract_infos(self, infos):
|
||||
if infos == None:
|
||||
if infos is None:
|
||||
return
|
||||
info_list = infos.split("\n")
|
||||
for info in info_list:
|
||||
_site_name, _official, _size, _num, _duration, date = info.split("|")
|
||||
site_id = self._get_site_id(_site_name)
|
||||
if site_id == None:
|
||||
if site_id is None:
|
||||
logger.error(f"站点{_site_name}不在数据库中,请检查配置!")
|
||||
continue
|
||||
date_format = "%Y/%m/%d"
|
||||
date = datetime.strptime(date, date_format).date()
|
||||
date = datetime.strptime(date, date_format)
|
||||
_official = True if _official == "是" else False
|
||||
c_info: self.ContractInfo = self.ContractInfo(
|
||||
c_info = self.ContractInfo(
|
||||
_site_name,
|
||||
_official,
|
||||
int(_size) * 1024 * 1024 * 1024,
|
||||
@@ -622,7 +622,7 @@ class ContractCheck(_PluginBase):
|
||||
i = html_text.find("window.location")
|
||||
if i == -1:
|
||||
return None
|
||||
tmp_url = url + html_text[i : html_text.find(";")].replace(
|
||||
tmp_url = url + html_text[i: html_text.find(";")].replace(
|
||||
'"', ""
|
||||
).replace("+", "").replace(" ", "").replace(
|
||||
"window.location=", ""
|
||||
@@ -689,8 +689,8 @@ class ContractCheck(_PluginBase):
|
||||
return None
|
||||
|
||||
# 检查契约达成情况,返回是否达成、差多少体积、差多少数量、还剩多少时间
|
||||
def _check_seed_states(self, contract_info, site_user_info):
|
||||
is_satisfied = False
|
||||
@staticmethod
|
||||
def _check_seed_states(contract_info, site_user_info):
|
||||
is_size_satisfied = False
|
||||
is_num_satisfied = False
|
||||
size_gap = 0
|
||||
@@ -883,4 +883,4 @@ class ContractCheck(_PluginBase):
|
||||
"queue_cnt": self._queue_cnt,
|
||||
"contract_infos": self._contract_infos,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -15,7 +15,6 @@ from bencode import bdecode, bencode
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.event import eventmanager
|
||||
from app.db.models import Site
|
||||
from app.db.site_oper import SiteOper
|
||||
from app.helper.sites import SitesHelper
|
||||
from app.helper.torrent import TorrentHelper
|
||||
@@ -35,15 +34,15 @@ class CSSiteConfig(object):
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str = None,
|
||||
url: str = None,
|
||||
passkey: str = None,
|
||||
id: int = None,
|
||||
cookie: str = None,
|
||||
ua: str = None,
|
||||
proxy: bool = None,
|
||||
query_gap: int = 1,
|
||||
self,
|
||||
name: str = None,
|
||||
url: str = None,
|
||||
passkey: str = None,
|
||||
id: int = None,
|
||||
cookie: str = None,
|
||||
ua: str = None,
|
||||
proxy: bool = None,
|
||||
query_gap: int = 1,
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.url = url
|
||||
@@ -264,11 +263,9 @@ class CrossSeed(_PluginBase):
|
||||
(site.get("id"), site.get("name")) for site in self.__custom_sites()
|
||||
]
|
||||
self._sites = [site_id for site_id, site_name in all_sites if site_id in self._sites]
|
||||
# 拆分出选中的站点
|
||||
site_names = [site_name for site_id, site_name in all_sites if site_id in self._sites]
|
||||
|
||||
# 整理所有可用内部站点信息
|
||||
all_site_cs_info_map : dict[str, CSSiteConfig] = dict()
|
||||
all_site_cs_info_map: dict[str, CSSiteConfig] = dict()
|
||||
for site in inner_site_list:
|
||||
if site.is_active:
|
||||
all_site_cs_info_map[site.name] = CSSiteConfig(
|
||||
@@ -277,7 +274,7 @@ class CrossSeed(_PluginBase):
|
||||
id=site.id,
|
||||
cookie=site.cookie,
|
||||
ua=site.ua,
|
||||
proxy=site.proxy,
|
||||
proxy=True if site.proxy else False,
|
||||
)
|
||||
for site in self.__custom_sites():
|
||||
all_site_cs_info_map[site.get("name")] = CSSiteConfig(
|
||||
@@ -295,7 +292,7 @@ class CrossSeed(_PluginBase):
|
||||
site_name_key_map = dict()
|
||||
site_name_gap_map = dict()
|
||||
for site_key in self._token.strip().split("\n"):
|
||||
site_key_arr = re.split("[\s::]+",site_key.strip())
|
||||
site_key_arr = re.split(r"[\s::]+", site_key.strip())
|
||||
site_name = site_key_arr[0]
|
||||
site_name_key_map[site_name] = site_key_arr[1]
|
||||
if len(site_key_arr) > 2:
|
||||
@@ -312,7 +309,8 @@ class CrossSeed(_PluginBase):
|
||||
for site_name in site_names:
|
||||
site_key = site_name_key_map.get(site_name)
|
||||
if not site_key:
|
||||
logger.warning(f"未找到站点{site_name}的passkey, 请检查passkey配置是否有误,站点{site_name}将跳过辅种")
|
||||
logger.warning(
|
||||
f"未找到站点{site_name}的passkey, 请检查passkey配置是否有误,站点{site_name}将跳过辅种")
|
||||
continue
|
||||
site_cs_info = all_site_cs_info_map.get(site_name)
|
||||
site_cs_info.passkey = site_key
|
||||
@@ -381,7 +379,7 @@ class CrossSeed(_PluginBase):
|
||||
"""
|
||||
if self.get_state():
|
||||
# 如果开启了定时任务,并且参数齐全
|
||||
if self._cron :
|
||||
if self._cron:
|
||||
return [{
|
||||
"id": "CrossSeed",
|
||||
"name": "青蛙辅种助手",
|
||||
@@ -392,10 +390,10 @@ class CrossSeed(_PluginBase):
|
||||
else:
|
||||
# 随机时间
|
||||
triggers = TimerUtils.random_scheduler(num_executions=1,
|
||||
begin_hour=2,
|
||||
end_hour=7,
|
||||
max_interval=290,
|
||||
min_interval=0)
|
||||
begin_hour=2,
|
||||
end_hour=7,
|
||||
max_interval=290,
|
||||
min_interval=0)
|
||||
ret_jobs = []
|
||||
for trigger in triggers:
|
||||
ret_jobs.append({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json,re
|
||||
import json
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from app.modules.emby import Emby
|
||||
@@ -347,10 +348,10 @@ class DiagParamAdjust(_PluginBase):
|
||||
return False
|
||||
|
||||
# 符合所有情况
|
||||
if (('repeatlast' in replaceText \
|
||||
and 'x=(W-w)/2:y=(H-h):repeatlast=0' in searchText \
|
||||
and result['Object']['TranscodingOptions']['DisableHardwareSubtitleOverlay'] is True) \
|
||||
or (searchText == "" and replaceText == "")) \
|
||||
if (('repeatlast' in replaceText
|
||||
and 'x=(W-w)/2:y=(H-h):repeatlast=0' in searchText
|
||||
and result['Object']['TranscodingOptions']['DisableHardwareSubtitleOverlay'] is True)
|
||||
or (searchText == "" and replaceText == "")) \
|
||||
and self._resolution == self._last_resolution:
|
||||
# (A or B) and C
|
||||
return True
|
||||
|
||||
@@ -24,7 +24,7 @@ from app.db.downloadhistory_oper import DownloadHistoryOper
|
||||
from app.db.transferhistory_oper import TransferHistoryOper
|
||||
from app.log import logger
|
||||
from app.plugins import _PluginBase
|
||||
from app.schemas import Notification, NotificationType, TransferInfo
|
||||
from app.schemas import NotificationType, TransferInfo
|
||||
from app.schemas.types import EventType, MediaType, SystemConfigKey
|
||||
from app.utils.string import StringUtils
|
||||
from app.utils.system import SystemUtils
|
||||
@@ -59,7 +59,7 @@ class DirMonitor(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "directory.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.9"
|
||||
plugin_version = "2.0"
|
||||
# 插件作者
|
||||
plugin_author = "jxxghp"
|
||||
# 作者主页
|
||||
@@ -655,10 +655,12 @@ class DirMonitor(_PluginBase):
|
||||
}]
|
||||
return []
|
||||
|
||||
def sync(self) -> schemas.Response:
|
||||
def sync(self, apikey: str) -> schemas.Response:
|
||||
"""
|
||||
API调用目录同步
|
||||
"""
|
||||
if apikey != settings.API_TOKEN:
|
||||
return schemas.Response(success=False, message="API密钥错误")
|
||||
self.sync_all()
|
||||
return schemas.Response(success=True)
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class DoubanRank(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "movie.jpg"
|
||||
# 插件版本
|
||||
plugin_version = "1.8"
|
||||
plugin_version = "1.9"
|
||||
# 插件作者
|
||||
plugin_author = "jxxghp"
|
||||
# 作者主页
|
||||
@@ -395,7 +395,8 @@ class DoubanRank(_PluginBase):
|
||||
'api': 'plugin/DoubanRank/delete_history',
|
||||
'method': 'get',
|
||||
'params': {
|
||||
'key': f"doubanrank: {title} (DB:{doubanid})"
|
||||
'key': f"doubanrank: {title} (DB:{doubanid})",
|
||||
'apikey': settings.API_TOKEN
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -488,10 +489,12 @@ class DoubanRank(_PluginBase):
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
|
||||
def delete_history(self, key: str):
|
||||
def delete_history(self, key: str, apikey: str):
|
||||
"""
|
||||
删除同步历史记录
|
||||
"""
|
||||
if apikey != settings.API_TOKEN:
|
||||
return schemas.Response(success=False, message="API密钥错误")
|
||||
# 历史记录
|
||||
historys = self.get_data('history')
|
||||
if not historys:
|
||||
|
||||
@@ -34,7 +34,7 @@ class DoubanSync(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "douban.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.6"
|
||||
plugin_version = "1.7"
|
||||
# 插件作者
|
||||
plugin_author = "jxxghp"
|
||||
# 作者主页
|
||||
@@ -371,7 +371,8 @@ class DoubanSync(_PluginBase):
|
||||
'api': 'plugin/DoubanSync/delete_history',
|
||||
'method': 'get',
|
||||
'params': {
|
||||
'doubanid': doubanid
|
||||
'doubanid': doubanid,
|
||||
'apikey': settings.API_TOKEN
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -463,10 +464,12 @@ class DoubanSync(_PluginBase):
|
||||
"clear": self._clear
|
||||
})
|
||||
|
||||
def delete_history(self, doubanid: str):
|
||||
def delete_history(self, doubanid: str, apikey: str):
|
||||
"""
|
||||
删除同步历史记录
|
||||
"""
|
||||
if apikey != settings.API_TOKEN:
|
||||
return schemas.Response(success=False, message="API密钥错误")
|
||||
# 历史记录
|
||||
historys = self.get_data('history')
|
||||
if not historys:
|
||||
|
||||
@@ -171,7 +171,7 @@ class DownloaderHelper(_PluginBase):
|
||||
|
||||
return [{
|
||||
'component': 'VForm',
|
||||
'content': [{ # 业务无关总控
|
||||
'content': [{ # 业务无关总控
|
||||
'component': 'VRow',
|
||||
'content': [{
|
||||
'component': 'VCol',
|
||||
@@ -216,7 +216,7 @@ class DownloaderHelper(_PluginBase):
|
||||
}
|
||||
}]
|
||||
}]
|
||||
}, { # 业务相关总控
|
||||
}, { # 业务相关总控
|
||||
'component': 'VRow',
|
||||
'content': [{
|
||||
'component': 'VCol',
|
||||
|
||||
@@ -192,8 +192,6 @@ class FeiShuMsg(_PluginBase):
|
||||
title = msg_body.get("title")
|
||||
# 文本
|
||||
text = msg_body.get("text")
|
||||
# 图像
|
||||
image = msg_body.get("image")
|
||||
|
||||
if not title and not text:
|
||||
logger.warn("标题和内容不能同时为空")
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import socket
|
||||
from typing import List, Tuple, Dict, Any
|
||||
import re
|
||||
import socket
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Tuple, Dict, Any
|
||||
|
||||
import pytz
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from dotenv import set_key
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.module import ModuleManager
|
||||
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
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
from app.schemas import NotificationType
|
||||
|
||||
|
||||
class IpDetect(_PluginBase):
|
||||
@@ -218,7 +220,8 @@ class IpDetect(_PluginBase):
|
||||
set_key(settings.CONFIG_PATH / "app.env", k, v)
|
||||
logger.info(f"重新设置服务地址{k}成功!")
|
||||
|
||||
def get_value(self, key):
|
||||
@staticmethod
|
||||
def get_value(key):
|
||||
if key == "QB_HOST":
|
||||
return settings.QB_HOST
|
||||
elif key == "TR_HOST":
|
||||
@@ -238,7 +241,8 @@ class IpDetect(_PluginBase):
|
||||
else:
|
||||
return None
|
||||
|
||||
def parse_ip(self, ip):
|
||||
@staticmethod
|
||||
def parse_ip(ip):
|
||||
ip_pattern = r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"
|
||||
match = re.search(ip_pattern, ip)
|
||||
if match:
|
||||
@@ -268,7 +272,8 @@ class IpDetect(_PluginBase):
|
||||
}
|
||||
]
|
||||
|
||||
def get_local_ip(self):
|
||||
@staticmethod
|
||||
def get_local_ip():
|
||||
try:
|
||||
# 创建一个 UDP 套接字
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
|
||||
@@ -17,7 +17,7 @@ from app.core.config import settings
|
||||
from app.core.event import eventmanager, Event
|
||||
from app.log import logger
|
||||
from app.plugins import _PluginBase
|
||||
from app.schemas import Notification, NotificationType
|
||||
from app.schemas import NotificationType
|
||||
from app.schemas.types import EventType
|
||||
from app.utils.system import SystemUtils
|
||||
|
||||
@@ -51,7 +51,7 @@ class LinkMonitor(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "Linkace_C.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.5"
|
||||
plugin_version = "1.6"
|
||||
# 插件作者
|
||||
plugin_author = "jxxghp"
|
||||
# 作者主页
|
||||
@@ -393,10 +393,12 @@ class LinkMonitor(_PluginBase):
|
||||
"kwargs": {}
|
||||
}]
|
||||
|
||||
def sync(self) -> schemas.Response:
|
||||
def sync(self, apikey: str) -> schemas.Response:
|
||||
"""
|
||||
API调用目录同步
|
||||
"""
|
||||
if apikey != settings.API_TOKEN:
|
||||
return schemas.Response(success=False, message="API密钥错误")
|
||||
self.sync_all()
|
||||
return schemas.Response(success=True)
|
||||
|
||||
|
||||
@@ -4,10 +4,9 @@ from threading import Lock
|
||||
from typing import Optional, Any, List, Dict, Tuple
|
||||
|
||||
import pytz
|
||||
import requests
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
import requests
|
||||
from app.schemas.types import MediaType
|
||||
|
||||
from app.chain.download import DownloadChain
|
||||
from app.chain.search import SearchChain
|
||||
@@ -19,7 +18,7 @@ from app.core.metainfo import MetaInfo
|
||||
from app.log import logger
|
||||
from app.plugins import _PluginBase
|
||||
from app.schemas.types import EventType
|
||||
from app.utils.http import RequestUtils
|
||||
from app.schemas.types import MediaType
|
||||
|
||||
lock = Lock()
|
||||
|
||||
@@ -308,7 +307,7 @@ class NeoDBSync(_PluginBase):
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
@@ -445,7 +444,6 @@ class NeoDBSync(_PluginBase):
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def __update_config(self):
|
||||
"""
|
||||
更新配置
|
||||
@@ -494,7 +492,6 @@ class NeoDBSync(_PluginBase):
|
||||
username = self.__get_username(token)
|
||||
# 同步每个 NeoDB 用户的数据
|
||||
logger.info(f"开始同步 NeoDB 用户 {username} 的想看数据 ...")
|
||||
results = []
|
||||
try:
|
||||
movie_response = requests.get(self._movie_url, headers=headers)
|
||||
movie_response.raise_for_status()
|
||||
@@ -545,7 +542,6 @@ class NeoDBSync(_PluginBase):
|
||||
exist_flag, no_exists = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo)
|
||||
if exist_flag:
|
||||
logger.info(f'{mediainfo.title_year} 媒体库中已存在')
|
||||
action = "exist"
|
||||
else:
|
||||
# 添加订阅
|
||||
logger.info(f'{mediainfo.title_year} 媒体库中不存在或不完整,添加订阅 ...')
|
||||
@@ -594,12 +590,13 @@ class NeoDBSync(_PluginBase):
|
||||
return False
|
||||
return True
|
||||
|
||||
def __get_username(self, token: str):
|
||||
@staticmethod
|
||||
def __get_username(token: str):
|
||||
"""
|
||||
获取 NeoDB 用户名
|
||||
"""
|
||||
try:
|
||||
user_info = requests.get(f"https://neodb.social/api/me", headers= {"Authorization": f"Bearer {token}"})
|
||||
user_info = requests.get(f"https://neodb.social/api/me", headers={"Authorization": f"Bearer {token}"})
|
||||
user_info.raise_for_status()
|
||||
try:
|
||||
username = user_info.json().get("username")
|
||||
|
||||
@@ -67,7 +67,7 @@ class PluginAutoUpgrade(_PluginBase):
|
||||
self.stop_service()
|
||||
# 如果需要立即运行一次
|
||||
if self.__get_config_item(config_key='run_once'):
|
||||
if (self.__start_scheduler()):
|
||||
if self.__start_scheduler():
|
||||
self.__scheduler.add_job(func=self.__try_run,
|
||||
trigger='date',
|
||||
run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3),
|
||||
@@ -81,9 +81,8 @@ class PluginAutoUpgrade(_PluginBase):
|
||||
"""
|
||||
获取插件状态
|
||||
"""
|
||||
state = True if self.__get_config_item(config_key='enable') \
|
||||
and self.__get_config_item(config_key='cron') \
|
||||
else False
|
||||
state = True if self.__get_config_item(config_key='enable') and self.__get_config_item(config_key='cron') \
|
||||
else False
|
||||
return state
|
||||
|
||||
@staticmethod
|
||||
@@ -133,7 +132,7 @@ class PluginAutoUpgrade(_PluginBase):
|
||||
installed_online_plugin_options = self.__get_installed_online_plugin_options()
|
||||
form = [{
|
||||
'component': 'VForm',
|
||||
'content': [{ # 业务无关总控
|
||||
'content': [{ # 业务无关总控
|
||||
'component': 'VRow',
|
||||
'content': [{
|
||||
'component': 'VCol',
|
||||
@@ -386,11 +385,12 @@ class PluginAutoUpgrade(_PluginBase):
|
||||
获取所有已安装的本地插件信息
|
||||
"""
|
||||
local_plugins = cls.__get_local_plugins()
|
||||
installed_local_plugins = [local_plugin for local_plugin in local_plugins if local_plugin and local_plugin.installed]
|
||||
installed_local_plugins = [local_plugin for local_plugin in local_plugins if
|
||||
local_plugin and local_plugin.installed]
|
||||
return installed_local_plugins
|
||||
|
||||
@classmethod
|
||||
def __get_installed_local_plugin(cls, plugin_id: str) -> List[schemas.Plugin]:
|
||||
def __get_installed_local_plugin(cls, plugin_id: str) -> Optional[schemas.Plugin]:
|
||||
"""
|
||||
获取指定的已安装的本地插件信息
|
||||
"""
|
||||
@@ -417,11 +417,12 @@ class PluginAutoUpgrade(_PluginBase):
|
||||
获取所有已安装的在线插件
|
||||
"""
|
||||
online_plugins = cls.__get_online_plugins()
|
||||
installed_online_plugins = [online_plugin for online_plugin in online_plugins if online_plugin and online_plugin.installed]
|
||||
installed_online_plugins = [online_plugin for online_plugin in online_plugins if
|
||||
online_plugin and online_plugin.installed]
|
||||
return installed_online_plugins
|
||||
|
||||
@classmethod
|
||||
def __get_installed_online_plugin_options(cls) -> Dict[str, Any]:
|
||||
def __get_installed_online_plugin_options(cls) -> list:
|
||||
"""
|
||||
获取所有已安装的在线插件的选项数据
|
||||
"""
|
||||
@@ -437,14 +438,15 @@ class PluginAutoUpgrade(_PluginBase):
|
||||
return installed_online_plugin_options
|
||||
|
||||
@classmethod
|
||||
def __get_has_update_online_plugins(cls) -> List[schemas.Plugin]:
|
||||
def __get_has_update_online_plugins(cls) -> Optional[List[schemas.Plugin]]:
|
||||
"""
|
||||
获取所有可升级的在线插件
|
||||
"""
|
||||
installed_online_plugins = cls.__get_installed_online_plugins()
|
||||
if not installed_online_plugins:
|
||||
return None
|
||||
has_update_online_plugins = [installed_online_plugin for installed_online_plugin in installed_online_plugins if installed_online_plugin and installed_online_plugin.has_update]
|
||||
has_update_online_plugins = [installed_online_plugin for installed_online_plugin in installed_online_plugins if
|
||||
installed_online_plugin and installed_online_plugin.has_update]
|
||||
return has_update_online_plugins
|
||||
|
||||
def __start_scheduler(self, timezone=None) -> bool:
|
||||
@@ -500,7 +502,7 @@ class PluginAutoUpgrade(_PluginBase):
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def __install_plugin(plugin_id: str, repo_url: str = "", force: bool = False) -> Tuple[bool, str]:
|
||||
def __install_plugin(plugin_id: str, repo_url: str = "", force: bool = False) -> Tuple[bool, Optional[str]]:
|
||||
"""
|
||||
安装插件,参考:app.api.endpoints.plugin.install
|
||||
:param plugin_id: 插件ID
|
||||
@@ -560,17 +562,20 @@ class PluginAutoUpgrade(_PluginBase):
|
||||
# 发送通知
|
||||
self.__send_notify(results=upgrade_results)
|
||||
|
||||
def __upgrade_single(self, online_plugin: schemas.Plugin) -> Dict[str, Any]:
|
||||
def __upgrade_single(self, online_plugin: schemas.Plugin) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
单个升级
|
||||
"""
|
||||
if not online_plugin or not online_plugin.has_update or not online_plugin.id or not online_plugin.repo_url or not self.__check_allow_upgrade(plugin_id=online_plugin.id):
|
||||
if not online_plugin or not online_plugin.has_update or not online_plugin.id or not online_plugin.repo_url or not self.__check_allow_upgrade(
|
||||
plugin_id=online_plugin.id):
|
||||
return None
|
||||
installed_local_plugin = self.__get_installed_local_plugin(plugin_id=online_plugin.id)
|
||||
if not installed_local_plugin:
|
||||
return None
|
||||
success, message = self.__install_plugin(plugin_id=online_plugin.id, repo_url=online_plugin.repo_url, force=True)
|
||||
logger.info(f"插件升级结果: plugin_name = {online_plugin.plugin_name}, plugin_version = v{installed_local_plugin.plugin_version} -> v{online_plugin.plugin_version}, success = {success}, message = {message}")
|
||||
success, message = self.__install_plugin(plugin_id=online_plugin.id, repo_url=online_plugin.repo_url,
|
||||
force=True)
|
||||
logger.info(
|
||||
f"插件升级结果: plugin_name = {online_plugin.plugin_name}, plugin_version = v{installed_local_plugin.plugin_version} -> v{online_plugin.plugin_version}, success = {success}, message = {message}")
|
||||
return {
|
||||
'success': success,
|
||||
'message': message,
|
||||
@@ -578,7 +583,7 @@ class PluginAutoUpgrade(_PluginBase):
|
||||
'plugin_name': online_plugin.plugin_name,
|
||||
'new_plugin_version': online_plugin.plugin_version,
|
||||
'old_plugin_version': installed_local_plugin.plugin_version,
|
||||
'datetime_str': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
'datetime_str': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
'upgrade_info': self.__extract_upgrade_history(online_plugin)
|
||||
}
|
||||
|
||||
@@ -627,7 +632,7 @@ class PluginAutoUpgrade(_PluginBase):
|
||||
self.save_data(self.__data_key_upgrade_records, upgrade_records)
|
||||
|
||||
@staticmethod
|
||||
def __convert_upgrade_record_to_page_data(upgrade_record: Dict[str, Any]) -> Dict[str, Any]:
|
||||
def __convert_upgrade_record_to_page_data(upgrade_record: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
if not upgrade_record:
|
||||
return None
|
||||
info = "成功" if upgrade_record.get("success") else upgrade_record.get("message")
|
||||
@@ -643,13 +648,14 @@ class PluginAutoUpgrade(_PluginBase):
|
||||
return []
|
||||
# 只展示最近10条
|
||||
upgrade_records = upgrade_records[-10:]
|
||||
page_data = [self.__convert_upgrade_record_to_page_data(upgrade_record) for upgrade_record in upgrade_records if upgrade_record]
|
||||
page_data = [self.__convert_upgrade_record_to_page_data(upgrade_record) for upgrade_record in upgrade_records if
|
||||
upgrade_record]
|
||||
# 按时间倒序
|
||||
page_data = sorted(page_data, key=lambda item: item.get("datetime_str"), reverse=True)
|
||||
return page_data
|
||||
|
||||
@staticmethod
|
||||
def __extract_upgrade_history(plugin: schemas.Plugin, version: str = None) -> str:
|
||||
def __extract_upgrade_history(plugin: schemas.Plugin, version: str = None) -> Optional[str]:
|
||||
"""
|
||||
提取指定版本的升级历史信息
|
||||
"""
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
from urllib.parse import urlencode
|
||||
from typing import Any, List, Dict, Tuple
|
||||
|
||||
from app.plugins import _PluginBase
|
||||
from app.core.event import eventmanager, Event
|
||||
from app.log import logger
|
||||
from app.plugins import _PluginBase
|
||||
from app.schemas.types import EventType, NotificationType
|
||||
from app.utils.http import RequestUtils
|
||||
from typing import Any, List, Dict, Tuple
|
||||
from app.log import logger
|
||||
|
||||
|
||||
class PushPlusMsg(_PluginBase):
|
||||
@@ -179,7 +178,7 @@ class PushPlusMsg(_PluginBase):
|
||||
"title": title,
|
||||
"content": text,
|
||||
"template": "txt",
|
||||
"channel":"wechat"
|
||||
"channel": "wechat"
|
||||
}
|
||||
res = RequestUtils(content_type="application/json").post_res(sc_url, json=event_info)
|
||||
if res and res.status_code == 200:
|
||||
|
||||
@@ -262,7 +262,8 @@ class QbCommand(_PluginBase):
|
||||
return []
|
||||
return all_torrents
|
||||
|
||||
def get_torrents_status(self, torrents):
|
||||
@staticmethod
|
||||
def get_torrents_status(torrents):
|
||||
downloading_torrents = []
|
||||
uploading_torrents = []
|
||||
paused_torrents = []
|
||||
@@ -372,7 +373,7 @@ class QbCommand(_PluginBase):
|
||||
f"暂停操作中请稍等...\n",
|
||||
)
|
||||
if len(to_be_paused) > 0:
|
||||
if self._qb.stop_torrents(ids=(to_be_paused)):
|
||||
if self._qb.stop_torrents(ids=to_be_paused):
|
||||
logger.info(f"暂停了{len(to_be_paused)}个种子")
|
||||
else:
|
||||
logger.error(f"暂停种子失败")
|
||||
@@ -625,7 +626,6 @@ class QbCommand(_PluginBase):
|
||||
if flag:
|
||||
logger.info(f"设置QB限速成功")
|
||||
if self._notify:
|
||||
text = "QB设置限速成功\n"
|
||||
if upload_limit == 0:
|
||||
text = f"上传无限速"
|
||||
else:
|
||||
@@ -639,7 +639,7 @@ class QbCommand(_PluginBase):
|
||||
title=f"【QB远程操作】",
|
||||
text=text,
|
||||
)
|
||||
elif flag == False:
|
||||
elif not flag:
|
||||
logger.error(f"QB设置限速失败")
|
||||
if self._notify:
|
||||
self.post_message(
|
||||
|
||||
@@ -10,7 +10,7 @@ from watchdog.observers import Observer
|
||||
|
||||
from app.log import logger
|
||||
from app.plugins import _PluginBase
|
||||
from app.schemas import Notification, NotificationType
|
||||
from app.schemas import NotificationType
|
||||
|
||||
state_lock = threading.Lock()
|
||||
|
||||
@@ -375,7 +375,8 @@ class RemoveLink(_PluginBase):
|
||||
return True
|
||||
return False
|
||||
|
||||
def scrape_files_left(self, path):
|
||||
@staticmethod
|
||||
def scrape_files_left(path):
|
||||
"""
|
||||
检查path目录是否只包含刮削文件
|
||||
"""
|
||||
@@ -415,6 +416,7 @@ class RemoveLink(_PluginBase):
|
||||
logger.info(f'删除刮削文件:{file}')
|
||||
# 清理空目录
|
||||
self.delete_empty_folders(path)
|
||||
|
||||
def delete_empty_folders(self, path):
|
||||
"""
|
||||
从指定路径开始,逐级向上层目录检测并删除空目录,直到遇到非空目录或到达指定监控目录为止
|
||||
@@ -485,7 +487,7 @@ class RemoveLink(_PluginBase):
|
||||
mtype=NotificationType.SiteMessage,
|
||||
title=f"【清理硬链接】",
|
||||
text=f"监控到删除源文件:[{file_path}]\n"
|
||||
f"同步删除硬链接文件:[{path}]",
|
||||
f"同步删除硬链接文件:[{path}]",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
|
||||
@@ -43,7 +43,7 @@ class SiteStatistic(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "statistic.png"
|
||||
# 插件版本
|
||||
plugin_version = "2.8"
|
||||
plugin_version = "2.9"
|
||||
# 插件作者
|
||||
plugin_author = "lightolly"
|
||||
# 作者主页
|
||||
@@ -1127,10 +1127,12 @@ class SiteStatistic(_PluginBase):
|
||||
return site_schema(site_name, url, site_cookie, html_text, session=session, ua=ua, proxy=proxy)
|
||||
return None
|
||||
|
||||
def refresh_by_domain(self, domain: str) -> schemas.Response:
|
||||
def refresh_by_domain(self, domain: str, apikey: str) -> schemas.Response:
|
||||
"""
|
||||
刷新一个站点数据,可由API调用
|
||||
"""
|
||||
if apikey != settings.API_TOKEN:
|
||||
return schemas.Response(success=False, message="API密钥错误")
|
||||
site_info = self.sites.get_indexer(domain)
|
||||
if site_info:
|
||||
site_data = self.__refresh_site_data(site_info)
|
||||
@@ -1309,18 +1311,18 @@ class SiteStatistic(_PluginBase):
|
||||
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"
|
||||
+ "————————————"
|
||||
f"【{site}】{updated_date}\n"
|
||||
+ f"上传量:{StringUtils.str_filesize(upload)}\n"
|
||||
+ f"下载量:{StringUtils.str_filesize(download)}\n"
|
||||
+ "————————————"
|
||||
)
|
||||
|
||||
if incDownloads or incUploads:
|
||||
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"————————————")
|
||||
f"总上传:{StringUtils.str_filesize(incUploads)}\n"
|
||||
f"总下载:{StringUtils.str_filesize(incDownloads)}\n"
|
||||
f"————————————")
|
||||
self.post_message(mtype=NotificationType.SiteMessage,
|
||||
title="站点数据统计", text="\n".join(sorted_messages))
|
||||
|
||||
|
||||
@@ -99,7 +99,6 @@ class TrackerEditor(_PluginBase):
|
||||
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)
|
||||
@@ -114,7 +113,7 @@ class TrackerEditor(_PluginBase):
|
||||
__tracker_list = new_tracker_list
|
||||
if torrent_update_cnt > 0:
|
||||
update_result = self._downloader.update_tracker(hash_string=torrent.hashString, tracker_list=__tracker_list)
|
||||
if update_result == False:
|
||||
if not update_result:
|
||||
logger.error(f"执行tracker修改出错,中止本次执行")
|
||||
torrent_update_cnt = 0
|
||||
break
|
||||
|
||||
@@ -200,7 +200,8 @@ class TrCommand(_PluginBase):
|
||||
return []
|
||||
return all_torrents
|
||||
|
||||
def get_torrents_status(self, torrents):
|
||||
@staticmethod
|
||||
def get_torrents_status(torrents):
|
||||
downloading_torrents = []
|
||||
uploading_torrents = []
|
||||
paused_torrents = []
|
||||
@@ -265,15 +266,15 @@ class TrCommand(_PluginBase):
|
||||
mtype=NotificationType.SiteMessage,
|
||||
title=f"【TR暂停任务启动】",
|
||||
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",
|
||||
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",
|
||||
)
|
||||
if len(to_be_paused) > 0:
|
||||
if self._tr.stop_torrents(ids=(to_be_paused)):
|
||||
if self._tr.stop_torrents(ids=to_be_paused):
|
||||
logger.info(f"暂停了{len(to_be_paused)}个种子")
|
||||
else:
|
||||
logger.error(f"暂停种子失败")
|
||||
@@ -305,11 +306,11 @@ class TrCommand(_PluginBase):
|
||||
mtype=NotificationType.SiteMessage,
|
||||
title=f"【TR暂停任务完成】",
|
||||
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"做种数量: {len(hash_uploading)}\n"
|
||||
f"下载数量: {len(hash_downloading)}\n"
|
||||
f"检查数量: {len(hash_checking)}\n"
|
||||
f"暂停数量: {len(hash_paused)}\n"
|
||||
f"错误数量: {len(hash_error)}\n",
|
||||
)
|
||||
|
||||
@eventmanager.register(EventType.PluginAction)
|
||||
@@ -345,12 +346,12 @@ class TrCommand(_PluginBase):
|
||||
mtype=NotificationType.SiteMessage,
|
||||
title=f"【TR开始任务启动】",
|
||||
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",
|
||||
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",
|
||||
)
|
||||
if not self._tr.start_torrents(ids=hash_paused):
|
||||
logger.error(f"开始种子失败")
|
||||
@@ -382,11 +383,11 @@ class TrCommand(_PluginBase):
|
||||
mtype=NotificationType.SiteMessage,
|
||||
title=f"【TR开始任务完成】",
|
||||
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"做种数量: {len(hash_uploading)}\n"
|
||||
f"下载数量: {len(hash_downloading)}\n"
|
||||
f"检查数量: {len(hash_checking)}\n"
|
||||
f"暂停数量: {len(hash_paused)}\n"
|
||||
f"错误数量: {len(hash_error)}\n",
|
||||
)
|
||||
|
||||
@eventmanager.register(EventType.PluginAction)
|
||||
@@ -410,7 +411,6 @@ class TrCommand(_PluginBase):
|
||||
return
|
||||
self.set_limit(self._upload_limit, self._download_limit)
|
||||
|
||||
|
||||
def set_both_limit(self, upload_limit, download_limit):
|
||||
if not self._enable_upload_limit or not self._enable_upload_limit:
|
||||
return True
|
||||
@@ -461,7 +461,6 @@ class TrCommand(_PluginBase):
|
||||
download_limit=int(download_limit), upload_limit=int(upload_limit_current_val)
|
||||
)
|
||||
|
||||
|
||||
def set_limit(self, upload_limit, download_limit):
|
||||
# 限速,满足以下三种情况设置限速
|
||||
# 1. 插件启用 && download_limit启用
|
||||
@@ -481,7 +480,6 @@ class TrCommand(_PluginBase):
|
||||
if flag:
|
||||
logger.info(f"设置TR限速成功")
|
||||
if self._notify:
|
||||
text = "TR设置限速成功\n"
|
||||
if upload_limit == 0:
|
||||
text = f"上传无限速"
|
||||
else:
|
||||
@@ -495,7 +493,7 @@ class TrCommand(_PluginBase):
|
||||
title=f"【TR远程操作】",
|
||||
text=text,
|
||||
)
|
||||
elif flag == False:
|
||||
elif not flag:
|
||||
logger.error(f"TR设置限速失败")
|
||||
if self._notify:
|
||||
self.post_message(
|
||||
|
||||
@@ -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,13 +14,12 @@ 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
|
||||
@@ -212,7 +211,6 @@ class VCBAnimeMonitor(_PluginBase):
|
||||
else:
|
||||
self._dirconf[mon_path] = None
|
||||
|
||||
|
||||
# 转移方式
|
||||
self._transferconf[mon_path] = _transfer_type
|
||||
|
||||
@@ -254,7 +252,6 @@ class VCBAnimeMonitor(_PluginBase):
|
||||
logger.error(f"{mon_path} 启动目录监控失败:{err_msg}")
|
||||
self.systemmessage.put(f"{mon_path} 启动目录监控失败:{err_msg}")
|
||||
|
||||
|
||||
# 运行一次定时服务
|
||||
if self._onlyonce:
|
||||
logger.info("目录监控服务启动,立即运行一次")
|
||||
@@ -298,7 +295,6 @@ class VCBAnimeMonitor(_PluginBase):
|
||||
def __get_data(self, key: str):
|
||||
return self.get_data(key)
|
||||
|
||||
|
||||
def sync_all(self):
|
||||
"""
|
||||
立即运行一次,全量同步目录中所有文件
|
||||
@@ -396,7 +392,7 @@ class VCBAnimeMonitor(_PluginBase):
|
||||
if remeta.is_special and self._switch_ova:
|
||||
logger.info(f"{file_path.name} 为OVA资源,开始处理")
|
||||
if self.get_data(key=f"OVA_{file_meta.title}") is not None:
|
||||
ova_history_ep = int(self.get_data(key=f"OVA_{file_meta.title}"))+1
|
||||
ova_history_ep = int(self.get_data(key=f"OVA_{file_meta.title}")) + 1
|
||||
file_meta.begin_episode = ova_history_ep
|
||||
self.save_data(key=f"OVA_{file_meta.title}", value=ova_history_ep)
|
||||
else:
|
||||
@@ -598,7 +594,6 @@ 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"):
|
||||
@@ -634,7 +629,6 @@ class VCBAnimeMonitor(_PluginBase):
|
||||
except qbittorrentapi.exceptions.APIError as e:
|
||||
logger.error(f"VCB辅助整理模块转移qb文件移动失败:{e}")
|
||||
|
||||
|
||||
def send_msg(self):
|
||||
"""
|
||||
定时检查是否有媒体处理完,发送统一消息
|
||||
|
||||
@@ -2,8 +2,9 @@ import concurrent
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
import threading
|
||||
|
||||
import roman
|
||||
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.tmdb import TmdbChain
|
||||
from app.core.metainfo import MetaInfoPath
|
||||
@@ -11,7 +12,7 @@ from app.log import logger
|
||||
from app.schemas import MediaType
|
||||
|
||||
|
||||
class ReMeta():
|
||||
class ReMeta:
|
||||
# 解析之后的标题:
|
||||
title: str = None
|
||||
# 识别出来的集数
|
||||
@@ -40,7 +41,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),
|
||||
@@ -49,7 +50,7 @@ class ReMeta():
|
||||
# 自定义添加的季度正则表达式
|
||||
_custom_season_patterns = []
|
||||
|
||||
def __init__(self, ova_switch: bool = False,high_performance: bool = False):
|
||||
def __init__(self, ova_switch: bool = False, high_performance: bool = False):
|
||||
self.ova_switch = ova_switch
|
||||
self.high_performance = high_performance
|
||||
|
||||
@@ -62,7 +63,7 @@ class ReMeta():
|
||||
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)
|
||||
@@ -83,12 +84,13 @@ class ReMeta():
|
||||
return meta
|
||||
|
||||
# 分离季度部分和集数部分
|
||||
def split_season_ep(self, pre_title: str):
|
||||
@staticmethod
|
||||
def split_season_ep(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]
|
||||
|
||||
@@ -96,7 +98,7 @@ class ReMeta():
|
||||
if self.high_performance:
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=2) as executor:
|
||||
title_season_result = executor.submit(self.handle_season, title[0])
|
||||
ep_result = executor.submit(self.re_ep, title[1],)
|
||||
ep_result = executor.submit(self.re_ep, title[1], )
|
||||
try:
|
||||
title_season_result = title_season_result.result() # Blocks until the task is complete.
|
||||
ep_result = ep_result.result() # Blocks until the task is complete.
|
||||
@@ -140,7 +142,8 @@ class ReMeta():
|
||||
return title_season
|
||||
|
||||
# 处理存在“Final”字样命名的季度
|
||||
def handle_final_season(self, title: str) -> int | None:
|
||||
@staticmethod
|
||||
def handle_final_season(title: str) -> int | None:
|
||||
medias = MediaChain().search(title=title)[1]
|
||||
if not medias:
|
||||
logger.warn("没有找到对应的媒体信息!")
|
||||
@@ -159,7 +162,7 @@ class ReMeta():
|
||||
logger.info(f"获取到最终季,季度为{len(seasons_info)}")
|
||||
return len(seasons_info)
|
||||
|
||||
def re_ep(self, ep_title: str,) -> dict:
|
||||
def re_ep(self, ep_title: str, ) -> dict:
|
||||
"""
|
||||
# 集数匹配处理模块
|
||||
:param ep_title: 从title解析出的集数,ep_title固定格式[集数]
|
||||
@@ -182,7 +185,3 @@ class ReMeta():
|
||||
ep_ova["ep"] = int(match.group(group))
|
||||
return ep_ova
|
||||
return ep_ova
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user