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 \n" "诊断: ping/dns/ssl <域名>\n" "扫描: port <域名> [端口范围]\n" "请求: post/put/delete [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)