Replies: 7 comments 17 replies
-
下面是关于实现的想法: 插件类型目前想到的插件类型仅有对解析和执行流程的修改插件(mod)和改变主题的插件(theme)。 插件加载目前考虑在plugins文件夹下新建配置文件指定加载的插件,比如 {
"plugins": [
{"type": "Theme", "path": "MyTheme.xml"},
{"type": "Mod", "path": "MyPlugin.py"}
]
} 在程序每次执行启动或录制时插件管理器都会先检查配置文件以实现热更新。程序中的插件选择控件则可以移除。 功能移除改变执行速度这个功能感觉可有可无,可以写成一个插件,使界面更简洁。 |
Beta Was this translation helpful? Give feedback.
-
我觉得我需要补补相关的插件知识。。。。。 我们目前的插件是较少的 我昨天在做一些测试的时候,我也在思考能不能写一段脚本,然后直接自动化的改变里面的参数 |
Beta Was this translation helpful? Give feedback.
-
我根据这种结构设想一下新脚本的语法:
每一个插件的目录结构为:
每一个插件需要有
|
Beta Was this translation helpful? Give feedback.
-
我正在尝试实现上面的想法,以下是对一些问题的想法: Label的定义位置脚本的语法修改后,录制的脚本会变成下面的形式 {
"scripts": [
{
"delay": ___ ,
"event_type": ___,
"action": ___,
"message": ____
},
...
]
} 或者 {
"scripts": [
{"delay": ___ , "event_type": ___, "action": ___, "message": ____},
...
]
} 如果label在script同级定义,需要指定每个索引处对应的label。如果脚本较长,又采用第一种形式保存,要数索引是很不方便的,用第二种形式的话结合带行数的文本编辑器做减法还是可以算出索引的。如果label直接指定在对应索引事件的定义处,则不需要人工数索引,相对方便一些,但仍需要指定索引。 插件配置文件我上面的回复设想在 // manifest.json
{
// 基本信息
"name": ____,
"description": ____,
"author": ____,
"version": ____,
// 如果要改动资源文件(如提示音,主题)
"resource_path": {
"theme": ____,
"tone": ____
}
} 钩子函数设计从使用的角度想到了几个”挂载点“:
方案一如果参考5.0版的设计,指定9个调用时机: class HookType(Enum):
INIT = 0 # 程序初始化时触发
PARSING = 1 # 解析脚本时触发
RECORD = 2 # 录制事件时触发
EXE_INIT = 3 # 脚本解析后,脚本执行前触发
EXE_LOOP = 4 # 每次执行前触发
EXE_EVENT = 5 # 每次执行事件前触发
EXE_EVENT_END = 6 # 每次执行事件后触发
EXE_LOOP_END = 7 # 每次执行后触发
EXE_END = 8 # 执行结束后触发 或者修改一下: EXE_EVENT = 5 # 每次执行到指定label的事件前触发
EXE_EVENT_END = 6 # 每次执行到指定label的事件后触发 假如要实现动态改变参数(如运行时随机改变延时),插件需要实现以下hook def get_exe_event_hook(label: str, event: ScriptEvent):
def random_delay(event):
# ...
event.delay = random_delay # mod event
return event
return random_delay 假如要实现跳转(如等待当前位置窗口出现) def get_exe_event_hook(label: str):
def check_handle(event):
# check handle of window
if window_is_open:
return event
else:
# sleep
raise JumpProcess(targetlabel)
return check_handle 这样的话因为”挂载点“相同,可以兼容前一版的插件。 方案二仅保留4个”挂载点“: class HookType(Enum):
INIT = 0 # 程序初始化时触发
PARSING = 1 # 解析脚本时触发
RECORD = 2 # 录制事件时触发
EXE_INIT = 3 # 脚本解析后,脚本执行前触发 而程序的执行交由一个”流程执行器“执行,在未追加任何定义时,执行器顺序执行脚本,用户则可以在执行器中添加一些条件判断或循环。 def get_exe_init_hook():
def check_handle(event):
# check window handle at current position
return True # or False
def add_window_check(workflow):
workflow.addCondition(
atlabel=targetlabel, # 在targetlabel处检查条件
judge=check_handle # 根据judge函数的结果(bool)判断结果
isTrue=WorkFlow.DONOTHING,
isFalse=WorkFlow.JUMP,
args=jumptolabel
)
return add_window_check 用户可以在脚本中加入自定义参数或变量 // script.json
{
"delay": “$random_delay”, // *
"event_type": ___,
"action": ___,
"message": ____,
"window_handle": ____ // *
}, 在解析时,变量需要转为增强基本类型以便在执行时能够被读取: import random
class RandomDelay(int):
def __new__(cls, a, b):
return int.__new__(cls, random.randint(a, b)) 这样子插件的编写也许对用户更友好?在脚本中,在运行时才知道具体值的参数可以设为变量,在py文件中给出读取方法,比方案一类似hack的方式更好一些? |
Beta Was this translation helpful? Give feedback.
-
我们在做这个的时候需不需要向下兼容 |
Beta Was this translation helpful? Give feedback.
-
还有一些小想法...就是目前的脚本和增强功能(流程修改、自定义功能等)是分离的,对于整体的执行内容需要结合脚本和插件的内容才能看出(有点后知后觉了,4个月前的issue就提到了这个问题,当时的思维有点局限)。
根据这个思路看新的设计,还是存在以下问题:
那么,是否可以:
改变脚本语法当前的脚本语法无论修改前后都是用 {
script: [
{/* ... */},
{
type: "if",
call: "${function}"
iftrue: [
{/* ... */},
{/* ... */}
],
else: [
{
type: "goto",
tolabel: "Window_check"
}
],
label: "Window_check"
},
{/* ... */}
]
} 或者,参考TagUI的设计,让脚本更加程序语言化一些。 引入图形化设计修改执行内容和流程可以参考UIPath StudioX中的设计界面。这只是个想法,目前不打算这么弄。 |
Beta Was this translation helpful? Give feedback.
-
终于改完了这个插件系统,下面是所有改动的内容: 脚本语法脚本统一采用 {
scripts: [
{
// 如果仅靠无插件的录制得到,每一个object仅含以下5个参数,且type一定为"event"
type: "event",
delay: ___ ,
event_type: ___,
action: ___,
message: ____,
// (可选)执行前调用的函数,可以指定多个函数
call: [/* ... */],
// (可选)标签,供goto跳转使用,其它类型的object也可以有标签
label: ___,
// (可选)其它参数,key和value均任意,可供插件注册函数读取
// ___: ___,
},
{
type: "sequence", // 程序将顺序执行events内的事件
events: [
// 内容必须为type=='event'的object
],
// (可选)执行前调用的函数,与call不同的是执行events内每一个object前都会调用attach内的函数
// 对于一系列需要添加相同调用函数的object来说可以靠这个简化编写
attach: [/* ... */]
},
{
type: "if", // 程序先调用judge指定的函数,根据返回值决定后续执行内容,为True执行do内的事件,为False执行else内的事件
judge: "function_name",
do: [/* ... */],
else: [/* ... */],
},
{
type: "subroutine",
path: ["path", /* ... */] // 执行其它脚本
},
{
type: "goto",
tolabel: "target_label" // 跳转到相应label的object
}
...
]
} 插件编写每个插件需要包含一个
// manifest.json5
{
// 基本信息
"name": "Example",
"description": "This is an example plugin",
"author": "",
"version": "1.0",
"enabled": true, // 是否启用插件,如为False,程序不会加载该插件
// (可选) 注册新的函数
"entry": "Example.py", // 实现插件接口的文件
"plugin_class": "MyExample", // 实现插件接口的类名
// (可选) 添加新的资源文件路径
"resource_path": {
"theme": ["mytheme.xml"]
}
} 插件接口的定义: class PluginInterface:
def __init__(self, manifest: Dict):
self.meta = PluginMeta(manifest)
# 注册运行时可调用的函数
@abstractmethod
def register_functions(self) -> Dict[str, Callable]:
pass
# 注册录制时可调用的函数
@abstractmethod
def register_record_functions(self) -> List[Callable]:
pass
这么说不够直接,这是测试时写的样例: {
scripts: [
{
type: "sequence",
events: [
{
type: "event",
delay: "2-8",
event_type: "EM",
message: "mouse move",
action: ["0.39427083333333335%","0.45740740740740743%"]
},
{
type: "event",
delay: "1-514",
event_type: "EM",
message: "mouse move",
action: ["0.49270833333333336%","0.4398148148148148%"]
}
],
attach: ["rd"]
},
]
} manifest.json5 {
// 基本信息
"name": "Random Delay",
"description": "使延时的格式变为[0-9]+-[0-9]+,即X-Y,其中X为最小延时,Y为最大延时,X,Y均为整数,单位毫秒",
"author": "Monomux",
"version": "1.0",
"entry": "Random_Delay.py",
"plugin_class": "RandomDelay",
"enabled": true
} Random_Delay.py from typing import Dict, Callable, List
from Util.Parser import JsonObject
from Plugin.Interface import PluginInterface
from loguru import logger
import re
import random
class RandomDelay(PluginInterface):
def __init__(self, manifest: Dict):
super().__init__(manifest)
def register_functions(self) -> Dict[str, Callable]:
funcs: Dict[str, Callable] = {}
def random_delay(json_object: JsonObject):
delay: str = json_object.content['delay']
# 避免重复修改
if type(delay) == str:
delays = re.match('([0-9]+)\-([0-9]+)', delay).groups()
json_object.content['delay'] = random.randint(int(delays[0]), int(delays[1]))
funcs['rd'] = random_delay
return funcs
def register_record_functions(self) -> List[Callable]:
return [] |
Beta Was this translation helpful? Give feedback.
-
问题
目前的插件功能实现还是有点野路子,存在一些问题:
改进
我对插件系统的改进有以下想法:
Beta Was this translation helpful? Give feedback.
All reactions