Files
chatrebot_aireply_plug/scripts/mc_status.py
Claw e1fbd31c64 完整 AgentSkill:词库外部化 + 全部脚本/Skill 脱敏上传
- 高危词库从 process.py 抽到 config.toml [security] section
- process.py 改为 _get_high_risk_words() 动态加载(配置优先)
- 新增 15 个 QQ 操作脚本(已脱敏:QQ号/IP/Token → 占位符)
- 新增 8 个 SKILL.md(qq-messenger/management/resolver/napcat-extras 等)
- 新增 SKILL.md 入口(完整部署方案文档)
- 保留 SDK 框架(plugin_modules.py, file_store_api.py, package.py 等)
2026-05-04 16:23:59 +08:00

192 lines
6.3 KiB
Python
Raw Permalink 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.

#!/usr/bin/env python3
"""获取Minecraft服务器运行状态JSON输出"""
import sys, os, subprocess, re
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from mc_config import RCON_HOST, RCON_PORT, RCON_PASSWORD, MC_SERVER_DIR
from mc_rcon import RCONClient
def get_mc_pid():
try:
result = subprocess.run(
["pgrep", "-f", "java.*(minecraft|forge|neoforge)"],
capture_output=True, text=True, timeout=5
)
if result.returncode == 0 and result.stdout.strip():
return int(result.stdout.strip().split()[0])
except Exception:
pass
return None
def get_memory(pid):
if not pid:
return None
try:
with open(f"/proc/{pid}/status", "r") as f:
for line in f:
if line.startswith("VmRSS:"):
kb = int(line.split()[1])
return {"mb": round(kb/1024), "gb": round(kb/1024/1024, 1)}
except Exception:
pass
return None
def get_cpu(pid):
if not pid:
return None
try:
with open(f"/proc/{pid}/stat", "r") as f:
stat = f.read().split()
utime, stime = int(stat[13]), int(stat[14])
clk_tck = os.sysconf(os.sysconf_names['SC_CLK_TCK'])
starttime = int(stat[21])
with open("/proc/uptime", "r") as f:
uptime = float(f.read().split()[0])
proc_uptime = uptime - (starttime / clk_tck)
if proc_uptime > 0:
return round(((utime + stime) / clk_tck) / proc_uptime * 100, 1)
except Exception:
pass
return None
def get_uptime(pid):
if not pid:
return None
try:
with open(f"/proc/{pid}/stat", "r") as f:
stat = f.read().split()
clk_tck = os.sysconf(os.sysconf_names['SC_CLK_TCK'])
starttime = int(stat[21])
with open("/proc/uptime", "r") as f:
uptime = float(f.read().split()[0])
sec = uptime - (starttime / clk_tck)
d, h, m = int(sec//86400), int((sec%86400)//3600), int((sec%3600)//60)
parts = []
if d: parts.append(f"{d}")
if h: parts.append(f"{h}小时")
parts.append(f"{m}分钟")
return "".join(parts)
except Exception:
return None
def get_tps():
try:
with RCONClient(RCON_HOST, RCON_PORT, RCON_PASSWORD) as client:
client.send_command("spark tps")
import time
time.sleep(3)
log_path = os.path.join(MC_SERVER_DIR, "logs", "latest.log")
if not os.path.exists(log_path):
return None
with open(log_path, "r", errors="ignore") as f:
lines = f.readlines()
for line in reversed(lines[-500:]):
if "tps" not in line.lower():
continue
m = re.search(r'\*?([\d.]+),\s*\*?([\d.]+),\s*\*?([\d.]+),\s*\*?([\d.]+),\s*\*?([\d.]+)', line)
if m:
return {
"tps_1m": float(m.group(3)),
"raw": f"5s:{m.group(1)} 10s:{m.group(2)} 1m:{m.group(3)} 5m:{m.group(4)} 15m:{m.group(5)}"
}
except Exception:
pass
return None
def get_version():
try:
for vendor, name in [("neoforged", "NeoForge"), ("minecraftforge", "Forge")]:
vdir = os.path.join(MC_SERVER_DIR, "libraries", "net", vendor, name.lower())
if os.path.exists(vdir):
vers = [d for d in os.listdir(vdir) if os.path.isdir(os.path.join(vdir, d))]
if vers:
return f"{name} {vers[0]}"
except Exception:
pass
return "未知"
def get_world_sizes():
sizes = {}
for w in ["world", "world_nether", "world_the_end"]:
p = os.path.join(MC_SERVER_DIR, w)
if os.path.exists(p):
try:
r = subprocess.run(["du", "-sh", p], capture_output=True, text=True, timeout=10)
if r.returncode == 0:
sizes[w] = r.stdout.split()[0]
except Exception:
sizes[w] = "?"
try:
r = subprocess.run(["du", "-sh", MC_SERVER_DIR], capture_output=True, text=True, timeout=30)
if r.returncode == 0:
sizes["total"] = r.stdout.split()[0]
except Exception:
pass
try:
st = os.statvfs("/")
total = st.f_frsize * st.f_blocks
used = st.f_frsize * (st.f_blocks - st.f_bfree)
sizes["disk_total_gb"] = round(total/1024**3, 1)
sizes["disk_used_gb"] = round(used/1024**3, 1)
sizes["disk_pct"] = round((used/total)*100, 1)
except Exception:
pass
return sizes
def get_system_load():
try:
with open("/proc/loadavg", "r") as f:
parts = f.read().split()
cpu_count = os.cpu_count() or 1
return {
"load_1": float(parts[0]),
"load_5": float(parts[1]),
"load_15": float(parts[2]),
"cpu_count": cpu_count,
"load_pct": round((float(parts[0])/cpu_count)*100, 1)
}
except Exception:
return None
def get_player_list():
try:
with RCONClient(RCON_HOST, RCON_PORT, RCON_PASSWORD) as client:
resp = client.send_command("list")
body = resp.get("body", "") if resp else ""
m = re.search(r'players online:\s*(.+)?$', body, re.IGNORECASE)
if m and m.group(1):
return [p.strip() for p in m.group(1).split(",") if p.strip()]
except Exception:
pass
return []
def get_player_count():
try:
with RCONClient(RCON_HOST, RCON_PORT, RCON_PASSWORD) as client:
resp = client.send_command("list")
body = resp.get("body", "") if resp else ""
m = re.search(r'There are (\d+) of a max of (\d+) players', body)
if m:
return {"online": int(m.group(1)), "max": int(m.group(2))}
except Exception:
pass
return {"online": 0, "max": 0}
if __name__ == "__main__":
import json
pid = get_mc_pid()
result = {
"online": bool(pid),
"pid": pid,
"players": get_player_count(),
"player_list": get_player_list(),
"memory_mb": get_memory(pid),
"cpu_percent": get_cpu(pid),
"uptime": get_uptime(pid),
"tps": get_tps(),
"version": get_version(),
"world_sizes": get_world_sizes(),
"system_load": get_system_load(),
}
print(json.dumps(result, ensure_ascii=False, indent=2))