- 新增 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 专属功能已降级处理(保留接口,返回不支持提示)
486 lines
19 KiB
Python
486 lines
19 KiB
Python
# METADATA
|
|
# {
|
|
# "name": "code_reader",
|
|
# "display_name": {"zh": "代码读取器", "en": "Code Reader"},
|
|
# "description": {"zh": "提供代码匹配抓取功能", "en": "Provide code searching and extraction utilities."},
|
|
# "enabledByDefault": True,
|
|
# "category": "File",
|
|
# "tools": [
|
|
# {"name": "search_code_in_folder", "description": {"zh": "在文件夹中搜索匹配的代码", "en": "Search for matching code in a folder."}, "parameters": [
|
|
# {"name": "folder_path", "description": {"zh": "要搜索的文件夹路径", "en": "Folder path to search."}, "type": "string", "required": True},
|
|
# {"name": "pattern", "description": {"zh": "要搜索的代码模式(字符串或正则表达式)", "en": "Pattern to search for (string or regular expression)."}, "type": "string", "required": True},
|
|
# {"name": "file_extensions", "description": {"zh": "要搜索的文件扩展名列表(如 \".ts,.js\")", "en": "File extension list to include (e.g. \".ts,.js\")."}, "type": "string", "required": False},
|
|
# {"name": "recursive", "description": {"zh": "是否递归搜索子文件夹(true/false)", "en": "Whether to search subfolders recursively (true/false)."}, "type": "string", "required": False}
|
|
# ]},
|
|
# {"name": "extract_functions", "description": {"zh": "提取文件夹中的所有函数定义", "en": "Extract all function definitions from a folder."}, "parameters": [
|
|
# {"name": "folder_path", "description": {"zh": "要搜索的文件夹路径", "en": "Folder path to search."}, "type": "string", "required": True},
|
|
# {"name": "function_name_pattern", "description": {"zh": "函数名匹配模式(可选)", "en": "Function name matching pattern (optional)."}, "type": "string", "required": False},
|
|
# {"name": "file_extensions", "description": {"zh": "要搜索的文件扩展名列表(如 \".ts,.js\")", "en": "File extension list to include (e.g. \".ts,.js\")."}, "type": "string", "required": False},
|
|
# {"name": "recursive", "description": {"zh": "是否递归搜索子文件夹(true/false)", "en": "Whether to search subfolders recursively (true/false)."}, "type": "string", "required": False}
|
|
# ]},
|
|
# {"name": "extract_function_block", "description": {"zh": "提取特定函数的代码块", "en": "Extract the code block for a specific function."}, "parameters": [
|
|
# {"name": "file_path", "description": {"zh": "要读取的文件路径", "en": "File path to read."}, "type": "string", "required": True},
|
|
# {"name": "function_name", "description": {"zh": "要提取的函数名称", "en": "Function name to extract."}, "type": "string", "required": True},
|
|
# {"name": "include_line_numbers", "description": {"zh": "是否在结果中包含行号(true/false)", "en": "Whether to include line numbers in the result (true/false)."}, "type": "string", "required": False}
|
|
# ]},
|
|
# {"name": "find_code_blocks", "description": {"zh": "查找文件中的代码块", "en": "Find code blocks in a file."}, "parameters": [
|
|
# {"name": "file_path", "description": {"zh": "要读取的文件路径", "en": "File path to read."}, "type": "string", "required": True},
|
|
# {"name": "block_pattern", "description": {"zh": "代码块匹配模式(如类定义、接口等)", "en": "Code block matching pattern (e.g., class definition, interface, etc.)."}, "type": "string", "required": True},
|
|
# {"name": "include_line_numbers", "description": {"zh": "是否在结果中包含行号(true/false)", "en": "Whether to include line numbers in the result (true/false)."}, "type": "string", "required": False}
|
|
# ]},
|
|
# {"name": "read_regex_matches", "description": {"zh": "读取文件中匹配正则表达式的内容", "en": "Read content in a file that matches a regular expression."}, "parameters": [
|
|
# {"name": "file_path", "description": {"zh": "要读取的文件路径", "en": "File path to read."}, "type": "string", "required": True},
|
|
# {"name": "regex", "description": {"zh": "正则表达式字符串", "en": "Regular expression string."}, "type": "string", "required": True},
|
|
# {"name": "include_line_numbers", "description": {"zh": "是否在结果中包含行号(true/false)", "en": "Whether to include line numbers in the result (true/false)."}, "type": "string", "required": False}
|
|
# ]}
|
|
# ]
|
|
# }
|
|
|
|
import json
|
|
import re
|
|
import time
|
|
import math
|
|
from urllib.parse import quote, unquote
|
|
|
|
Error = Exception
|
|
|
|
|
|
def convertParamTypes(params, paramTypes):
|
|
if not params or not paramTypes:
|
|
return params
|
|
|
|
result = {}
|
|
|
|
for key in params:
|
|
if params[key] is None:
|
|
result[key] = params[key]
|
|
continue
|
|
|
|
expectedType = paramTypes.get(key)
|
|
if not expectedType:
|
|
result[key] = params[key]
|
|
continue
|
|
|
|
value = params[key]
|
|
|
|
try:
|
|
et = expectedType.lower()
|
|
if et == 'number':
|
|
if isinstance(value, str):
|
|
if '.' in value:
|
|
result[key] = float(value)
|
|
else:
|
|
result[key] = int(value, 10)
|
|
if isinstance(result[key], float) and math.isnan(result[key]):
|
|
raise Error(f"参数 {key} 无法转换为数字: {value}")
|
|
else:
|
|
result[key] = value
|
|
elif et == 'boolean':
|
|
if isinstance(value, str):
|
|
lowerValue = value.lower()
|
|
if lowerValue in ('true', '1', 'yes'):
|
|
result[key] = True
|
|
elif lowerValue in ('false', '0', 'no'):
|
|
result[key] = False
|
|
else:
|
|
raise Error(f"参数 {key} 无法转换为布尔值: {value}")
|
|
else:
|
|
result[key] = value
|
|
elif et == 'array':
|
|
if isinstance(value, str):
|
|
try:
|
|
result[key] = json.loads(value)
|
|
if not isinstance(result[key], list):
|
|
raise Error('解析结果不是数组')
|
|
except Exception:
|
|
raise Error(f"参数 {key} 无法转换为数组: {value}")
|
|
else:
|
|
result[key] = value
|
|
elif et == 'object':
|
|
if isinstance(value, str):
|
|
try:
|
|
result[key] = json.loads(value)
|
|
if isinstance(result[key], list) or not isinstance(result[key], dict):
|
|
raise Error('解析结果不是对象')
|
|
except Exception:
|
|
raise Error(f"参数 {key} 无法转换为对象: {value}")
|
|
else:
|
|
result[key] = value
|
|
else:
|
|
result[key] = value
|
|
except Exception as error:
|
|
console.error(f"参数类型转换错误: {str(error)}")
|
|
result[key] = value
|
|
|
|
return result
|
|
|
|
|
|
async def writer_wrap(func, params, successMessage, failMessage):
|
|
try:
|
|
console.log(f"开始执行函数: {func.__name__ or '匿名函数'}")
|
|
console.log("参数:", json.dumps(params, indent=2, ensure_ascii=False))
|
|
|
|
result = await func(params)
|
|
|
|
console.log(f"函数 {func.__name__ or '匿名函数'} 执行结果:", json.dumps(result, indent=2, ensure_ascii=False, default=str))
|
|
|
|
if result is None:
|
|
return
|
|
|
|
if isinstance(result, bool):
|
|
complete({
|
|
"success": result,
|
|
"message": successMessage if result else failMessage,
|
|
})
|
|
else:
|
|
complete({
|
|
"success": True,
|
|
"message": successMessage,
|
|
"data": result,
|
|
})
|
|
except Exception as error:
|
|
console.error(f"函数 {func.__name__ or '匿名函数'} 执行失败!")
|
|
console.error(f"错误信息: {str(error)}")
|
|
import traceback
|
|
stack = traceback.format_exc()
|
|
console.error(f"错误堆栈: {stack}")
|
|
|
|
complete({
|
|
"success": False,
|
|
"message": f"{failMessage}: {str(error)}",
|
|
"error_stack": stack,
|
|
})
|
|
|
|
|
|
async def readFilesRecursively(dir_, fileList=None, extensions=None):
|
|
if fileList is None:
|
|
fileList = []
|
|
try:
|
|
listResult = await Tools.Files.list(dir_)
|
|
entries = getattr(listResult, "entries", None) or []
|
|
|
|
for entry in entries:
|
|
filePath = f"{dir_}/{entry.name}"
|
|
|
|
if getattr(entry, "isDirectory", False):
|
|
await readFilesRecursively(filePath, fileList, extensions)
|
|
else:
|
|
if not extensions or len(extensions) == 0 or any(entry.name.lower().endswith(ext.lower()) for ext in extensions):
|
|
fileList.append(filePath)
|
|
except Exception as error:
|
|
console.error(f"读取目录失败: {str(error)}")
|
|
|
|
return fileList
|
|
|
|
|
|
async def search_code_in_folder(params):
|
|
paramTypes = {"recursive": "boolean"}
|
|
params = convertParamTypes(params, paramTypes)
|
|
|
|
folder_path = params.get("folder_path")
|
|
pattern = params.get("pattern")
|
|
file_extensions = params.get("file_extensions", ".js,.ts,.jsx,.tsx,.java,.cs,.py")
|
|
recursive = params.get("recursive", True)
|
|
|
|
if not folder_path:
|
|
raise Error("文件夹路径不能为空")
|
|
if not pattern:
|
|
raise Error("搜索模式不能为空")
|
|
|
|
try:
|
|
patternRegex = re.compile(pattern, re.MULTILINE)
|
|
extensions = file_extensions.split(",")
|
|
|
|
if recursive:
|
|
files = await readFilesRecursively(folder_path, [], extensions)
|
|
else:
|
|
listResult = await Tools.Files.list(folder_path)
|
|
entries = getattr(listResult, "entries", None) or []
|
|
files = [
|
|
f"{folder_path}/{entry.name}"
|
|
for entry in entries
|
|
if not getattr(entry, "isDirectory", False)
|
|
and any(entry.name.lower().endswith(ext.lower()) for ext in extensions)
|
|
]
|
|
|
|
results = []
|
|
|
|
for file in files:
|
|
try:
|
|
fileResult = await Tools.Files.read(file)
|
|
content = getattr(fileResult, "content", "") or ""
|
|
matches = []
|
|
|
|
for match in patternRegex.finditer(content):
|
|
lineNumber = content[:match.start()].count("\n") + 1
|
|
lines = content.split("\n")
|
|
lineContent = lines[lineNumber - 1] if lineNumber - 1 < len(lines) else ""
|
|
|
|
matches.append({
|
|
"match": match.group(0),
|
|
"line": lineNumber,
|
|
"content": lineContent,
|
|
})
|
|
|
|
if len(matches) > 0:
|
|
results.append({
|
|
"file": file,
|
|
"matches": matches,
|
|
})
|
|
except Exception as error:
|
|
console.error(f"处理文件 {file} 时出错: {str(error)}")
|
|
|
|
return results
|
|
except Exception as error:
|
|
raise Error(f"搜索代码时出错: {str(error)}")
|
|
|
|
|
|
async def extract_functions(params):
|
|
paramTypes = {"recursive": "boolean"}
|
|
params = convertParamTypes(params, paramTypes)
|
|
|
|
folder_path = params.get("folder_path")
|
|
function_name_pattern = params.get("function_name_pattern", ".*")
|
|
file_extensions = params.get("file_extensions", ".js,.ts,.jsx,.tsx")
|
|
recursive = params.get("recursive", True)
|
|
|
|
if not folder_path:
|
|
raise Error("文件夹路径不能为空")
|
|
|
|
try:
|
|
functionPatterns = [
|
|
f"function\\s+({function_name_pattern})\\s*\\([^)]*\\)\\s*\\{{",
|
|
f"(?:const|let|var)\\s+({function_name_pattern})\\s*=\\s*function\\s*\\([^)]*\\)\\s*\\{{",
|
|
f"(?:const|let|var)\\s+({function_name_pattern})\\s*=\\s*\\([^)]*\\)\\s*=>\\s*\\{{",
|
|
f"(?:async\\s+)?({function_name_pattern})\\s*\\([^)]*\\)\\s*\\{{",
|
|
]
|
|
|
|
combinedPattern = "|".join(functionPatterns)
|
|
|
|
return await search_code_in_folder({
|
|
"folder_path": folder_path,
|
|
"pattern": combinedPattern,
|
|
"file_extensions": file_extensions,
|
|
"recursive": recursive,
|
|
})
|
|
except Exception as error:
|
|
raise Error(f"提取函数时出错: {str(error)}")
|
|
|
|
|
|
async def extract_function_block(params):
|
|
paramTypes = {"include_line_numbers": "boolean"}
|
|
params = convertParamTypes(params, paramTypes)
|
|
|
|
file_path = params.get("file_path")
|
|
function_name = params.get("function_name")
|
|
include_line_numbers = params.get("include_line_numbers", False)
|
|
|
|
if not file_path:
|
|
raise Error("文件路径不能为空")
|
|
if not function_name:
|
|
raise Error("函数名称不能为空")
|
|
|
|
try:
|
|
fileResult = await Tools.Files.read(file_path)
|
|
content = getattr(fileResult, "content", "") or ""
|
|
lines = content.split("\n")
|
|
|
|
functionPatterns = [
|
|
f"function\\s+{function_name}\\s*\\([^)]*\\)\\s*\\{{",
|
|
f"(?:const|let|var)\\s+{function_name}\\s*=\\s*function\\s*\\([^)]*\\)\\s*\\{{",
|
|
f"(?:const|let|var)\\s+{function_name}\\s*=\\s*\\([^)]*\\)\\s*=>\\s*\\{{",
|
|
f"(?:async\\s+)?{function_name}\\s*\\([^)]*\\)\\s*\\{{",
|
|
]
|
|
|
|
combinedPattern = "|".join(functionPatterns)
|
|
functionRegex = re.compile(combinedPattern)
|
|
|
|
startLine = -1
|
|
for i in range(len(lines)):
|
|
if functionRegex.search(lines[i]):
|
|
startLine = i
|
|
break
|
|
|
|
if startLine == -1:
|
|
return {"error": f"未找到函数: {function_name}"}
|
|
|
|
bracesCount = 0
|
|
endLine = startLine
|
|
|
|
openingBraces = len(re.findall(r"{", lines[startLine]))
|
|
closingBraces = len(re.findall(r"}", lines[startLine]))
|
|
bracesCount = openingBraces - closingBraces
|
|
|
|
for i in range(startLine + 1, len(lines)):
|
|
openBraces = len(re.findall(r"{", lines[i]))
|
|
closeBraces = len(re.findall(r"}", lines[i]))
|
|
|
|
bracesCount += openBraces
|
|
bracesCount -= closeBraces
|
|
|
|
if bracesCount == 0:
|
|
endLine = i
|
|
break
|
|
|
|
functionBlock = "\n".join(lines[startLine:endLine + 1])
|
|
|
|
result = {
|
|
"function_name": function_name,
|
|
"code": functionBlock,
|
|
}
|
|
|
|
if include_line_numbers:
|
|
result["start_line"] = startLine + 1
|
|
result["end_line"] = endLine + 1
|
|
|
|
return result
|
|
except Exception as error:
|
|
raise Error(f"提取函数代码块时出错: {str(error)}")
|
|
|
|
|
|
async def find_code_blocks(params):
|
|
paramTypes = {"include_line_numbers": "boolean"}
|
|
params = convertParamTypes(params, paramTypes)
|
|
|
|
file_path = params.get("file_path")
|
|
block_pattern = params.get("block_pattern")
|
|
include_line_numbers = params.get("include_line_numbers", False)
|
|
|
|
if not file_path:
|
|
raise Error("文件路径不能为空")
|
|
if not block_pattern:
|
|
raise Error("代码块匹配模式不能为空")
|
|
|
|
try:
|
|
fileResult = await Tools.Files.read(file_path)
|
|
content = getattr(fileResult, "content", "") or ""
|
|
blockRegex = re.compile(block_pattern, re.MULTILINE)
|
|
|
|
matches = []
|
|
|
|
for match in blockRegex.finditer(content):
|
|
matchText = match.group(0)
|
|
startIndex = match.start()
|
|
|
|
lines_before = content[:startIndex].split("\n")
|
|
startLine = len(lines_before)
|
|
|
|
endIndex = startIndex + len(matchText)
|
|
bracesCount = 0
|
|
|
|
if "{" in matchText:
|
|
initialOpenBraces = len(re.findall(r"{", matchText))
|
|
initialCloseBraces = len(re.findall(r"}", matchText))
|
|
bracesCount = initialOpenBraces - initialCloseBraces
|
|
|
|
if bracesCount > 0:
|
|
remainingContent = content[endIndex:]
|
|
currentPos = 0
|
|
|
|
for i in range(len(remainingContent)):
|
|
char = remainingContent[i]
|
|
|
|
if char == "{":
|
|
bracesCount += 1
|
|
elif char == "}":
|
|
bracesCount -= 1
|
|
|
|
if bracesCount == 0:
|
|
currentPos = i + 1
|
|
break
|
|
|
|
endIndex += currentPos
|
|
|
|
codeBlock = content[startIndex:endIndex]
|
|
|
|
blockLines = codeBlock.split("\n")
|
|
endLine = startLine + len(blockLines) - 1
|
|
|
|
result = {"block": codeBlock}
|
|
|
|
if include_line_numbers:
|
|
result["start_line"] = startLine
|
|
result["end_line"] = endLine
|
|
|
|
matches.append(result)
|
|
|
|
return {
|
|
"file": file_path,
|
|
"blocks": matches,
|
|
}
|
|
except Exception as error:
|
|
raise Error(f"查找代码块时出错: {str(error)}")
|
|
|
|
|
|
async def read_regex_matches(params):
|
|
paramTypes = {"include_line_numbers": "boolean"}
|
|
params = convertParamTypes(params, paramTypes)
|
|
|
|
file_path = params.get("file_path")
|
|
regex = params.get("regex")
|
|
include_line_numbers = params.get("include_line_numbers", False)
|
|
|
|
if not file_path:
|
|
raise Error("文件路径不能为空")
|
|
if not regex:
|
|
raise Error("正则表达式不能为空")
|
|
|
|
try:
|
|
fileResult = await Tools.Files.read(file_path)
|
|
content = getattr(fileResult, "content", "") or ""
|
|
lines = content.split("\n")
|
|
pattern = re.compile(regex, re.MULTILINE)
|
|
|
|
matches = []
|
|
|
|
for match in pattern.finditer(content):
|
|
lineNumber = content[:match.start()].count("\n") + 1
|
|
lineContent = lines[lineNumber - 1] if lineNumber - 1 < len(lines) else ""
|
|
|
|
result = {
|
|
"match": match.group(0),
|
|
"content": lineContent,
|
|
}
|
|
|
|
if include_line_numbers:
|
|
result["line"] = lineNumber
|
|
|
|
if match.lastindex and match.lastindex >= 1:
|
|
result["groups"] = [match.group(i) for i in range(1, match.lastindex + 1)]
|
|
|
|
matches.append(result)
|
|
|
|
return {
|
|
"file": file_path,
|
|
"matches": matches,
|
|
}
|
|
except Exception as error:
|
|
raise Error(f"匹配正则表达式时出错: {str(error)}")
|
|
|
|
|
|
async def _search_code_in_folder_wrapper(params):
|
|
await writer_wrap(search_code_in_folder, params, "代码搜索完成", "代码搜索失败")
|
|
|
|
|
|
async def _extract_functions_wrapper(params):
|
|
await writer_wrap(extract_functions, params, "函数提取完成", "函数提取失败")
|
|
|
|
|
|
async def _extract_function_block_wrapper(params):
|
|
await writer_wrap(extract_function_block, params, "函数代码块提取完成", "函数代码块提取失败")
|
|
|
|
|
|
async def _find_code_blocks_wrapper(params):
|
|
await writer_wrap(find_code_blocks, params, "代码块查找完成", "代码块查找失败")
|
|
|
|
|
|
async def _read_regex_matches_wrapper(params):
|
|
await writer_wrap(read_regex_matches, params, "正则表达式匹配完成", "正则表达式匹配失败")
|
|
|
|
|
|
exports.search_code_in_folder = _search_code_in_folder_wrapper
|
|
exports.extract_functions = _extract_functions_wrapper
|
|
exports.extract_function_block = _extract_function_block_wrapper
|
|
exports.find_code_blocks = _find_code_blocks_wrapper
|
|
exports.read_regex_matches = _read_regex_matches_wrapper
|