mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-06-14 07:26:50 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb4981adb3 | ||
|
|
6880b42a84 | ||
|
|
97054adc61 | ||
|
|
de94e5d595 | ||
|
|
a5a734d091 | ||
|
|
efb607d22f | ||
|
|
d0b2787a7c | ||
|
|
d5988ff443 | ||
|
|
96b4f1b575 | ||
|
|
bb6b8439c7 | ||
|
|
9cdce4509d | ||
|
|
3956ab1fe8 | ||
|
|
14686fdb03 | ||
|
|
32892ab747 | ||
|
|
79c637e003 |
@@ -331,6 +331,29 @@ def read_rss_sites(db: Session = Depends(get_db),
|
||||
return rss_sites
|
||||
|
||||
|
||||
@router.get("/auth", summary="查询认证站点", response_model=dict)
|
||||
def read_auth_sites(_: schemas.TokenPayload = Depends(verify_token)) -> dict:
|
||||
"""
|
||||
获取可认证站点列表
|
||||
"""
|
||||
return SitesHelper().get_authsites()
|
||||
|
||||
|
||||
@router.post("/auth", summary="用户站点认证", response_model=schemas.Response)
|
||||
def auth_site(
|
||||
auth_info: schemas.SiteAuth,
|
||||
_: User = Depends(get_current_active_superuser)
|
||||
) -> Any:
|
||||
"""
|
||||
用户站点认证
|
||||
"""
|
||||
if not auth_info or not auth_info.site or not auth_info.params:
|
||||
return schemas.Response(success=False, message="请输入认证站点和认证参数")
|
||||
status, msg = SitesHelper().check_user(auth_info.site, auth_info.params)
|
||||
SystemConfigOper().set(SystemConfigKey.UserSiteAuthParams, auth_info.dict())
|
||||
return schemas.Response(success=status, message=msg)
|
||||
|
||||
|
||||
@router.get("/{site_id}", summary="站点详情", response_model=schemas.Site)
|
||||
def read_site(
|
||||
site_id: int,
|
||||
|
||||
@@ -180,7 +180,7 @@ class DownloadChain(ChainBase):
|
||||
torrent_file, content, download_folder, files, error_msg = self.torrent.download_torrent(
|
||||
url=torrent_url,
|
||||
cookie=site_cookie,
|
||||
ua=torrent.site_ua,
|
||||
ua=torrent.site_ua or settings.USER_AGENT,
|
||||
proxy=torrent.site_proxy)
|
||||
|
||||
if isinstance(content, str):
|
||||
|
||||
@@ -365,10 +365,6 @@ class SubscribeChain(ChainBase):
|
||||
torrent_info = context.torrent_info
|
||||
torrent_mediainfo = context.media_info
|
||||
|
||||
# 匹配订阅附加参数
|
||||
if not self.torrenthelper.filter_torrent(torrent_info=torrent_info,
|
||||
filter_params=self.get_params(subscribe)):
|
||||
continue
|
||||
# 洗版
|
||||
if subscribe.best_version:
|
||||
# 洗版时,非整季不要
|
||||
|
||||
@@ -389,14 +389,13 @@ class TransferChain(ChainBase):
|
||||
download_hash = download_file.download_hash
|
||||
|
||||
# 查询整理目标目录
|
||||
if not target_directory:
|
||||
if target_path:
|
||||
target_directory = self.directoryhelper.get_dir(file_mediainfo,
|
||||
storage=target_storage, dest_path=target_path)
|
||||
elif src_match:
|
||||
if not target_directory and not target_path:
|
||||
if src_match:
|
||||
# 按源目录匹配,以便找到更合适的目录配置
|
||||
target_directory = self.directoryhelper.get_dir(file_mediainfo,
|
||||
storage=file_item.storage, src_path=file_path)
|
||||
else:
|
||||
# 未指定目标路径,根据媒体信息获取目标目录
|
||||
target_directory = self.directoryhelper.get_dir(file_mediainfo)
|
||||
|
||||
# 执行整理
|
||||
|
||||
@@ -84,6 +84,7 @@ class EventManager(metaclass=Singleton):
|
||||
self.__disabled_handlers = set() # 禁用的事件处理器集合
|
||||
self.__disabled_classes = set() # 禁用的事件处理器类集合
|
||||
self.__lock = threading.Lock() # 线程锁
|
||||
self.__processing_events = {} # 用于记录当前正在处理的事件 {event_hash: event}
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
@@ -129,6 +130,14 @@ class EventManager(metaclass=Singleton):
|
||||
for handler in handlers.values()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def __get_event_hash(event: Event) -> str:
|
||||
"""
|
||||
计算事件的唯一标识符(hash)
|
||||
"""
|
||||
data_string = str(event.event_type.value) + str(event.event_data)
|
||||
return str(uuid.uuid5(uuid.NAMESPACE_DNS, data_string))
|
||||
|
||||
def send_event(self, etype: Union[EventType, ChainEventType], data: Optional[Union[Dict, ChainEventData]] = None,
|
||||
priority: int = DEFAULT_EVENT_PRIORITY) -> Optional[Event]:
|
||||
"""
|
||||
@@ -139,6 +148,12 @@ class EventManager(metaclass=Singleton):
|
||||
:return: 如果是链式事件,返回处理后的事件数据;否则返回 None
|
||||
"""
|
||||
event = Event(etype, data, priority)
|
||||
event_hash = self.__get_event_hash(event)
|
||||
with self.__lock:
|
||||
if event_hash in self.__processing_events:
|
||||
logger.debug(f"Duplicate event ignored: {event}")
|
||||
return None
|
||||
self.__processing_events[event_hash] = event
|
||||
if isinstance(etype, EventType):
|
||||
self.__trigger_broadcast_event(event)
|
||||
elif isinstance(etype, ChainEventType):
|
||||
@@ -320,9 +335,14 @@ class EventManager(metaclass=Singleton):
|
||||
"""
|
||||
触发链式事件,按顺序调用订阅的处理器,并记录处理耗时
|
||||
"""
|
||||
logger.debug(f"Triggering synchronous chain event: {event}")
|
||||
dispatch = self.__dispatch_chain_event(event)
|
||||
return event if dispatch else None
|
||||
try:
|
||||
logger.debug(f"Triggering synchronous chain event: {event}")
|
||||
dispatch = self.__dispatch_chain_event(event)
|
||||
return event if dispatch else None
|
||||
finally:
|
||||
event_hash = self.__get_event_hash(event)
|
||||
with self.__lock:
|
||||
self.__processing_events.pop(event_hash, None)
|
||||
|
||||
def __trigger_broadcast_event(self, event: Event):
|
||||
"""
|
||||
@@ -363,6 +383,9 @@ class EventManager(metaclass=Singleton):
|
||||
return
|
||||
for handler_id, handler in handlers.items():
|
||||
self.__executor.submit(self.__safe_invoke_handler, handler, event)
|
||||
event_hash = self.__get_event_hash(event)
|
||||
with self.__lock:
|
||||
self.__processing_events.pop(event_hash, None)
|
||||
|
||||
def __safe_invoke_handler(self, handler: Callable, event: Event):
|
||||
"""
|
||||
|
||||
@@ -524,16 +524,7 @@ class MetaVideo(MetaBase):
|
||||
"""
|
||||
if not self.name:
|
||||
return
|
||||
source_res = re.search(r"(%s)" % self._source_re, token, re.IGNORECASE)
|
||||
if source_res:
|
||||
self._last_token_type = "source"
|
||||
self._continue_flag = False
|
||||
self._stop_name_flag = True
|
||||
if not self._source:
|
||||
self._source = source_res.group(1)
|
||||
self._last_token = self._source.upper()
|
||||
return
|
||||
elif token.upper() == "DL" \
|
||||
if token.upper() == "DL" \
|
||||
and self._last_token_type == "source" \
|
||||
and self._last_token == "WEB":
|
||||
self._source = "WEB-DL"
|
||||
@@ -553,7 +544,7 @@ class MetaVideo(MetaBase):
|
||||
self._source = "WEB-DL"
|
||||
self._continue_flag = False
|
||||
return
|
||||
# UHD REMUX组合
|
||||
# UHD REMUX组合
|
||||
if token.upper() == "REMUX" \
|
||||
and self._source == "BluRay":
|
||||
self._source = "BluRay REMUX"
|
||||
@@ -562,7 +553,17 @@ class MetaVideo(MetaBase):
|
||||
elif token.upper() == "BLURAY" \
|
||||
and self._source == "UHD":
|
||||
self._source = "UHD BluRay"
|
||||
|
||||
self._continue_flag = False
|
||||
return
|
||||
source_res = re.search(r"(%s)" % self._source_re, token, re.IGNORECASE)
|
||||
if source_res:
|
||||
self._last_token_type = "source"
|
||||
self._continue_flag = False
|
||||
self._stop_name_flag = True
|
||||
if not self._source:
|
||||
self._source = source_res.group(1)
|
||||
self._last_token = self._source.upper()
|
||||
return
|
||||
effect_res = re.search(r"(%s)" % self._effect_re, token, re.IGNORECASE)
|
||||
if effect_res:
|
||||
self._last_token_type = "effect"
|
||||
|
||||
@@ -46,7 +46,7 @@ class Site(Base):
|
||||
# 流控间隔
|
||||
limit_seconds = Column(Integer, default=0)
|
||||
# 超时时间
|
||||
timeout = Column(Integer, default=0)
|
||||
timeout = Column(Integer, default=15)
|
||||
# 是否启用
|
||||
is_active = Column(Boolean(), default=True)
|
||||
# 创建时间
|
||||
|
||||
@@ -213,7 +213,7 @@ class Alist(StorageBase):
|
||||
path=(Path(fileitem.path) / item["name"]).as_posix() + ("/" if item["is_dir"] else ""),
|
||||
name=item["name"],
|
||||
basename=Path(item["name"]).stem,
|
||||
extension=Path(item["name"]).suffix if not item["is_dir"] else None,
|
||||
extension=Path(item["name"]).suffix[1:] if not item["is_dir"] else None,
|
||||
size=item["size"] if not item["is_dir"] else None,
|
||||
modify_time=self.__parse_timestamp(item["modified"]),
|
||||
thumbnail=item["thumb"],
|
||||
@@ -351,7 +351,7 @@ class Alist(StorageBase):
|
||||
path=path.as_posix() + ("/" if result["data"]["is_dir"] else ""),
|
||||
name=result["data"]["name"],
|
||||
basename=Path(result["data"]["name"]).stem,
|
||||
extension=Path(result["data"]["name"]).suffix,
|
||||
extension=Path(result["data"]["name"]).suffix[1:],
|
||||
size=result["data"]["size"],
|
||||
modify_time=self.__parse_timestamp(result["data"]["modified"]),
|
||||
thumbnail=result["data"]["thumb"],
|
||||
@@ -521,13 +521,15 @@ class Alist(StorageBase):
|
||||
).get_res(download_url)
|
||||
|
||||
if not path:
|
||||
path = settings.TEMP_PATH / fileitem.name
|
||||
new_path = settings.TEMP_PATH / fileitem.name
|
||||
else:
|
||||
new_path = path / fileitem.name
|
||||
|
||||
with open(path, "wb") as f:
|
||||
with open(new_path, "wb") as f:
|
||||
f.write(resp.content)
|
||||
|
||||
if path.exists():
|
||||
return path
|
||||
if new_path.exists():
|
||||
return new_path
|
||||
return None
|
||||
|
||||
def upload(
|
||||
|
||||
@@ -21,7 +21,8 @@ class U115Pan(StorageBase, metaclass=Singleton):
|
||||
|
||||
# 支持的整理方式
|
||||
transtype = {
|
||||
"move": "移动"
|
||||
"move": "移动",
|
||||
"copy": "复制"
|
||||
}
|
||||
|
||||
client: P115Client = None
|
||||
@@ -34,7 +35,8 @@ class U115Pan(StorageBase, metaclass=Singleton):
|
||||
"""
|
||||
try:
|
||||
if not self.client or not self.client.cookies or force:
|
||||
self.client = P115Client(self.__credential)
|
||||
self.client = P115Client(self.__credential,
|
||||
check_for_relogin=True, app="alipaymini", console_qrcode=False)
|
||||
self.fs = P115FileSystem(self.client)
|
||||
except Exception as err:
|
||||
logger.error(f"115连接失败,请重新扫码登录:{str(err)}")
|
||||
|
||||
@@ -19,10 +19,11 @@ from app.chain.transfer import TransferChain
|
||||
from app.core.config import settings
|
||||
from app.core.event import EventManager
|
||||
from app.core.plugin import PluginManager
|
||||
from app.db.systemconfig_oper import SystemConfigOper
|
||||
from app.helper.sites import SitesHelper
|
||||
from app.log import logger
|
||||
from app.schemas import Notification, NotificationType
|
||||
from app.schemas.types import EventType
|
||||
from app.schemas.types import EventType, SystemConfigKey
|
||||
from app.utils.singleton import Singleton
|
||||
from app.utils.timer import TimerUtils
|
||||
|
||||
@@ -74,8 +75,12 @@ class Scheduler(metaclass=Singleton):
|
||||
message="用户认证失败次数过多,将不再尝试认证!",
|
||||
role="system")
|
||||
return
|
||||
logger.info("用户未认证,正在尝试重新认证...")
|
||||
status, msg = SitesHelper().check_user()
|
||||
logger.info("用户未认证,正在尝试认证...")
|
||||
auth_conf = SystemConfigOper().get(SystemConfigKey.UserSiteAuthParams)
|
||||
if auth_conf:
|
||||
status, msg = SitesHelper().check_user(**auth_conf)
|
||||
else:
|
||||
status, msg = SitesHelper().check_user()
|
||||
if status:
|
||||
self._auth_count = 0
|
||||
logger.info(f"{msg} 用户认证成功")
|
||||
@@ -169,6 +174,9 @@ class Scheduler(metaclass=Singleton):
|
||||
# 停止定时服务
|
||||
self.stop()
|
||||
|
||||
# 用户认证立即执行一次
|
||||
user_auth()
|
||||
|
||||
# 调试模式不启动定时服务
|
||||
if settings.DEV:
|
||||
return
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Optional, Any, Union
|
||||
from typing import Optional, Any, Union, Dict
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -35,7 +35,7 @@ class Site(BaseModel):
|
||||
# 备注
|
||||
note: Optional[Any] = None
|
||||
# 超时时间
|
||||
timeout: Optional[int] = 0
|
||||
timeout: Optional[int] = 15
|
||||
# 流控单位周期
|
||||
limit_interval: Optional[int] = None
|
||||
# 流控次数
|
||||
@@ -110,3 +110,8 @@ class SiteUserData(BaseModel):
|
||||
updated_day: Optional[str] = None
|
||||
# 更新时间
|
||||
updated_time: Optional[str] = None
|
||||
|
||||
|
||||
class SiteAuth(BaseModel):
|
||||
site: Optional[str] = None
|
||||
params: Optional[Dict[str, Union[int, str]]] = {}
|
||||
|
||||
@@ -122,6 +122,8 @@ class SystemConfigKey(Enum):
|
||||
DefaultMovieSubscribeConfig = "DefaultMovieSubscribeConfig"
|
||||
# 默认电视剧订阅规则
|
||||
DefaultTvSubscribeConfig = "DefaultTvSubscribeConfig"
|
||||
# 用户站点认证参数
|
||||
UserSiteAuthParams = "UserSiteAuthParams"
|
||||
|
||||
|
||||
# 处理进度Key字典
|
||||
|
||||
@@ -345,13 +345,13 @@ meta_cases = [{
|
||||
"part": "",
|
||||
"season": "",
|
||||
"episode": "",
|
||||
"restype": "BluRay Remux",
|
||||
"restype": "BluRay REMUX",
|
||||
"pix": "1080p",
|
||||
"video_codec": "AVC",
|
||||
"audio_codec": "LPCM 7³"
|
||||
}
|
||||
}, {
|
||||
"title": "30.Rock.S02E01.1080p.BluRay.X264-BORDURE.mkv",
|
||||
"title": "30.Rock.S02E01.1080p.UHD.BluRay.X264-BORDURE.mkv",
|
||||
"subtitle": "",
|
||||
"target": {
|
||||
"type": "电视剧",
|
||||
@@ -361,7 +361,7 @@ meta_cases = [{
|
||||
"part": "",
|
||||
"season": "S02",
|
||||
"episode": "E01",
|
||||
"restype": "BluRay",
|
||||
"restype": "UHD BluRay",
|
||||
"pix": "1080p",
|
||||
"video_codec": "X264",
|
||||
"audio_codec": ""
|
||||
@@ -611,7 +611,7 @@ meta_cases = [{
|
||||
"subtitle": "",
|
||||
"target": {
|
||||
"type": "电视剧",
|
||||
"cn_name": "处刑少女的生存之道",
|
||||
"cn_name": "處刑少女的生存之道",
|
||||
"en_name": "",
|
||||
"year": "",
|
||||
"part": "",
|
||||
@@ -665,7 +665,7 @@ meta_cases = [{
|
||||
"part": "",
|
||||
"season": "",
|
||||
"episode": "",
|
||||
"restype": "BluRay DoVi UHD",
|
||||
"restype": "UHD BluRay DoVi",
|
||||
"pix": "1080p",
|
||||
"video_codec": "X265",
|
||||
"audio_codec": "DD 7.1"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
APP_VERSION = 'v2.0.6'
|
||||
FRONTEND_VERSION = 'v2.0.6'
|
||||
APP_VERSION = 'v2.0.7'
|
||||
FRONTEND_VERSION = 'v2.0.7'
|
||||
|
||||
Reference in New Issue
Block a user