# METADATA # { # "name": "market_duckduckgo", # "display_name": { # "zh": "DuckDuckGo 搜索(市场插件翻译)", # "en": "DuckDuckGo Search (translated from operit market)" # }, # "description": { # "zh": "从 operit 市场 duckduckgo.ts 忠实翻译:搜索与网页内容抓取。", # "en": "Faithful translation of operit market duckduckgo.ts: search & fetch." # }, # "category": "Search", # "tools": [ # { # "name": "search", # "description": { # "zh": "搜索 DuckDuckGo 并返回格式化结果", # "en": "Search DuckDuckGo and return formatted results" # }, # "parameters": [ # {"name": "query", "type": "string", "required": true}, # {"name": "max_results", "type": "string", "required": false} # ] # }, # { # "name": "fetch_content", # "description": { # "zh": "抓取并解析网页内容为纯文本", # "en": "Fetch and parse webpage content to plain text" # }, # "parameters": [ # {"name": "url", "type": "string", "required": true} # ] # } # ] # } import re import html import asyncio BASE_URL = "https://html.duckduckgo.com/html/" _UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" class RateLimiter: """轻量异步令牌桶,忠实于 TS 版 RateLimiter.acquire()""" def __init__(self, max_concurrent: int): self._sem = asyncio.Semaphore(max_concurrent) async def acquire(self): await self._sem.acquire() def release(self): self._sem.release() _search_limiter = RateLimiter(20) _fetch_limiter = RateLimiter(20) def getErrorMessage(error): return str(error) if error else "unknown" def getErrorStack(error): import traceback return traceback.format_exc() def _decode_entities(text: str) -> str: return html.unescape(text) def _format_results_for_llm(results: list) -> str: lines = [] for r in results: lines.append(f"{r['position']}. {r['title']}") lines.append(f" URL: {r['link']}") lines.append(f" {r['snippet']}") lines.append("") return "\n".join(lines).strip() async def wrap(func, params, ok_msg="操作成功", err_msg="操作失败"): try: result = await func(params) complete({"success": True, "message": ok_msg, "data": result}) except Exception as error: console.error(f"Error: {getErrorMessage(error)}") complete({"success": False, "message": getErrorMessage(error), "error_stack": getErrorStack(error)}) async def search(params): """忠实翻译 duckduckgo.ts search()""" query = params.get("query", "") max_results = 10 if params.get("max_results"): try: parsed = int(params["max_results"]) if not str(parsed).startswith("NaN"): max_results = parsed except (ValueError, TypeError): pass if not query: raise ValueError("查询不能为空") await _search_limiter.acquire() try: console.log(f"正在从DuckDuckGo搜索: {query}") client = OkHttp.newClient() request = (client.newRequest() .url(BASE_URL) .method("POST") .body({"q": query, "b": "", "kl": ""}, "form") .headers({"User-Agent": _UA})) response = await request.build().execute() if not response.isSuccessful(): raise RuntimeError(f"HTTP 错误! 状态码: {response.statusCode}") html_text = response.content results = [] result_regex = re.compile( r'