fix 远程交互命令

This commit is contained in:
jxxghp
2024-10-20 01:54:24 +08:00
parent c650f1b5e3
commit 08e07625cd
8 changed files with 63 additions and 51 deletions

288
app/chain/command.py Normal file
View File

@@ -0,0 +1,288 @@
import traceback
from typing import Any, Union, Dict
from app.chain import ChainBase
from app.chain.download import DownloadChain
from app.chain.site import SiteChain
from app.chain.subscribe import SubscribeChain
from app.chain.system import SystemChain
from app.chain.transfer import TransferChain
from app.core.config import settings
from app.core.event import Event as ManagerEvent, eventmanager
from app.core.plugin import PluginManager
from app.helper.message import MessageHelper
from app.log import logger
from app.scheduler import Scheduler
from app.schemas import Notification
from app.schemas.types import EventType, MessageChannel
from app.utils.object import ObjectUtils
from app.utils.singleton import Singleton
class CommandChain(ChainBase, metaclass=Singleton):
"""
全局命令管理,消费事件
"""
# 内建命令
_commands = {}
def __init__(self):
# 插件管理器
super().__init__()
self.pluginmanager = PluginManager()
# 定时服务管理
self.scheduler = Scheduler()
# 消息管理器
self.messagehelper = MessageHelper()
# 内置命令:标准参数 arg_str: str, channel: MessageChannel, userid: Union[str, int] = None, source: str = None
# 其中 arg_str 为用户输入的参数channel 为消息渠道userid 为用户IDsource 为消息来源arg_str 可选
self._commands = {
"/cookiecloud": {
"id": "cookiecloud",
"type": "scheduler",
"description": "同步站点",
"category": "站点"
},
"/sites": {
"func": SiteChain().remote_list,
"description": "查询站点",
"category": "站点",
"data": {}
},
"/site_cookie": {
"func": SiteChain().remote_cookie,
"description": "更新站点Cookie",
"data": {}
},
"/site_enable": {
"func": SiteChain().remote_enable,
"description": "启用站点",
"data": {}
},
"/site_disable": {
"func": SiteChain().remote_disable,
"description": "禁用站点",
"data": {}
},
"/mediaserver_sync": {
"id": "mediaserver_sync",
"type": "scheduler",
"description": "同步媒体服务器",
"category": "管理"
},
"/subscribes": {
"func": SubscribeChain().remote_list,
"description": "查询订阅",
"category": "订阅",
"data": {}
},
"/subscribe_refresh": {
"id": "subscribe_refresh",
"type": "scheduler",
"description": "刷新订阅",
"category": "订阅"
},
"/subscribe_search": {
"id": "subscribe_search",
"type": "scheduler",
"description": "搜索订阅",
"category": "订阅"
},
"/subscribe_delete": {
"func": SubscribeChain().remote_delete,
"description": "删除订阅",
"data": {}
},
"/subscribe_tmdb": {
"id": "subscribe_tmdb",
"type": "scheduler",
"description": "订阅元数据更新"
},
"/downloading": {
"func": DownloadChain().remote_downloading,
"description": "正在下载",
"category": "管理",
"data": {}
},
"/transfer": {
"id": "transfer",
"type": "scheduler",
"description": "下载文件整理",
"category": "管理"
},
"/redo": {
"func": TransferChain().remote_transfer,
"description": "手动整理",
"data": {}
},
"/clear_cache": {
"func": SystemChain().remote_clear_cache,
"description": "清理缓存",
"category": "管理",
"data": {}
},
"/restart": {
"func": SystemChain().restart,
"description": "重启系统",
"category": "管理",
"data": {}
},
"/version": {
"func": SystemChain().version,
"description": "当前版本",
"category": "管理",
"data": {}
}
}
# 汇总插件命令
plugin_commands = self.pluginmanager.get_plugin_commands()
for command in plugin_commands:
self.register(
cmd=command.get('cmd'),
func=self.send_plugin_event,
desc=command.get('desc'),
category=command.get('category'),
data={
'etype': command.get('event'),
'data': command.get('data')
}
)
# 广播注册命令菜单
if not settings.DEV:
self.register_commands(commands=self.get_commands())
def __run_command(self, command: Dict[str, any], data_str: str = "",
channel: MessageChannel = None, source: str = None, userid: Union[str, int] = None):
"""
运行定时服务
"""
if command.get("type") == "scheduler":
# 定时服务
if userid:
self.post_message(
Notification(
channel=channel,
source=source,
title=f"开始执行 {command.get('description')} ...",
userid=userid
)
)
# 执行定时任务
self.scheduler.start(job_id=command.get("id"))
if userid:
self.post_message(
Notification(
channel=channel,
source=source,
title=f"{command.get('description')} 执行完成",
userid=userid
)
)
else:
# 命令
cmd_data = command['data'] if command.get('data') else {}
args_num = ObjectUtils.arguments(command['func'])
if args_num > 0:
if cmd_data:
# 有内置参数直接使用内置参数
data = cmd_data.get("data") or {}
data['channel'] = channel
data['source'] = source
data['user'] = userid
if data_str:
data['arg_str'] = data_str
cmd_data['data'] = data
command['func'](**cmd_data)
elif args_num == 3:
# 没有输入参数只输入渠道来源、用户ID和消息来源
command['func'](channel, userid, source)
elif args_num > 3:
# 多个输入参数用户输入、用户ID
command['func'](data_str, channel, userid, source)
else:
# 没有参数
command['func']()
def get_commands(self):
"""
获取命令列表
"""
return self._commands
def register(self, cmd: str, func: Any, data: dict = None,
desc: str = None, category: str = None) -> None:
"""
注册命令
"""
self._commands[cmd] = {
"func": func,
"description": desc,
"category": category,
"data": data or {}
}
def get(self, cmd: str) -> Any:
"""
获取命令
"""
return self._commands.get(cmd, {})
def execute(self, cmd: str, data_str: str = "",
channel: MessageChannel = None, source: str = None,
userid: Union[str, int] = None) -> None:
"""
执行命令
"""
command = self.get(cmd)
if command:
try:
if userid:
logger.info(f"用户 {userid} 开始执行:{command.get('description')} ...")
else:
logger.info(f"开始执行:{command.get('description')} ...")
# 执行命令
self.__run_command(command, data_str=data_str,
channel=channel, source=source, userid=userid)
if userid:
logger.info(f"用户 {userid} {command.get('description')} 执行完成")
else:
logger.info(f"{command.get('description')} 执行完成")
except Exception as err:
logger.error(f"执行命令 {cmd} 出错:{str(err)} - {traceback.format_exc()}")
self.messagehelper.put(title=f"执行命令 {cmd} 出错",
message=str(err),
role="system")
@staticmethod
def send_plugin_event(etype: EventType, data: dict) -> None:
"""
发送插件命令
"""
eventmanager.send_event(etype, data)
@eventmanager.register(EventType.CommandExcute)
def command_event(self, event: ManagerEvent) -> None:
"""
注册命令执行事件
event_data: {
"cmd": "/xxx args"
}
"""
# 命令参数
event_str = event.event_data.get('cmd')
# 消息渠道
event_channel = event.event_data.get('channel')
# 消息来源
event_source = event.event_data.get('source')
# 消息用户
event_user = event.event_data.get('user')
if event_str:
cmd = event_str.split()[0]
args = " ".join(event_str.split()[1:])
if self.get(cmd):
self.execute(cmd=cmd, data_str=args,
channel=event_channel, source=event_source, userid=event_user)

View File

@@ -874,7 +874,7 @@ class DownloadChain(ChainBase):
# 全部存在
return True, no_exists
def remote_downloading(self, channel: MessageChannel, source: str, userid: Union[str, int] = None):
def remote_downloading(self, channel: MessageChannel, userid: Union[str, int] = None, source: str = None):
"""
查询正在下载的任务,并发送消息
"""

View File

@@ -504,7 +504,8 @@ class SiteChain(ChainBase):
return False, f"无法打开网站!"
return True, "连接成功"
def remote_list(self, channel: MessageChannel, userid: Union[str, int] = None):
def remote_list(self, channel: MessageChannel,
userid: Union[str, int] = None, source: str = None):
"""
查询所有站点,发送消息
"""
@@ -532,10 +533,13 @@ class SiteChain(ChainBase):
# 发送列表
self.post_message(Notification(
channel=channel,
source=source,
title=title, text="\n".join(messages), userid=userid,
link=settings.MP_DOMAIN('#/site')))
link=settings.MP_DOMAIN('#/site'))
)
def remote_disable(self, arg_str, channel: MessageChannel, userid: Union[str, int] = None):
def remote_disable(self, arg_str: str, channel: MessageChannel,
userid: Union[str, int] = None, source: str = None):
"""
禁用站点
"""
@@ -557,9 +561,10 @@ class SiteChain(ChainBase):
"is_active": False
})
# 重新发送消息
self.remote_list(channel, userid)
self.remote_list(channel=channel, userid=userid, source=source)
def remote_enable(self, arg_str, channel: MessageChannel, userid: Union[str, int] = None):
def remote_enable(self, arg_str: str, channel: MessageChannel,
userid: Union[str, int] = None, source: str = None):
"""
启用站点
"""
@@ -582,7 +587,7 @@ class SiteChain(ChainBase):
"is_active": True
})
# 重新发送消息
self.remote_list(channel, userid)
self.remote_list(channel=channel, userid=userid, source=source)
def update_cookie(self, site_info: Site,
username: str, password: str, two_step_code: str = None) -> Tuple[bool, str]:
@@ -613,7 +618,8 @@ class SiteChain(ChainBase):
return True, msg
return False, "未知错误"
def remote_cookie(self, arg_str: str, channel: MessageChannel, userid: Union[str, int] = None):
def remote_cookie(self, arg_str: str, channel: MessageChannel,
userid: Union[str, int] = None, source: str = None):
"""
使用用户名密码更新站点Cookie
"""
@@ -622,6 +628,7 @@ class SiteChain(ChainBase):
if not arg_str:
self.post_message(Notification(
channel=channel,
source=source,
title=err_title, userid=userid))
return
arg_str = str(arg_str).strip()
@@ -633,12 +640,14 @@ class SiteChain(ChainBase):
elif len(args) != 3:
self.post_message(Notification(
channel=channel,
source=source,
title=err_title, userid=userid))
return
site_id = args[0]
if not site_id.isdigit():
self.post_message(Notification(
channel=channel,
source=source,
title=err_title, userid=userid))
return
# 站点ID
@@ -648,10 +657,12 @@ class SiteChain(ChainBase):
if not site_info:
self.post_message(Notification(
channel=channel,
source=source,
title=f"站点编号 {site_id} 不存在!", userid=userid))
return
self.post_message(Notification(
channel=channel,
source=source,
title=f"开始更新【{site_info.name}】Cookie&UA ...", userid=userid))
# 用户名
username = args[1]
@@ -666,11 +677,13 @@ class SiteChain(ChainBase):
logger.error(msg)
self.post_message(Notification(
channel=channel,
source=source,
title=f"{site_info.name}】 Cookie&UA更新失败",
text=f"错误原因:{msg}",
userid=userid))
else:
self.post_message(Notification(
channel=channel,
source=source,
title=f"{site_info.name}】 Cookie&UA更新成功",
userid=userid))

View File

@@ -941,13 +941,15 @@ class SubscribeChain(ChainBase):
"doubanid": mediainfo.douban_id
})
def remote_list(self, channel: MessageChannel, userid: Union[str, int] = None):
def remote_list(self, channel: MessageChannel,
userid: Union[str, int] = None, source: str = None):
"""
查询订阅并发送消息
"""
subscribes = self.subscribeoper.list()
if not subscribes:
self.post_message(Notification(channel=channel,
source=source,
title='没有任何订阅!', userid=userid))
return
title = f"共有 {len(subscribes)} 个订阅,回复对应指令操作: " \
@@ -964,15 +966,16 @@ class SubscribeChain(ChainBase):
f"[{subscribe.total_episode - (subscribe.lack_episode or subscribe.total_episode)}"
f"/{subscribe.total_episode}]")
# 发送列表
self.post_message(Notification(channel=channel,
self.post_message(Notification(channel=channel, source=source,
title=title, text='\n'.join(messages), userid=userid))
def remote_delete(self, arg_str: str, channel: MessageChannel, userid: Union[str, int] = None):
def remote_delete(self, arg_str: str, channel: MessageChannel,
userid: Union[str, int] = None, source: str = None):
"""
删除订阅
"""
if not arg_str:
self.post_message(Notification(channel=channel,
self.post_message(Notification(channel=channel, source=source,
title="请输入正确的命令格式:/subscribe_delete [id]"
"[id]为订阅编号", userid=userid))
return
@@ -984,7 +987,7 @@ class SubscribeChain(ChainBase):
subscribe_id = int(arg_str)
subscribe = self.subscribeoper.get(subscribe_id)
if not subscribe:
self.post_message(Notification(channel=channel,
self.post_message(Notification(channel=channel, source=source,
title=f"订阅编号 {subscribe_id} 不存在!", userid=userid))
return
# 删除订阅
@@ -995,7 +998,7 @@ class SubscribeChain(ChainBase):
"doubanid": subscribe.doubanid
})
# 重新发送消息
self.remote_list(channel, userid)
self.remote_list(channel=channel, userid=userid, source=source)
@staticmethod
def __get_subscribe_no_exits(subscribe_name: str,

View File

@@ -19,20 +19,25 @@ class SystemChain(ChainBase, metaclass=Singleton):
_restart_file = "__system_restart__"
def remote_clear_cache(self, channel: MessageChannel, userid: Union[int, str]):
def __init__(self):
super().__init__()
# 重启完成检测
self.restart_finish()
def remote_clear_cache(self, channel: MessageChannel, userid: Union[int, str], source: str = None):
"""
清理系统缓存
"""
self.clear_cache()
self.post_message(Notification(channel=channel,
self.post_message(Notification(channel=channel, source=source,
title=f"缓存清理完成!", userid=userid))
def restart(self, channel: MessageChannel, userid: Union[int, str]):
def restart(self, channel: MessageChannel, userid: Union[int, str], source: str = None):
"""
重启系统
"""
if channel and userid:
self.post_message(Notification(channel=channel,
self.post_message(Notification(channel=channel, source=source,
title="系统正在重启,请耐心等候!", userid=userid))
# 保存重启信息
self.save_cache({
@@ -59,11 +64,11 @@ class SystemChain(ChainBase, metaclass=Singleton):
title += f"当前前端版本:{front_local_version},远程版本:{front_release_version}"
return title
def version(self, channel: MessageChannel, userid: Union[int, str]):
def version(self, channel: MessageChannel, userid: Union[int, str], source: str = None):
"""
查看当前版本、远程版本
"""
self.post_message(Notification(channel=channel,
self.post_message(Notification(channel=channel, source=source,
title=self.__get_version_message(),
userid=userid))

View File

@@ -534,13 +534,14 @@ class TransferChain(ChainBase):
return trans_items
def remote_transfer(self, arg_str: str, channel: MessageChannel, userid: Union[str, int] = None):
def remote_transfer(self, arg_str: str, channel: MessageChannel,
userid: Union[str, int] = None, source: str = None):
"""
远程重新整理,参数 历史记录ID TMDBID|类型
"""
def args_error():
self.post_message(Notification(channel=channel,
self.post_message(Notification(channel=channel, source=source,
title="请输入正确的命令格式:/redo [id] [tmdbid/豆瓣id]|[类型]"
"[id]历史记录编号", userid=userid))
@@ -571,7 +572,7 @@ class TransferChain(ChainBase):
mtype=MediaType(type_str),
mediaid=media_id)
if not state:
self.post_message(Notification(channel=channel, title="手动整理失败",
self.post_message(Notification(channel=channel, title="手动整理失败", source=source,
text=errmsg, userid=userid, link=settings.MP_DOMAIN('#/history')))
return