Files
HeXi/hexi/plugins/nonebot_plugin_mc_server_status/services/mc.py
T
sansenhoshiandClaude Code 814c1994b6 feat(mc-server-status): 服务器列表 Web 管理页 / 数据层抽离 / mcstatus 13 适配
- services/servers.py:校验 + 读写 + 增删改收敛成一份数据层(顶层不 import nonebot,
  可脱离运行时单测),群命令与 Web API 都只经它改数据,校验不再各写一套
- web_hub.py + /hub/mc_server_status 页面:按群列出服务器,抽屉新增/编辑(改名)/
  测试连接,行内删除(删空一台连带删群);每条路由自带 require_admin
- handlers/server.py 改为薄封装,只在需要命令特有提示时自己判断
- services/mc.py 的图标属性名兼容 mcstatus ≥13 的 icon(旧版叫 favicon,
  读错会把整条查询打成"查询失败")
- tests/test_mc_servers.py:数据层归一/改名/删群/坏文件兜底 + 图标解码两版属性名

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-22 14:23:46 +08:00

77 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""MC 服务器状态 —— 业务逻辑(Service 层)。"""
from __future__ import annotations
from base64 import b64decode
from io import BytesIO
from re import findall
from mcstatus import BedrockServer, JavaServer
from nonebot.adapters.onebot.v11 import Message, MessageSegment as MS
def _server_icon(status) -> bytes | None:
"""取服务器图标字节;没有图标或格式不认识就返回 None。
属性名跨版本换过:mcstatus ≥13 叫 `icon`,11.x/12.x 叫 `favicon`
(13.0.0 的 changelog:`JavaStatusResponse.favicon` → `icon`,值没变)。
值可能是完整的 data URI,也可能是不带前缀的裸 base64,两种都认。
"""
raw = getattr(status, "icon", None) or getattr(status, "favicon", None)
if not raw:
return None
data = raw.split("base64,", 1)[-1] if "base64," in raw else raw
try:
return b64decode(data)
except Exception: # noqa: BLE001 — 图标坏了不影响其它字段
return None
async def check_mc_status(
name: str, host: str, server_type: str
) -> str | Message:
try:
if server_type == "js":
js = await JavaServer.async_lookup(host, timeout=2)
status = js.status()
version_list = findall(r"\d+\.\d+(?:\.[\dxX]+)?", status.version.name)
if len(version_list) != 1:
version = f"{version_list[0]}-{version_list[-1]}"
else:
version = version_list[0]
online = f"{status.players.online}/{status.players.max}"
if status.players.online and status.players.sample:
anonymous_player = 0
_player_list = []
for p in status.players.sample:
if p.id == "00000000-0000-0000-0000-000000000000":
anonymous_player += 1
else:
_player_list.append(p.name)
if anonymous_player:
_player_list.append(f"[{anonymous_player}个匿名玩家]")
player_list = "\n".join(_player_list) if _player_list else "没返回玩家列表"
else:
player_list = "没人在线"
latency = round(status.latency)
icon_data = _server_icon(status)
icon = MS.image(BytesIO(icon_data)) + "\n" if icon_data else ""
msg = (
icon
+ f"名称:{name}\n版本:{version}\n在线:{online}\n延迟:{latency}ms\n在线列表:\n{player_list}"
)
else:
if host.find(":") != -1:
host, port = host.split(":")
else:
host, port = host, 19132
bds = BedrockServer(host=host, port=int(port))
status = await bds.async_status()
online = f"{status.players_online}/{status.players_max}"
latency = round(status.latency)
version = status.version.version
msg = f"名称:{name} 【{version}】\n在线:{online} 延迟:{latency}ms"
except Exception as e:
msg = f"名称:{name} 查询失败!\n错误:{repr(e)}"
return msg