- 高危词库从 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 等)
192 lines
6.3 KiB
Python
192 lines
6.3 KiB
Python
#!/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))
|