import asyncio import logging import struct import time from yuxi.channel.extensions.minecraft.client import McClient from yuxi.channel.extensions.minecraft.protocol import write_varint, write_string, read_string, write_long logger = logging.getLogger(__name__) async def probe_mc_server( host: str, port: int, version: str = "auto", ) -> dict: result = { "reachable": False, "latency_ms": None, "version_name": None, "protocol_version": None, "players_online": 0, "players_max": 0, } try: client = McClient(host, port) await client.connect() from yuxi.channel.extensions.minecraft.version_adapter import get_adapter, DEFAULT_PROTOCOL adapter = get_adapter(version) protocol_version = adapter.version if adapter else DEFAULT_PROTOCOL handshake_data = write_varint(protocol_version) + write_string(host) + struct.pack(">H", port) + write_varint(1) await client.send_raw(write_varint(len(handshake_data)) + write_varint(0x00) + handshake_data) client.state = "STATUS" await client.send_raw(write_varint(1) + write_varint(0x00)) packet = await asyncio.wait_for(client.recv_packet(), timeout=5.0) if packet.packet_id == 0x00: json_str, _ = read_string(packet.data, 0) import json status = json.loads(json_str) version_info = status.get("version", {}) players_info = status.get("players", {}) result["version_name"] = version_info.get("name") result["protocol_version"] = version_info.get("protocol") result["players_online"] = players_info.get("online", 0) result["players_max"] = players_info.get("max", 0) ping_start = time.time() * 1000 ping_data = write_long(int(ping_start)) await client.send_packet(0x01, ping_data) pong = await asyncio.wait_for(client.recv_packet(), timeout=5.0) if pong.packet_id == 0x01: result["latency_ms"] = round(time.time() * 1000 - ping_start) result["reachable"] = True logger.debug("MC probe success for %s:%d (latency=%s)", host, port, result["latency_ms"]) await client.disconnect() return result await client.disconnect() return result except Exception as e: logger.warning("MC probe failed for %s:%d: %s", host, port, e) return result def build_mc_status_summary(snapshot: object) -> dict: if snapshot is None: return { "channel": "minecraft", "account_id": "default", "connected": False, } return { "channel": "minecraft", "account_id": getattr(snapshot, "account_id", "default"), "host": getattr(snapshot, "host", ""), "username": getattr(snapshot, "username", ""), "connected": getattr(snapshot, "connected", False), "version": getattr(snapshot, "version", "auto"), "auth_mode": getattr(snapshot, "auth_mode", "offline"), }