xiaoli-cli 7.0.0: 重构底层架构 + 移除工具截断
- 重构 core/ 底层 (7文件): app/runner/engine/parser/history/safety/display - FC原生调用为主,prompt-JSON保底 - 移除所有工具返回截断 (ai_search/git_tools/cmd_executor/browser_auto/network_tools/sub_agent/tool_search) - 删除无用文件 (旧服务器/WebUI/彩蛋/启动器等21个文件) - 清理 constants.py 死代码 - 版本号更新为 7.0.0
This commit is contained in:
commit
78fea7ccdd
57 files changed
+18358
No files matched your search
@@ -0,0 +1,295 @@
|
||||
import requests
|
||||
import time
|
||||
import socket
|
||||
import ssl
|
||||
import json
|
||||
from urllib.parse import urlparse
|
||||
from colorama import Fore, Style
|
||||
|
||||
|
||||
class Liugin:
|
||||
"""网络工具插件"""
|
||||
|
||||
_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'
|
||||
|
||||
def __init__(self):
|
||||
self.cli = None
|
||||
self.timeout = 10
|
||||
self.max_retries = 2
|
||||
|
||||
def set_cli(self, cli):
|
||||
self.cli = cli
|
||||
self.cli.register_liugin_command('net', self.command_handler)
|
||||
self.cli.register_liugin_command('ping', lambda a: self.handle(f"ping {a}"))
|
||||
self.cli.register_liugin_command('website', lambda a: self.handle(f"status {a}"))
|
||||
|
||||
def command_handler(self, args):
|
||||
parts = args.strip().split(maxsplit=1)
|
||||
if not parts:
|
||||
return "用法: network_tools <操作> <参数>"
|
||||
return self.handle(f"{parts[0].lower()} {parts[1] if len(parts) > 1 else ''}")
|
||||
|
||||
def get_tool_info(self):
|
||||
return {
|
||||
"name": "network_tools",
|
||||
"description": "网络工具:ping/get/status/headers/ip/dns/ssl/port/post/put/delete",
|
||||
"keywords": ["网络", "ping", "get", "post", "dns", "ssl", "port", "ip", "headers", "status"],
|
||||
"usage": (
|
||||
"network_tools <操作> <参数>\n\n"
|
||||
"读取: get/status/headers <URL>\n"
|
||||
"诊断: ping/dns/ssl <域名>\n"
|
||||
"扫描: port <域名> [端口范围]\n"
|
||||
"请求: post/put/delete <URL> [JSON体]\n"
|
||||
"解析: ip <域名>"
|
||||
),
|
||||
}
|
||||
|
||||
def get_mcp_definition(self):
|
||||
return {
|
||||
"name": "network_tools",
|
||||
"description": "网络工具:读取网页、诊断连接、发送HTTP请求、DNS/SSL/端口扫描",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"operation": {
|
||||
"type": "string",
|
||||
"enum": ["ping", "get", "status", "headers", "ip", "dns", "ssl", "port", "post", "put", "delete"],
|
||||
},
|
||||
"target": {"type": "string", "description": "URL、域名或IP"},
|
||||
"extra": {"type": "string", "description": "附加参数:JSON body 或端口范围"}
|
||||
},
|
||||
"required": ["operation", "target"]
|
||||
}
|
||||
}
|
||||
|
||||
def convert_mcp_args(self, arguments):
|
||||
op = arguments.get("operation", "")
|
||||
target = arguments.get("target", "")
|
||||
extra = arguments.get("extra", "")
|
||||
return f"{op} {target} {extra}".strip()
|
||||
|
||||
def handle(self, args):
|
||||
try:
|
||||
parts = args.strip().split(maxsplit=2)
|
||||
if not parts:
|
||||
return "参数不足"
|
||||
op = parts[0].lower()
|
||||
target = parts[1].strip() if len(parts) > 1 else ""
|
||||
extra = parts[2].strip() if len(parts) > 2 else ""
|
||||
|
||||
if op == "post":
|
||||
return self._api(target, "POST", extra)
|
||||
elif op == "put":
|
||||
return self._api(target, "PUT", extra)
|
||||
elif op == "delete":
|
||||
return self._api(target, "DELETE", extra)
|
||||
elif op == "ping":
|
||||
return self._ping(target)
|
||||
elif op == "get":
|
||||
return self._get(target)
|
||||
elif op == "status":
|
||||
return self._status(target)
|
||||
elif op == "headers":
|
||||
return self._headers(target)
|
||||
elif op == "ip":
|
||||
return self._ip(target)
|
||||
elif op == "dns":
|
||||
return self._dns(target)
|
||||
elif op == "ssl":
|
||||
return self._ssl(target)
|
||||
elif op == "port":
|
||||
return self._port(target, extra)
|
||||
else:
|
||||
return f"不支持: {op}。支持: ping/get/status/headers/ip/dns/ssl/port/post/put/delete"
|
||||
except Exception as e:
|
||||
return f"错误: {e}"
|
||||
|
||||
def _url(self, t):
|
||||
return t if t.startswith(('http://', 'https://')) else 'https://' + t
|
||||
|
||||
def _req(self, url, method='GET', data=None, ct=None):
|
||||
headers = {'User-Agent': self._UA, 'Accept': '*/*'}
|
||||
if ct:
|
||||
headers['Content-Type'] = ct
|
||||
for i in range(self.max_retries + 1):
|
||||
try:
|
||||
r = requests.request(method, url, timeout=self.timeout, headers=headers, allow_redirects=True,
|
||||
json=data if ct == 'application/json' else None,
|
||||
data=data if (ct and ct != 'application/json') else None)
|
||||
if not r.encoding or r.encoding.lower() == 'iso-8859-1':
|
||||
r.encoding = r.apparent_encoding or 'utf-8'
|
||||
return r
|
||||
except requests.exceptions.Timeout:
|
||||
if i < self.max_retries:
|
||||
time.sleep(1)
|
||||
continue
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
|
||||
def _ping(self, t):
|
||||
url = self._url(t)
|
||||
start = time.time()
|
||||
r = self._req(url)
|
||||
ms = (time.time() - start) * 1000
|
||||
if not r:
|
||||
return f"Ping {t}: 连接失败"
|
||||
status = "成功" if r.status_code < 400 else f"HTTP {r.status_code}"
|
||||
extra = f"\n 重定向: {r.url}" if r.url != url else ""
|
||||
return f"Ping {t}:\n 状态: {status}\n 延迟: {ms:.0f}ms\n HTTP: {r.status_code}{extra}"
|
||||
|
||||
def _get(self, t):
|
||||
url = self._url(t)
|
||||
r = self._req(url)
|
||||
if not r:
|
||||
return f"GET {t}: 失败"
|
||||
content = r.text
|
||||
return f"GET {url} [{r.status_code}]:\n{content}"
|
||||
|
||||
def _status(self, t):
|
||||
url = self._url(t)
|
||||
r = self._req(url)
|
||||
if not r:
|
||||
return f"STATUS {t}: 失败"
|
||||
return (
|
||||
f"STATUS {url}:\n"
|
||||
f" 状态码: {r.status_code} {r.reason}\n"
|
||||
f" 延迟: {r.elapsed.total_seconds()*1000:.0f}ms\n"
|
||||
f" 大小: {len(r.content)} bytes\n"
|
||||
f" 服务器: {r.headers.get('Server', '?')}\n"
|
||||
f" 类型: {r.headers.get('Content-Type', '?')}\n"
|
||||
f" 最终URL: {r.url}"
|
||||
)
|
||||
|
||||
def _headers(self, t):
|
||||
url = self._url(t)
|
||||
r = self._req(url, method='HEAD')
|
||||
if not r:
|
||||
r = self._req(url)
|
||||
if not r:
|
||||
return f"HEADERS {t}: 失败"
|
||||
lines = [f"HEADERS {url}:"]
|
||||
for k, v in r.headers.items():
|
||||
lines.append(f" {k}: {v}")
|
||||
return "\n".join(lines)
|
||||
|
||||
def _ip(self, t):
|
||||
if t.startswith(('http://', 'https://')):
|
||||
t = urlparse(t).netloc
|
||||
try:
|
||||
ips = list(set(addr[4][0] for addr in socket.getaddrinfo(t, None)))
|
||||
return f"IP {t}:\n" + "\n".join(f" {ip}" for ip in ips)
|
||||
except socket.gaierror:
|
||||
return f"IP {t}: 解析失败"
|
||||
|
||||
def _dns(self, t):
|
||||
if t.startswith(('http://', 'https://')):
|
||||
t = urlparse(t).netloc
|
||||
lines = [f"DNS {t}:"]
|
||||
try:
|
||||
seen = set()
|
||||
for item in socket.getaddrinfo(t, None):
|
||||
ip = item[4][0]
|
||||
fam = "IPv4" if item[0] == socket.AF_INET else "IPv6"
|
||||
if (ip, fam) not in seen:
|
||||
seen.add((ip, fam))
|
||||
lines.append(f" {fam}: {ip}")
|
||||
except socket.gaierror as e:
|
||||
lines.append(f" 失败: {e}")
|
||||
fqdn = socket.getfqdn(t)
|
||||
if fqdn != t:
|
||||
lines.append(f" FQDN: {fqdn}")
|
||||
return "\n".join(lines)
|
||||
|
||||
def _ssl(self, t):
|
||||
if t.startswith(('http://', 'https://')):
|
||||
t = urlparse(t).netloc
|
||||
host = t.split(':')[0]
|
||||
lines = [f"SSL {t}:"]
|
||||
try:
|
||||
ctx = ssl.create_default_context()
|
||||
with ctx.wrap_socket(socket.socket(), server_hostname=host) as s:
|
||||
s.settimeout(self.timeout)
|
||||
s.connect((host, 443))
|
||||
cert = s.getpeercert()
|
||||
subj = dict(x[0] for x in cert.get('subject', []))
|
||||
iss = dict(x[0] for x in cert.get('issuer', []))
|
||||
lines.append(f" 主题: {subj.get('commonName', '?')}")
|
||||
lines.append(f" 颁发: {iss.get('organizationName', iss.get('commonName', '?'))}")
|
||||
lines.append(f" 生效: {cert.get('notBefore', '?')}")
|
||||
lines.append(f" 过期: {cert.get('notAfter', '?')}")
|
||||
san = cert.get('subjectAltName', [])
|
||||
if san:
|
||||
doms = [v for _, v in san[:5]]
|
||||
lines.append(f" SAN: {', '.join(doms)}{'...' if len(san) > 5 else ''}")
|
||||
lines.append(f" 状态: 有效")
|
||||
except ssl.SSLCertVerificationError as e:
|
||||
lines.append(f" 状态: 验证失败 - {e}")
|
||||
except socket.timeout:
|
||||
lines.append(f" 状态: 超时")
|
||||
except ConnectionRefusedError:
|
||||
lines.append(f" 状态: 连接被拒绝")
|
||||
except Exception as e:
|
||||
lines.append(f" 状态: {e}")
|
||||
return "\n".join(lines)
|
||||
|
||||
def _port(self, t, rng=''):
|
||||
if t.startswith(('http://', 'https://')):
|
||||
t = urlparse(t).netloc.split(':')[0]
|
||||
if '-' in rng:
|
||||
try:
|
||||
s, e = rng.split('-', 1)
|
||||
s, e = max(1, int(s)), min(65535, int(e))
|
||||
except ValueError:
|
||||
s, e = 1, 1024
|
||||
else:
|
||||
s, e = 1, 1024
|
||||
try:
|
||||
ip = socket.gethostbyname(t)
|
||||
except socket.gaierror:
|
||||
return f"PORT {t}: 解析失败"
|
||||
common = {21:'FTP',22:'SSH',23:'Telnet',25:'SMTP',53:'DNS',80:'HTTP',110:'POP3',143:'IMAP',443:'HTTPS',993:'IMAPS',995:'POP3S',3306:'MySQL',3389:'RDP',5432:'PostgreSQL',6379:'Redis',8080:'HTTP-Alt',8443:'HTTPS-Alt',27017:'MongoDB'}
|
||||
open_p = []
|
||||
for p in range(s, e + 1):
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(0.3)
|
||||
if sock.connect_ex((ip, p)) == 0:
|
||||
open_p.append((p, common.get(p, '?')))
|
||||
sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
lines = [f"PORT {t} ({s}-{e}), IP={ip}:"]
|
||||
if open_p:
|
||||
for p, svc in sorted(open_p):
|
||||
lines.append(f" {p}/{svc}")
|
||||
else:
|
||||
lines.append(f" 无开放端口")
|
||||
return "\n".join(lines)
|
||||
|
||||
def _api(self, url, method, body_str):
|
||||
url = self._url(url)
|
||||
data = None
|
||||
ct = None
|
||||
if body_str:
|
||||
try:
|
||||
data = json.loads(body_str)
|
||||
ct = 'application/json'
|
||||
except json.JSONDecodeError:
|
||||
data = body_str
|
||||
ct = 'text/plain'
|
||||
r = self._req(url, method=method, data=data, ct=ct)
|
||||
if not r:
|
||||
return f"{method} {url}: 失败"
|
||||
lines = [f"{method} {url} [{r.status_code}]"]
|
||||
ctype = r.headers.get('Content-Type', '')
|
||||
if 'json' in ctype:
|
||||
try:
|
||||
body = json.dumps(r.json(), ensure_ascii=False, indent=2)
|
||||
lines.append(body)
|
||||
except Exception:
|
||||
lines.append(r.text)
|
||||
else:
|
||||
lines.append(r.text)
|
||||
return "\n".join(lines)
|
||||
Reference in new issue
Block a user