90 lines
3.1 KiB
Python
90 lines
3.1 KiB
Python
|
|
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"),
|
||
|
|
}
|