新增 Minecraft 渠道扩展,支持在 Yuxi 平台中集成 Minecraft 游戏服务器渠道。 包含以下功能模块: - client: Minecraft 客户端封装 - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - outbound: 外发消息管理 - streaming: 流式消息处理 - pairing: 用户配对与绑定 - security: 安全校验 - auth: 认证管理 - accounts: 账户管理 - dedupe: 消息去重 - monitor: 渠道状态监控 - status: 会话状态管理 - protocol: Minecraft 协议处理 - rcon: RCON 远程控制 - keepalive: 连接保活 - version_adapter: 版本适配 - setup: 初始化设置 - types: 类型定义
75 lines
2.3 KiB
Python
75 lines
2.3 KiB
Python
import asyncio
|
|
import logging
|
|
import struct
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
RCON_TYPE_COMMAND = 2
|
|
RCON_TYPE_AUTH = 3
|
|
RCON_TYPE_RESPONSE = 0
|
|
|
|
|
|
class McRconClient:
|
|
def __init__(self, host: str, port: int, password: str):
|
|
self.host = host
|
|
self.port = port
|
|
self.password = password
|
|
self._reader: asyncio.StreamReader | None = None
|
|
self._writer: asyncio.StreamWriter | None = None
|
|
self._request_id = 0
|
|
|
|
async def connect(self) -> bool:
|
|
try:
|
|
self._reader, self._writer = await asyncio.wait_for(
|
|
asyncio.open_connection(self.host, self.port), timeout=10
|
|
)
|
|
except Exception:
|
|
logger.exception("RCON connection failed to %s:%d", self.host, self.port)
|
|
return False
|
|
|
|
self._request_id += 1
|
|
success = await self._send_packet(RCON_TYPE_AUTH, self.password)
|
|
if not success:
|
|
logger.error("RCON authentication failed")
|
|
return False
|
|
return True
|
|
|
|
async def execute(self, command: str) -> str:
|
|
if not self._writer or self._writer.is_closing():
|
|
if not await self.connect():
|
|
return "RCON connection failed"
|
|
self._request_id += 1
|
|
return await self._send_packet(RCON_TYPE_COMMAND, command)
|
|
|
|
async def _send_packet(self, ptype: int, payload: str) -> str:
|
|
req_id = self._request_id
|
|
packet = struct.pack("<iii", len(payload) + 10, req_id, ptype) + payload.encode("utf-8") + b"\x00\x00"
|
|
self._writer.write(packet)
|
|
await self._writer.drain()
|
|
|
|
response_parts = []
|
|
while True:
|
|
length_data = await self._reader.readexactly(4)
|
|
length = struct.unpack("<i", length_data)[0]
|
|
remaining = await self._reader.readexactly(length)
|
|
resp_id = struct.unpack("<i", remaining[:4])[0]
|
|
body = remaining[8:-2].decode("utf-8", errors="replace")
|
|
|
|
if resp_id != req_id:
|
|
continue
|
|
|
|
response_parts.append(body)
|
|
|
|
if ptype == RCON_TYPE_AUTH:
|
|
return resp_id != -1
|
|
|
|
if len(body) < 4096:
|
|
break
|
|
|
|
return "".join(response_parts)
|
|
|
|
async def close(self):
|
|
if self._writer:
|
|
self._writer.close()
|
|
await self._writer.wait_closed()
|