Files
xiaoli-cli/operit_py/browser.py
T
shuiyu1123 dc77dfd304 feat: 将 47 个 Operit TypeScript 脚本全面翻译为 Python
- 新增 operit_runtime.py: 跨平台 Python 运行时
  - 实现 Tools.System/Files/Net/UI/Memory/FFmpeg/Workflow/Chat/SoftwareSettings/Tasker
  - 实现 OkHttp 链式 HTTP 客户端兼容层
  - 实现 complete()/exports/console 全局对象
  - 实现 OperitScriptLoader 脚本加载器
  - 支持 Windows/macOS/Linux 三平台

- 新增 operit_py/ 目录: 47 个 Python 翻译脚本
  - 搜索类: duckduckgo, google_search, tavily, zhipu_search, various_search, crossref
  - 绘图类: openai_draw, qwen_draw, xai_draw, minimax_draw, siliconflow_draw, zhipu_draw, nanobanana_draw
  - 媒体类: ffmpeg, file_converter, douyin_download, bilibili_tools, jmcomic
  - 文件类: extended_file_tools, hex_editor, reader, beeimg_image_uploader
  - 网络类: extended_http_tools, network_test
  - 自动化类: browser, automatic_ui_base, automatic_ui_subagent, automatic_bilibili_assistant, automatic_xiaohongshu_assistant, automatic_baidu_map_assistant
  - 系统类: system_tools, super_admin, daily_life, operit_editor
  - 对话类: ai_chat, extended_chat, qq_intelligent
  - 工作流类: workflow, vflow_trigger, tasker
  - 工具类: time, code_runner, quick_start, extended_memory_tools, baidu_map, 12306, pdf_vision_parser

- 测试结果: 47/47 语法检查通过, 38 脚本加载成功, 280 个工具注册
- Android 专属功能已降级处理(保留接口,返回不支持提示)
2026-06-23 12:27:18 +00:00

411 lines
19 KiB
Python

# METADATA
# {
# "name": "browser",
# "display_name": {
# "zh": "Browser 自动化操作",
# "en": "Browser Automation"
# },
# "description": {
# "zh": "严格对齐 Playwright MCP 默认 browser 工具面的浏览器自动化工具集。",
# "en": "Browser automation tools aligned to the default Playwright MCP browser surface."
# },
# "enabledByDefault": true,
# "category": "Automatic",
# "tools": [
# {
# "name": "click",
# "description": { "zh": "按快照 ref 点击页面元素,包括同源 iframe 内的元素。", "en": "Click an element by snapshot ref, including refs inside same-origin iframes." },
# "parameters": [
# { "name": "ref", "description": { "zh": "快照中的目标元素引用;和 selector 至少提供一个。", "en": "Target element ref from the snapshot; provide ref or selector." }, "type": "string", "required": false },
# { "name": "selector", "description": { "zh": "可选,ref 不可用时的元素选择器。", "en": "Optional selector fallback when ref is not available." }, "type": "string", "required": false },
# { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false },
# { "name": "doubleClick", "description": { "zh": "可选,是否双击。", "en": "Optional double click." }, "type": "boolean", "required": false },
# { "name": "button", "description": { "zh": "可选,left/right/middle。", "en": "Optional mouse button: left/right/middle." }, "type": "string", "required": false },
# { "name": "modifiers", "description": { "zh": "可选,修饰键数组。", "en": "Optional modifier keys array." }, "type": "array", "required": false }
# ]
# },
# {
# "name": "close",
# "description": { "zh": "关闭当前 tab。", "en": "Close the current tab." },
# "parameters": []
# },
# {
# "name": "console_messages",
# "description": { "zh": "读取控制台消息。", "en": "Read console messages." },
# "parameters": [
# { "name": "level", "description": { "zh": "可选,日志级别:error/warning/info/debug,默认 info。", "en": "Optional log level: error/warning/info/debug. Defaults to info." }, "type": "string", "required": false },
# { "name": "filename", "description": { "zh": "可选,保存输出的文件名。", "en": "Optional output file name." }, "type": "string", "required": false }
# ]
# },
# {
# "name": "drag",
# "description": { "zh": "在两个元素之间拖拽。", "en": "Drag between two elements." },
# "parameters": [
# { "name": "startElement", "description": { "zh": "源元素的人类可读描述。", "en": "Human-readable source element description." }, "type": "string", "required": true },
# { "name": "startRef", "description": { "zh": "源元素 ref。", "en": "Source element ref." }, "type": "string", "required": true },
# { "name": "endElement", "description": { "zh": "目标元素的人类可读描述。", "en": "Human-readable target element description." }, "type": "string", "required": true },
# { "name": "endRef", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true }
# ]
# },
# {
# "name": "evaluate",
# "description": { "zh": "在页面或元素上执行 JavaScript 函数。", "en": "Evaluate a JavaScript function on the page or an element." },
# "parameters": [
# { "name": "function", "description": { "zh": "要执行的函数源码。", "en": "Function source to execute." }, "type": "string", "required": true },
# { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false },
# { "name": "ref", "description": { "zh": "可选,目标元素 ref。", "en": "Optional target element ref." }, "type": "string", "required": false }
# ]
# },
# {
# "name": "upload",
# "description": { "zh": "向当前文件选择器上传文件。", "en": "Upload files to the current file chooser." },
# "parameters": [
# { "name": "paths", "description": { "zh": "可选,绝对路径数组;不传则取消 file chooser。", "en": "Optional absolute file paths; omit to cancel the file chooser." }, "type": "array", "required": false }
# ]
# },
# {
# "name": "fill_form",
# "description": { "zh": "批量填写表单字段。", "en": "Fill multiple form fields." },
# "parameters": [
# { "name": "fields", "description": { "zh": "字段数组。", "en": "Array of form fields." }, "type": "array", "required": true }
# ]
# },
# {
# "name": "handle_dialog",
# "description": { "zh": "处理当前对话框。", "en": "Handle the current dialog." },
# "parameters": [
# { "name": "accept", "description": { "zh": "是否接受对话框。", "en": "Whether to accept the dialog." }, "type": "boolean", "required": true },
# { "name": "promptText", "description": { "zh": "可选,prompt 的输入文本。", "en": "Optional prompt text." }, "type": "string", "required": false }
# ]
# },
# {
# "name": "hover",
# "description": { "zh": "悬停到页面元素上。", "en": "Hover over an element." },
# "parameters": [
# { "name": "ref", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true },
# { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false }
# ]
# },
# {
# "name": "goto",
# "description": { "zh": "导航到指定 URL。", "en": "Navigate to a URL." },
# "parameters": [
# { "name": "url", "description": { "zh": "目标 URL。", "en": "Target URL." }, "type": "string", "required": true }
# ]
# },
# {
# "name": "back",
# "description": { "zh": "后退到上一页。", "en": "Go back to the previous page." },
# "parameters": []
# },
# {
# "name": "network_requests",
# "description": { "zh": "读取当前页面的网络请求。", "en": "Read network requests for the current page." },
# "parameters": [
# { "name": "includeStatic", "description": { "zh": "可选,是否包含静态资源请求,默认 false。", "en": "Optional include static resource requests. Defaults to false." }, "type": "boolean", "required": false },
# { "name": "filename", "description": { "zh": "可选,保存输出的文件名。", "en": "Optional output file name." }, "type": "string", "required": false }
# ]
# },
# {
# "name": "press_key",
# "description": { "zh": "按下键盘按键。", "en": "Press a keyboard key." },
# "parameters": [
# { "name": "key", "description": { "zh": "按键名。", "en": "Key name." }, "type": "string", "required": true }
# ]
# },
# {
# "name": "resize",
# "description": { "zh": "调整浏览器视口大小。", "en": "Resize the browser viewport." },
# "parameters": [
# { "name": "width", "description": { "zh": "宽度。", "en": "Width." }, "type": "number", "required": true },
# { "name": "height", "description": { "zh": "高度。", "en": "Height." }, "type": "number", "required": true }
# ]
# },
# {
# "name": "run_code",
# "description": { "zh": "运行 Playwright 风格代码片段。", "en": "Run a Playwright-style code snippet." },
# "parameters": [
# { "name": "code", "description": { "zh": "代码片段。", "en": "Code snippet." }, "type": "string", "required": true }
# ]
# },
# {
# "name": "select_option",
# "description": { "zh": "在下拉框中选择选项。", "en": "Select options in a dropdown." },
# "parameters": [
# { "name": "ref", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true },
# { "name": "values", "description": { "zh": "要选择的值数组。", "en": "Values to select." }, "type": "array", "required": true },
# { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false }
# ]
# },
# {
# "name": "snapshot",
# "description": { "zh": "获取当前页面结构化快照,包括同源 iframe 内容。", "en": "Get a structured page snapshot, including same-origin iframe content." },
# "parameters": [
# { "name": "filename", "description": { "zh": "可选,保存快照的文件名。", "en": "Optional snapshot output file name." }, "type": "string", "required": false },
# { "name": "selector", "description": { "zh": "可选,作为快照根节点的元素选择器。", "en": "Optional root element selector for a partial snapshot." }, "type": "string", "required": false },
# { "name": "depth", "description": { "zh": "可选,限制快照树深度。", "en": "Optional snapshot tree depth limit." }, "type": "number", "required": false }
# ]
# },
# {
# "name": "type",
# "description": { "zh": "向可编辑元素输入文本。", "en": "Type text into an editable element." },
# "parameters": [
# { "name": "ref", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true },
# { "name": "text", "description": { "zh": "输入文本。", "en": "Text to type." }, "type": "string", "required": true },
# { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false },
# { "name": "submit", "description": { "zh": "可选,输入后是否提交。", "en": "Optional submit after typing." }, "type": "boolean", "required": false },
# { "name": "slowly", "description": { "zh": "可选,是否逐字输入。", "en": "Optional type slowly." }, "type": "boolean", "required": false }
# ]
# },
# {
# "name": "wait_for",
# "description": { "zh": "等待文本出现、消失或等待指定时间。", "en": "Wait for text to appear, disappear, or for a duration." },
# "parameters": [
# { "name": "time", "description": { "zh": "可选,等待秒数。", "en": "Optional number of seconds to wait." }, "type": "number", "required": false },
# { "name": "text", "description": { "zh": "可选,等待出现的文本。", "en": "Optional text to wait for." }, "type": "string", "required": false },
# { "name": "textGone", "description": { "zh": "可选,等待消失的文本。", "en": "Optional text to wait to disappear." }, "type": "string", "required": false }
# ]
# },
# {
# "name": "tabs",
# "description": { "zh": "列出、创建、切换或关闭 tab。", "en": "List, create, select, or close tabs." },
# "parameters": [
# { "name": "action", "description": { "zh": "操作:list/create/select/close。", "en": "Action: list/create/select/close." }, "type": "string", "required": true },
# { "name": "index", "description": { "zh": "可选,0-based tab 索引。", "en": "Optional 0-based tab index." }, "type": "number", "required": false }
# ]
# }
# ]
# }
import datetime
import random
import re
MAX_INLINE_BROWSER_TEXT_CHARS = 24000
TOOL_NAMES = [
"click",
"close",
"close_all",
"console_messages",
"drag",
"evaluate",
"upload",
"fill_form",
"handle_dialog",
"hover",
"goto",
"back",
"network_requests",
"press_key",
"resize",
"run_code",
"select_option",
"snapshot",
"type",
"wait_for",
"tabs"
]
def normalizeOptionalString(value=None):
if value is None:
return None
normalized = str(value).strip()
return normalized if normalized else None
def buildLargeOutputFilename(prefix, extension):
timestamp = datetime.datetime.utcnow().isoformat().replace(":", "-").replace(".", "-")
rand = int(random.random() * 1000000)
return OPERIT_CLEAN_ON_EXIT_DIR + "/browser_" + prefix + "_" + timestamp + "_" + rand + "." + extension
async def maybePersistLargeBrowserResponse(result, prefix, extension="md"):
if not isinstance(result, str) or len(result) <= MAX_INLINE_BROWSER_TEXT_CHARS:
return result
await Tools.Files.mkdir(OPERIT_CLEAN_ON_EXIT_DIR, True)
filename = buildLargeOutputFilename(prefix, extension)
await Tools.Files.write(filename, result, False)
normalizedPath = filename.replace("\\", "/")
return "Large browser response saved to:\n- [Browser Output](" + normalizedPath + ")"
async def click(params):
result = await Tools.Net.browserClick(params)
return await maybePersistLargeBrowserResponse(result, "click")
async def close(params=None):
if params is None:
params = {}
result = await Tools.Net.browserClose()
return await maybePersistLargeBrowserResponse(result, "close")
async def close_all(params=None):
if params is None:
params = {}
result = await Tools.Net.browserCloseAll()
return await maybePersistLargeBrowserResponse(result, "close_all")
async def console_messages(params=None):
if params is None:
params = {}
result = await Tools.Net.browserConsoleMessages(params)
return await maybePersistLargeBrowserResponse(result, "console_messages")
async def drag(params):
result = await Tools.Net.browserDrag(params)
return await maybePersistLargeBrowserResponse(result, "drag")
async def evaluate(params):
result = await Tools.Net.browserEvaluate(params)
return await maybePersistLargeBrowserResponse(result, "evaluate")
async def upload(params=None):
if params is None:
params = {}
result = await Tools.Net.browserFileUpload(params)
return await maybePersistLargeBrowserResponse(result, "upload")
def normalizeFormFields(fields):
if len(fields) == 0:
raise Exception("fields must be a non-empty array")
normalizedFields = []
for index, field in enumerate(fields):
normalized = {
"name": str(field.get("name", "")).strip(),
"type": str(field.get("type", "")).strip(),
"value": field.get("value")
}
if not normalized["name"]:
raise Exception("fields[" + str(index) + "].name is required")
if not normalized["type"]:
raise Exception("fields[" + str(index) + "].type is required")
ref = normalizeOptionalString(field.get("ref"))
selector = normalizeOptionalString(field.get("selector"))
if not ref and not selector:
raise Exception("fields[" + str(index) + "] requires ref or selector")
if ref:
normalized["ref"] = ref
if selector:
normalized["selector"] = selector
normalizedFields.append(normalized)
return normalizedFields
async def fill_form(params):
result = await Tools.Net.browserFillForm({
"fields": normalizeFormFields(params["fields"])
})
return await maybePersistLargeBrowserResponse(result, "fill_form")
async def handle_dialog(params):
result = await Tools.Net.browserHandleDialog(params)
return await maybePersistLargeBrowserResponse(result, "handle_dialog")
async def hover(params):
result = await Tools.Net.browserHover(params)
return await maybePersistLargeBrowserResponse(result, "hover")
async def goto(params):
result = await Tools.Net.browserNavigate(params)
return await maybePersistLargeBrowserResponse(result, "goto")
async def back(params=None):
if params is None:
params = {}
result = await Tools.Net.browserNavigateBack()
return await maybePersistLargeBrowserResponse(result, "back")
async def network_requests(params=None):
if params is None:
params = {}
result = await Tools.Net.browserNetworkRequests(params)
return await maybePersistLargeBrowserResponse(result, "network_requests")
async def press_key(params):
result = await Tools.Net.browserPressKey(params)
return await maybePersistLargeBrowserResponse(result, "press_key")
async def resize(params):
result = await Tools.Net.browserResize(params)
return await maybePersistLargeBrowserResponse(result, "resize")
async def run_code(params):
result = await Tools.Net.browserRunCode(params)
return await maybePersistLargeBrowserResponse(result, "run_code")
async def select_option(params):
result = await Tools.Net.browserSelectOption(params)
return await maybePersistLargeBrowserResponse(result, "select_option")
async def snapshot(params=None):
if params is None:
params = {}
result = await Tools.Net.browserSnapshot(params)
return await maybePersistLargeBrowserResponse(result, "snapshot")
async def type(params):
result = await Tools.Net.browserType(params)
return await maybePersistLargeBrowserResponse(result, "type")
async def wait_for(params=None):
if params is None:
params = {}
result = await Tools.Net.browserWaitFor(params)
return await maybePersistLargeBrowserResponse(result, "wait_for")
async def tabs(params):
result = await Tools.Net.browserTabs(params)
return await maybePersistLargeBrowserResponse(result, "tabs")
async def browserMain(params=None):
if params is None:
params = {}
return "Browser package ready: " + ", ".join(TOOL_NAMES)
exports.click = click
exports.close = close
exports.close_all = close_all
exports.console_messages = console_messages
exports.drag = drag
exports.evaluate = evaluate
exports.upload = upload
exports.fill_form = fill_form
exports.handle_dialog = handle_dialog
exports.hover = hover
exports.goto = goto
exports.back = back
exports.network_requests = network_requests
exports.press_key = press_key
exports.resize = resize
exports.run_code = run_code
exports.select_option = select_option
exports.snapshot = snapshot
exports.type = type
exports.wait_for = wait_for
exports.tabs = tabs
exports.main = browserMain