mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 23:47:41 +08:00
fix actions
This commit is contained in:
@@ -1,7 +1,5 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
from pydantic.main import BaseModel
|
|
||||||
|
|
||||||
from app.chain import ChainBase
|
from app.chain import ChainBase
|
||||||
from app.schemas import ActionContext, ActionParams
|
from app.schemas import ActionContext, ActionParams
|
||||||
|
|
||||||
@@ -10,7 +8,7 @@ class ActionChain(ChainBase):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BaseAction(BaseModel, ABC):
|
class BaseAction(ABC):
|
||||||
"""
|
"""
|
||||||
工作流动作基类
|
工作流动作基类
|
||||||
"""
|
"""
|
||||||
@@ -18,10 +16,6 @@ class BaseAction(BaseModel, ABC):
|
|||||||
# 完成标志
|
# 完成标志
|
||||||
_done_flag = False
|
_done_flag = False
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.chain = ActionChain()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
@@ -32,6 +26,11 @@ class BaseAction(BaseModel, ABC):
|
|||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def data(self) -> dict:
|
||||||
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def execute(self, params: ActionParams, context: ActionContext) -> ActionContext:
|
def execute(self, params: ActionParams, context: ActionContext) -> ActionContext:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ class AddDownloadAction(BaseAction):
|
|||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
return "根据资源列表添加下载任务"
|
return "根据资源列表添加下载任务"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self) -> dict:
|
||||||
|
return AddDownloadParams().dict()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def success(self) -> bool:
|
def success(self) -> bool:
|
||||||
return True if self._added_downloads else False
|
return True if self._added_downloads else False
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ class AddSubscribeAction(BaseAction):
|
|||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
return "根据媒体列表添加订阅"
|
return "根据媒体列表添加订阅"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self) -> dict:
|
||||||
|
return AddSubscribeParams().dict()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def success(self) -> bool:
|
def success(self) -> bool:
|
||||||
return True if self._added_subscribes else False
|
return True if self._added_subscribes else False
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from app.actions import BaseAction
|
from app.actions import BaseAction, ActionChain
|
||||||
from app.schemas import ActionParams, ActionContext
|
from app.schemas import ActionParams, ActionContext
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
|
|
||||||
@@ -17,6 +17,10 @@ class FetchDownloadsAction(BaseAction):
|
|||||||
|
|
||||||
_downloads = []
|
_downloads = []
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.chain = ActionChain()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
return "获取下载任务"
|
return "获取下载任务"
|
||||||
@@ -25,6 +29,10 @@ class FetchDownloadsAction(BaseAction):
|
|||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
return "获取下载任务,更新任务状态"
|
return "获取下载任务,更新任务状态"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self) -> dict:
|
||||||
|
return FetchDownloadsParams().dict()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def success(self) -> bool:
|
def success(self) -> bool:
|
||||||
if not self._downloads:
|
if not self._downloads:
|
||||||
|
|||||||
@@ -89,6 +89,10 @@ class FetchMediasAction(BaseAction):
|
|||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
return "获取媒体数据"
|
return "获取媒体数据"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self) -> dict:
|
||||||
|
return FetchMediasParams().dict()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def success(self) -> bool:
|
def success(self) -> bool:
|
||||||
return True if self.__medias else False
|
return True if self.__medias else False
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from typing import Optional
|
|||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
from app.actions import BaseAction
|
from app.actions import BaseAction, ActionChain
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.core.context import Context
|
from app.core.context import Context
|
||||||
from app.core.metainfo import MetaInfo
|
from app.core.metainfo import MetaInfo
|
||||||
@@ -33,6 +33,7 @@ class FetchRssAction(BaseAction):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.rsshelper = RssHelper()
|
self.rsshelper = RssHelper()
|
||||||
|
self.chain = ActionChain()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
@@ -42,6 +43,10 @@ class FetchRssAction(BaseAction):
|
|||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
return "请求RSS地址获取数据,并解析为资源列表"
|
return "请求RSS地址获取数据,并解析为资源列表"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self) -> dict:
|
||||||
|
return FetchRssParams().dict()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def success(self) -> bool:
|
def success(self) -> bool:
|
||||||
return True if self._rss_torrents else False
|
return True if self._rss_torrents else False
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ class FetchTorrentsAction(BaseAction):
|
|||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
return "根据关键字搜索站点种子资源"
|
return "根据关键字搜索站点种子资源"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self) -> dict:
|
||||||
|
return FetchTorrentsParams().dict()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def success(self) -> bool:
|
def success(self) -> bool:
|
||||||
return True if self._torrents else False
|
return True if self._torrents else False
|
||||||
@@ -55,7 +59,7 @@ class FetchTorrentsAction(BaseAction):
|
|||||||
if params.season and torrent.meta_info.begin_season != params.season:
|
if params.season and torrent.meta_info.begin_season != params.season:
|
||||||
continue
|
continue
|
||||||
# 识别媒体信息
|
# 识别媒体信息
|
||||||
torrent.media_info = self.chain.recognize_media(torrent.meta_info)
|
torrent.media_info = self.searchchain.recognize_media(torrent.meta_info)
|
||||||
if not torrent.media_info:
|
if not torrent.media_info:
|
||||||
logger.warning(f"{torrent.torrent_info.title} 未识别到媒体信息")
|
logger.warning(f"{torrent.torrent_info.title} 未识别到媒体信息")
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ class FilterMediasAction(BaseAction):
|
|||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
return "过滤媒体数据列表"
|
return "过滤媒体数据列表"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self) -> dict:
|
||||||
|
return FilterMediasParams().dict()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def success(self) -> bool:
|
def success(self) -> bool:
|
||||||
return True if self.__medias else False
|
return True if self.__medias else False
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from typing import Optional, List
|
|||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
from app.actions import BaseAction
|
from app.actions import BaseAction, ActionChain
|
||||||
from app.helper.torrent import TorrentHelper
|
from app.helper.torrent import TorrentHelper
|
||||||
from app.schemas import ActionParams, ActionContext
|
from app.schemas import ActionParams, ActionContext
|
||||||
|
|
||||||
@@ -30,6 +30,7 @@ class FilterTorrentsAction(BaseAction):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.torrenthelper = TorrentHelper()
|
self.torrenthelper = TorrentHelper()
|
||||||
|
self.chain = ActionChain()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
@@ -39,6 +40,10 @@ class FilterTorrentsAction(BaseAction):
|
|||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
return "过滤资源数据列表"
|
return "过滤资源数据列表"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self) -> dict:
|
||||||
|
return FilterTorrentsParams().dict()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def success(self) -> bool:
|
def success(self) -> bool:
|
||||||
return self.done
|
return self.done
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from app.actions import BaseAction
|
from app.actions import BaseAction
|
||||||
from app.schemas import ActionParams, ActionContext
|
from app.schemas import ActionParams, ActionContext
|
||||||
from app.chain.media import MediaChain
|
from app.chain.media import MediaChain
|
||||||
@@ -33,6 +35,10 @@ class ScrapeFileAction(BaseAction):
|
|||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
return "刮削媒体信息和图片"
|
return "刮削媒体信息和图片"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self) -> dict:
|
||||||
|
return ScrapeFileParams().dict()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def success(self) -> bool:
|
def success(self) -> bool:
|
||||||
return True if self.__scraped_files else False
|
return True if self.__scraped_files else False
|
||||||
@@ -46,8 +52,8 @@ class ScrapeFileAction(BaseAction):
|
|||||||
continue
|
continue
|
||||||
if not self.storagechain.exists(fileitem):
|
if not self.storagechain.exists(fileitem):
|
||||||
continue
|
continue
|
||||||
meta = MetaInfoPath(fileitem.path)
|
meta = MetaInfoPath(Path(fileitem.path))
|
||||||
mediainfo = self.chain.recognize_media(meta)
|
mediainfo = self.mediachain.recognize_media(meta)
|
||||||
if not mediainfo:
|
if not mediainfo:
|
||||||
logger.info(f"{fileitem.path} 未识别到媒体信息,无法刮削")
|
logger.info(f"{fileitem.path} 未识别到媒体信息,无法刮削")
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ class SendEventAction(BaseAction):
|
|||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
return "发送特定事件"
|
return "发送特定事件"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self) -> dict:
|
||||||
|
return SendEventParams().dict()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def success(self) -> bool:
|
def success(self) -> bool:
|
||||||
return self.__success
|
return self.__success
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from typing import List, Optional, Union
|
|||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
from app.actions import BaseAction
|
from app.actions import BaseAction, ActionChain
|
||||||
from app.schemas import ActionParams, ActionContext, MessageChannel
|
from app.schemas import ActionParams, ActionContext, MessageChannel
|
||||||
|
|
||||||
|
|
||||||
@@ -19,6 +19,10 @@ class SendMessageAction(BaseAction):
|
|||||||
发送消息
|
发送消息
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.chain = ActionChain()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
return "发送消息"
|
return "发送消息"
|
||||||
@@ -27,6 +31,10 @@ class SendMessageAction(BaseAction):
|
|||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
return "发送特定消息"
|
return "发送特定消息"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self) -> dict:
|
||||||
|
return SendMessageParams().dict()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def success(self) -> bool:
|
def success(self) -> bool:
|
||||||
return self.done
|
return self.done
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from app.actions import BaseAction
|
from app.actions import BaseAction
|
||||||
from app.schemas import ActionParams, ActionContext
|
from app.schemas import ActionParams, ActionContext
|
||||||
from app.chain.storage import StorageChain
|
from app.chain.storage import StorageChain
|
||||||
@@ -32,6 +34,10 @@ class TransferFileAction(BaseAction):
|
|||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
return "整理和转移文件"
|
return "整理和转移文件"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self) -> dict:
|
||||||
|
return TransferFileParams().dict()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def success(self) -> bool:
|
def success(self) -> bool:
|
||||||
return True if self.__fileitems else False
|
return True if self.__fileitems else False
|
||||||
@@ -44,7 +50,7 @@ class TransferFileAction(BaseAction):
|
|||||||
if not download.completed:
|
if not download.completed:
|
||||||
logger.info(f"下载任务 {download.download_id} 未完成")
|
logger.info(f"下载任务 {download.download_id} 未完成")
|
||||||
continue
|
continue
|
||||||
fileitem = self.storagechain.get_file_item(storage="local", path=download.path)
|
fileitem = self.storagechain.get_file_item(storage="local", path=Path(download.path))
|
||||||
if not fileitem:
|
if not fileitem:
|
||||||
logger.info(f"文件 {download.path} 不存在")
|
logger.info(f"文件 {download.path} 不存在")
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from fastapi import APIRouter, Depends
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
|
from app.core.workflow import WorkFlowManager
|
||||||
from app.db import get_db
|
from app.db import get_db
|
||||||
from app.db.models.workflow import Workflow
|
from app.db.models.workflow import Workflow
|
||||||
from app.db.user_oper import get_current_active_user
|
from app.db.user_oper import get_current_active_user
|
||||||
@@ -40,6 +41,14 @@ def create_workflow(workflow: schemas.Workflow,
|
|||||||
return schemas.Response(success=True, message="创建工作流成功")
|
return schemas.Response(success=True, message="创建工作流成功")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/actions", summary="所有动作", response_model=List[dict])
|
||||||
|
def list_actions(_: schemas.TokenPayload = Depends(get_current_active_user)) -> Any:
|
||||||
|
"""
|
||||||
|
获取所有动作
|
||||||
|
"""
|
||||||
|
return WorkFlowManager().list_actions()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{workflow_id}", summary="工作流详情", response_model=schemas.Workflow)
|
@router.get("/{workflow_id}", summary="工作流详情", response_model=schemas.Workflow)
|
||||||
def get_workflow(workflow_id: int,
|
def get_workflow(workflow_id: int,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
|
|||||||
+27
-8
@@ -1,5 +1,5 @@
|
|||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Dict, Any, Tuple
|
from typing import Dict, Any, Tuple, List
|
||||||
|
|
||||||
from app.helper.module import ModuleHelper
|
from app.helper.module import ModuleHelper
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
@@ -43,7 +43,10 @@ class WorkFlowManager(metaclass=Singleton):
|
|||||||
)
|
)
|
||||||
for action in actions:
|
for action in actions:
|
||||||
logger.debug(f"加载动作: {action.__name__}")
|
logger.debug(f"加载动作: {action.__name__}")
|
||||||
self._actions[action.__name__] = action
|
try:
|
||||||
|
self._actions[action.__name__] = action()
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"加载动作失败: {action.__name__} - {err}")
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""
|
"""
|
||||||
@@ -59,22 +62,22 @@ class WorkFlowManager(metaclass=Singleton):
|
|||||||
context = ActionContext()
|
context = ActionContext()
|
||||||
if action.type in self._actions:
|
if action.type in self._actions:
|
||||||
# 实例化
|
# 实例化
|
||||||
action_obj = self._actions[action.type]()
|
action_obj = self._actions[action.type]
|
||||||
# 执行
|
# 执行
|
||||||
logger.info(f"执行动作: {action.id} - {action.name}")
|
logger.info(f"执行动作: {action.id} - {action.name}")
|
||||||
result_context = action_obj.execute(action.params, context)
|
result_context = action_obj.execute(action.data, context)
|
||||||
if action_obj.success:
|
if action_obj.success:
|
||||||
logger.info(f"{action.name} 执行成功")
|
logger.info(f"{action.name} 执行成功")
|
||||||
else:
|
else:
|
||||||
logger.error(f"{action.name} 执行失败")
|
logger.error(f"{action.name} 执行失败")
|
||||||
if action.loop and action.loop_interval:
|
if action.data.loop and action.data.loop_interval:
|
||||||
while not action_obj.done:
|
while not action_obj.done:
|
||||||
# 等待
|
# 等待
|
||||||
logger.info(f"{action.name} 等待 {action.loop_interval} 秒后继续执行 ...")
|
logger.info(f"{action.name} 等待 {action.data.loop_interval} 秒后继续执行 ...")
|
||||||
sleep(action.loop_interval)
|
sleep(action.data.loop_interval)
|
||||||
# 执行
|
# 执行
|
||||||
logger.info(f"继续执行动作: {action.id} - {action.name}")
|
logger.info(f"继续执行动作: {action.id} - {action.name}")
|
||||||
result_context = action_obj.execute(action.params, result_context)
|
result_context = action_obj.execute(action.data, result_context)
|
||||||
if action_obj.success:
|
if action_obj.success:
|
||||||
logger.info(f"{action.name} 执行成功")
|
logger.info(f"{action.name} 执行成功")
|
||||||
else:
|
else:
|
||||||
@@ -84,3 +87,19 @@ class WorkFlowManager(metaclass=Singleton):
|
|||||||
else:
|
else:
|
||||||
logger.error(f"未找到动作: {action.type} - {action.name}")
|
logger.error(f"未找到动作: {action.type} - {action.name}")
|
||||||
return False, context
|
return False, context
|
||||||
|
|
||||||
|
def list_actions(self) -> List[dict]:
|
||||||
|
"""
|
||||||
|
获取所有动作
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"type": key,
|
||||||
|
"name": action.name,
|
||||||
|
"description": action.description,
|
||||||
|
"data": {
|
||||||
|
"label": action.name,
|
||||||
|
**action.data
|
||||||
|
}
|
||||||
|
} for key, action in self._actions.items()
|
||||||
|
]
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ class ActionParams(BaseModel):
|
|||||||
"""
|
"""
|
||||||
动作基础参数
|
动作基础参数
|
||||||
"""
|
"""
|
||||||
pass
|
loop: Optional[bool] = Field(False, description="是否需要循环")
|
||||||
|
loop_interval: Optional[int] = Field(0, description="循环间隔 (秒)")
|
||||||
|
|
||||||
|
|
||||||
class Action(BaseModel):
|
class Action(BaseModel):
|
||||||
@@ -47,10 +48,7 @@ class Action(BaseModel):
|
|||||||
type: Optional[str] = Field(None, description="动作类型 (类名)")
|
type: Optional[str] = Field(None, description="动作类型 (类名)")
|
||||||
name: Optional[str] = Field(None, description="动作名称")
|
name: Optional[str] = Field(None, description="动作名称")
|
||||||
description: Optional[str] = Field(None, description="动作描述")
|
description: Optional[str] = Field(None, description="动作描述")
|
||||||
loop: Optional[bool] = Field(False, description="是否需要循环")
|
data: Optional[ActionParams] = Field({}, description="参数")
|
||||||
loop_interval: Optional[int] = Field(0, description="循环间隔 (秒)")
|
|
||||||
params: Optional[ActionParams] = Field({}, description="参数")
|
|
||||||
label: Optional[str] = Field(None, description="标签")
|
|
||||||
position: Optional[dict] = Field({}, description="位置")
|
position: Optional[dict] = Field({}, description="位置")
|
||||||
|
|
||||||
|
|
||||||
@@ -76,4 +74,4 @@ class ActionFlow(BaseModel):
|
|||||||
id: Optional[str] = Field(None, description="流程ID")
|
id: Optional[str] = Field(None, description="流程ID")
|
||||||
source: Optional[str] = Field(None, description="源动作")
|
source: Optional[str] = Field(None, description="源动作")
|
||||||
target: Optional[str] = Field(None, description="目标动作")
|
target: Optional[str] = Field(None, description="目标动作")
|
||||||
animated: Optional[bool] = Field(False, description="是否动画流程")
|
animated: Optional[bool] = Field(True, description="是否动画流程")
|
||||||
|
|||||||
Reference in New Issue
Block a user